diff options
author | Jan-Christoph Borchardt <hey@jancborchardt.net> | 2016-07-28 11:12:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-07-28 11:12:39 +0200 |
commit | 9ebd0914b7442e68105a962990497c7b54adaf97 (patch) | |
tree | 02484455dc7fde1b96cd7164c1f98b825494f2a4 /apps | |
parent | 73311091bf27fc3f0bbc16652c73d1cf7dfb7586 (diff) | |
parent | 217b02aaa0a3cac88938e8968cee05830cacaf81 (diff) | |
download | nextcloud-server-9ebd0914b7442e68105a962990497c7b54adaf97.tar.gz nextcloud-server-9ebd0914b7442e68105a962990497c7b54adaf97.zip |
Merge pull request #415 from nextcloud/theming-colorize-checkboxes
Colorize checkboxes depending on theming color
Diffstat (limited to 'apps')
-rw-r--r-- | apps/theming/js/settings-admin.js | 27 | ||||
-rw-r--r-- | apps/theming/lib/controller/themingcontroller.php | 43 | ||||
-rw-r--r-- | apps/theming/lib/util.php | 25 | ||||
-rw-r--r-- | apps/theming/tests/lib/UtilTest.php | 21 | ||||
-rw-r--r-- | apps/theming/tests/lib/controller/ThemingControllerTest.php | 129 |
5 files changed, 191 insertions, 54 deletions
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js index 941ec5c711b..01ff9123842 100644 --- a/apps/theming/js/settings-admin.js +++ b/apps/theming/js/settings-admin.js @@ -46,29 +46,46 @@ function calculateLuminance(rgb) { return (0.299*r + 0.587*g + 0.114*b)/255; } +function generateRadioButton(color) { + var radioButton = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' + + '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="' + color + '"/></svg>'; + return btoa(radioButton); +} + function preview(setting, value) { if (setting === 'color') { var headerClass = document.getElementById('header'); var expandDisplayNameClass = document.getElementById('expandDisplayName'); var headerAppName = headerClass.getElementsByClassName('header-appname')[0]; var textColor, icon; + var luminance = calculateLuminance(value); + var elementColor = value; - if (calculateLuminance(value) > 0.5) { + if (luminance > 0.5) { textColor = "#000000"; icon = 'caret-dark'; } else { textColor = "#ffffff"; icon = 'caret'; } + if (luminance>0.8) { + elementColor = '#555555'; + } headerClass.style.background = value; headerClass.style.backgroundImage = '../img/logo-icon.svg'; expandDisplayNameClass.style.color = textColor; headerAppName.style.color = textColor; - $(headerClass).find('.icon-caret').each(function() { - $(this).css('background-image', "url('" + OC.getRootPath() + '/core/img/actions/' + icon + ".svg')"); - }); + $('#previewStyles').html( + '#header .icon-caret { background-image: url(\'' + OC.getRootPath() + '/core/img/actions/' + icon + '.svg\') }' + + 'input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' + + 'background-image:url(\'' + OC.getRootPath() + '/core/img/actions/checkmark-white.svg\');' + + 'background-color: ' + elementColor + '; background-position: center center; background-size:contain;' + + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;}' + + 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' + + 'background-image: url(\'data:image/svg+xml;base64,' + generateRadioButton(elementColor) + '\'); }' + ); } if (setting === 'logoMime') { console.log(setting); @@ -87,6 +104,8 @@ function preview(setting, value) { $(document).ready(function () { $('#theming [data-toggle="tooltip"]').tooltip(); + $('html > head').append($('<style type="text/css" id="previewStyles"></style>')); + var uploadParamsLogo = { pasteZone: null, dropZone: null, diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php index 3e5d6f3e0d1..6a61293828f 100644 --- a/apps/theming/lib/controller/themingcontroller.php +++ b/apps/theming/lib/controller/themingcontroller.php @@ -214,35 +214,46 @@ class ThemingController extends Controller { $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0'); $responseCss = ''; $color = $this->config->getAppValue($this->appName, 'color'); + $elementColor = Util::elementColor($color); if($color !== '') { $responseCss .= sprintf( - '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: %s}', + '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: %s}' . "\n", $color ); + $responseCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . + 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . + 'background-color: %s; background-position: center center; background-size:contain;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . + "}\n", + \OC::$WEBROOT, + $elementColor + ); + $responseCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' . + 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' . + "}\n"; } $logo = $this->config->getAppValue($this->appName, 'logoMime'); if($logo !== '') { - $responseCss .= sprintf('#header .logo { - background-image: url(\'./logo?v='.$cacheBusterValue.'\'); - background-size: contain; - } - #header .logo-icon { - background-image: url(\'./logo?v='.$cacheBusterValue.'\'); - background-size: contain; - }' + $responseCss .= sprintf( + '#header .logo {' . + 'background-image: url(\'./logo?v='.$cacheBusterValue.'\')' . + 'background-size: contain;' . + '}' . "\n" . + '#header .logo-icon {' . + 'background-image: url(\'./logo?v='.$cacheBusterValue.'\');' . + 'background-size: contain;' . + '}' . "\n" ); } $backgroundLogo = $this->config->getAppValue($this->appName, 'backgroundMime'); if($backgroundLogo !== '') { - $responseCss .= '#body-login { - background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\'); - }'; + $responseCss .= '#body-login {background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\');}' . "\n"; } if(Util::invertTextColor($color)) { - $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; } '; - $responseCss .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } '; - $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }'; - $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }'; + $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . "\n"; + $responseCss .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . "\n"; + $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . "\n"; + $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . "\n"; } \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT'); diff --git a/apps/theming/lib/util.php b/apps/theming/lib/util.php index 2088650b19d..f0ce30ac5ba 100644 --- a/apps/theming/lib/util.php +++ b/apps/theming/lib/util.php @@ -39,6 +39,21 @@ class Util { } /** + * get color for on-page elements: + * theme color by default, grey if theme color is to bright + * @param $color + * @return string + */ + public static function elementColor($color) { + $l = self::calculateLuminance($color); + if($l>0.8) { + return '#555555'; + } else { + return $color; + } + } + + /** * @param string $color rgb color value * @return float */ @@ -56,4 +71,14 @@ class Util { return (0.299 * $r + 0.587 * $g + 0.114 * $b)/255; } + /** + * @param $color + * @return string base64 encoded radio button svg + */ + public static function generateRadioButton($color) { + $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' . + '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>'; + return base64_encode($radioButtonIcon); + } + } diff --git a/apps/theming/tests/lib/UtilTest.php b/apps/theming/tests/lib/UtilTest.php index 9ebb11d6288..cf64b389d11 100644 --- a/apps/theming/tests/lib/UtilTest.php +++ b/apps/theming/tests/lib/UtilTest.php @@ -65,4 +65,25 @@ class UtilTest extends TestCase { $invert = Util::invertTextColor(''); $this->assertEquals(false, $invert); } + + public function testElementColorDefault() { + $elementColor = Util::elementColor("#000000"); + $this->assertEquals('#000000', $elementColor); + } + + public function testElementColorOnBrightBackground() { + $elementColor = Util::elementColor('#ffffff'); + $this->assertEquals('#555555', $elementColor); + } + + public function testGenerateRadioButtonWhite() { + $button = Util::generateRadioButton('#ffffff'); + $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiNmZmZmZmYiLz48L3N2Zz4='; + $this->assertEquals($expected, $button); + } + public function testGenerateRadioButtonBlack() { + $button = Util::generateRadioButton('#000000'); + $expected = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+PHBhdGggZD0iTTggMWE3IDcgMCAwIDAtNyA3IDcgNyAwIDAgMCA3IDcgNyA3IDAgMCAwIDctNyA3IDcgMCAwIDAtNy03em0wIDFhNiA2IDAgMCAxIDYgNiA2IDYgMCAwIDEtNiA2IDYgNiAwIDAgMS02LTYgNiA2IDAgMCAxIDYtNnptMCAyYTQgNCAwIDEgMCAwIDggNCA0IDAgMCAwIDAtOHoiIGZpbGw9IiMwMDAwMDAiLz48L3N2Zz4='; + $this->assertEquals($expected, $button); + } } diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php index 24eb0510f99..59e33c755db 100644 --- a/apps/theming/tests/lib/controller/ThemingControllerTest.php +++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php @@ -26,6 +26,7 @@ namespace OCA\Theming\Tests\Controller; use OCA\Theming\Controller\ThemingController; use OCA\Theming\Template; +use OCA\Theming\Util; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\Files\IRootFolder; @@ -327,7 +328,20 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn(''); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}', 'style', 'text/css'); + $elementColor = '#000'; + $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}' . "\n"; + $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . + 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . + 'background-color: %s; background-position: center center; background-size:contain;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . + "}\n", + \OC::$WEBROOT, + $elementColor + ); + $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' . + 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' . + "}\n"; + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -353,8 +367,24 @@ class ThemingControllerTest extends TestCase { ->method('getAppValue') ->with('theming', 'backgroundMime', '') ->willReturn(''); - - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); + $elementColor = '#555555'; + $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}' . "\n"; + $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . + 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . + 'background-color: %s; background-position: center center; background-size:contain;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . + "}\n", + \OC::$WEBROOT, + $elementColor + ); + $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' . + 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' . + "}\n"; + $expectedCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . "\n" . + '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . "\n" . + '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . "\n" . + '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . "\n"; + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -381,14 +411,15 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn(''); - $expected = new Http\DataDownloadResponse('#header .logo { - background-image: url(\'./logo?v=0\'); - background-size: contain; - } - #header .logo-icon { - background-image: url(\'./logo?v=0\'); - background-size: contain; - }', 'style', 'text/css'); + $expectedCss = '#header .logo {' . + 'background-image: url(\'./logo?v=0\')' . + 'background-size: contain;' . + '}' . "\n" . + '#header .logo-icon {' . + 'background-image: url(\'./logo?v=0\');' . + 'background-size: contain;' . + '}' . "\n"; + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -415,9 +446,8 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn('text/svg'); - $expected = new Http\DataDownloadResponse('#body-login { - background-image: url(\'./loginbackground?v=0\'); - }', 'style', 'text/css'); + $expectedCss = '#body-login {background-image: url(\'./loginbackground?v=0\');}' . "\n"; + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -444,16 +474,30 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn('image/png'); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}#header .logo { - background-image: url(\'./logo?v=0\'); - background-size: contain; - } - #header .logo-icon { - background-image: url(\'./logo?v=0\'); - background-size: contain; - }#body-login { - background-image: url(\'./loginbackground?v=0\'); - }', 'style', 'text/css'); + $elementColor = '#000'; + $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #000}' . "\n"; + $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . + 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . + 'background-color: %s; background-position: center center; background-size:contain;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . + "}\n", + \OC::$WEBROOT, + $elementColor + ); + $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' . + 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' . + "}\n"; + $expectedCss .= '#header .logo {' . + 'background-image: url(\'./logo?v=0\')' . + 'background-size: contain;' . + '}' . "\n" . + '#header .logo-icon {' . + 'background-image: url(\'./logo?v=0\');' . + 'background-size: contain;' . + '}' . "\n"; + $expectedCss .= '#body-login {background-image: url(\'./loginbackground?v=0\');}' . PHP_EOL; + + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -479,16 +523,33 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn('image/png'); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}#header .logo { - background-image: url(\'./logo?v=0\'); - background-size: contain; - } - #header .logo-icon { - background-image: url(\'./logo?v=0\'); - background-size: contain; - }#body-login { - background-image: url(\'./loginbackground?v=0\'); - }#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); + $elementColor = '#555555'; + $expectedCss = '#body-user #header,#body-settings #header,#body-public #header,#body-login,.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid {background-color: #fff}' . "\n"; + $expectedCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . + 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . + 'background-color: %s; background-position: center center; background-size:contain;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . + "}\n", + \OC::$WEBROOT, + $elementColor + ); + $expectedCss .= 'input[type="radio"].radio:checked:not(.radio--white):not(:disabled) + label:before {' . + 'background-image: url(\'data:image/svg+xml;base64,'.Util::generateRadioButton($elementColor).'\');' . + "}\n"; + $expectedCss .= '#header .logo {' . + 'background-image: url(\'./logo?v=0\')' . + 'background-size: contain;' . + '}' . PHP_EOL . + '#header .logo-icon {' . + 'background-image: url(\'./logo?v=0\');' . + 'background-size: contain;' . + '}' . PHP_EOL; + $expectedCss .= '#body-login {background-image: url(\'./loginbackground?v=0\');}' . PHP_EOL; + $expectedCss .= '#header .header-appname, #expandDisplayName { color: #000000; }' . PHP_EOL . + '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); }' . PHP_EOL . + '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }' . PHP_EOL . + '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }' . PHP_EOL; + $expected = new Http\DataDownloadResponse($expectedCss, 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } |