Merge pull request #9862 from nextcloud/accessibility

Accessibility 
pull/7205/merge
John Molakvoæ 2018-06-26 09:04:17 +07:00 committed by GitHub
commit f148e3fb29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 9402 additions and 16 deletions

@ -35,6 +35,13 @@ pipeline:
when:
matrix:
TESTS: vue-build-oauth2
vue-build-accessibility:
image: node
commands:
- ./build/vue-builds.sh ./apps/accessibility/js/accessibility.js
when:
matrix:
TESTS: vue-build-accessibility
checkers:
image: nextcloudci/php7.0:php7.0-19
commands:
@ -693,6 +700,7 @@ matrix:
- TESTS: vue-build-settings
- TESTS: vue-build-updatenotification
- TESTS: vue-build-oauth2
- TESTS: vue-build-accessibility
- TESTS: nodb-codecov
ENABLE_REDIS: true
- TESTS: db-codecov

1
.gitignore vendored

@ -12,6 +12,7 @@
# ignore all apps except core ones
/apps*/*
!/apps/accessibility
!/apps/comments
!/apps/dav
!/apps/files

@ -0,0 +1,12 @@
{
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "not ie <= 11"]
}
}
]
]
}

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

@ -0,0 +1,16 @@
module.exports = {
env: {
browser: true,
es6: true
},
extends: 'eslint:recommended',
parserOptions: {
sourceType: 'module'
},
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always']
}
};

@ -0,0 +1,12 @@
.DS_Store
node_modules/
dist/
npm-debug.log
yarn-error.log
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln

@ -0,0 +1,3 @@
{
"esversion": 6
}

@ -0,0 +1,26 @@
all: dev-setup build-js-production
dev-setup: clean clean-dev npm-init
npm-init:
npm install
npm-update:
npm update
build-js:
npm run dev
build-js-production:
npm run build
watch-js:
npm run watch
clean:
rm -f js/accessibility.js
rm -f js/accessibility.js.map
clean-dev:
rm -rf node_modules

@ -0,0 +1,22 @@
# Accessibility ♿
> This app provide multiple features to ease the use of nextcloud.
## Build Setup
``` bash
# install dependencies
make dev-setup
# build for development
make build-js
# build for development and watch edits
make watch-js
# build for production with minification
make build-js-production
# clean output files
make clean
```

@ -0,0 +1,30 @@
<?php
declare (strict_types = 1);
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
$app = new \OCA\Accessibility\AppInfo\Application();
// Separate from the constructor since the route are not initialized before that
// 1. create the app
// 2. generate css route and inject
$app->injectCss();

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>accessibility</id>
<name>Accessibility</name>
<summary>Accessibility options for nextcloud</summary>
<description><![CDATA[Provides multiple accessibilities options to ease your use of nextcloud]]></description>
<version>1.0.0</version>
<licence>agpl</licence>
<author>John Molakvoæ</author>
<namespace>Accessibility</namespace>
<category>accessibility</category>
<default_enable/>
<dependencies>
<nextcloud min-version="14" max-version="14"/>
</dependencies>
<default_enable/>
<bugs>https://github.com/nextcloud/server/issues</bugs>
<settings>
<personal>OCA\Accessibility\Settings\Personal</personal>
<personal-section>OCA\Accessibility\Settings\PersonalSection</personal-section>
</settings>
</info>

@ -0,0 +1,40 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
return [
'routes' => [
['name' => 'accessibility#getCss', 'url' => '/css/user-{md5}.css', 'verb' => 'GET'],
],
'ocs' => [
[
'name' => 'Config#getConfig',
'url' => '/api/v1/config',
'verb' => 'GET',
],
[
'name' => 'Config#setConfig',
'url' => '/api/v1/config/{key}',
'verb' => 'POST',
],
]
];

@ -0,0 +1,22 @@
@font-face {
font-family: 'OpenDyslexic';
font-style: normal;
font-weight: 300;
src: url('../fonts/OpenDyslexic-Regular.woff') format('woff');
}
@font-face {
font-family: 'OpenDyslexic';
font-style: normal;
font-weight: normal;
src: url('../fonts/OpenDyslexic-Regular.woff') format('woff');
}
@font-face {
font-family: 'OpenDyslexic';
font-style: normal;
font-weight: 600;
src: url('../fonts/OpenDyslexic-Bold.woff') format('woff');
}
$font-face: OpenDyslexic, 'Open Sans', Frutiger, Calibri, 'Myriad Pro', Myriad, sans-serif;

@ -0,0 +1,59 @@
.preview-list {
display: flex;
flex-wrap: wrap;
}
.preview {
display: flex;
flex-direction: column;
min-width: 250px;
max-width: 400px;
flex: 1 1 300px;
border: 1px solid var(--color-border);
padding: 10px;
border-radius: var(--border-radius);
transition: all 200ms ease-in-out;
filter: drop-shadow(0 1px 2px var(--color-box-shadow));
background-color: var(--color-main-background);
opacity: 0.9;
margin: 10px;
position: relative;
&,
* {
cursor: pointer;
user-select: none;
}
&:hover,
&:focus,
&.selected {
filter: drop-shadow(0 1px 4px var(--color-box-shadow));
opacity: 1;
}
.preview-image {
height: 200px;
background-position: top left;
background-size: cover;
background-repeat: no-repeat;
}
h3 {
display: flex;
justify-content: space-between;
line-height: 1em;
align-items: center;
}
p {
text-align: justify;
}
.icon-checkmark-color {
transition: all 100ms ease-in-out;
border-radius: 1em;
padding: 4px 5px 4px 20px;
background-position: 4px center;
opacity: 0;
visibility: hidden;
}
&.selected .icon-checkmark-color {
opacity: 1;
visibility: visible;
box-shadow: 0 0 0 1px var(--color-success);
}
}

@ -0,0 +1,48 @@
// SCSS variables
$color-main-text: #d8d8d8;
$color-main-background: #181818;
$color-background-dark: lighten($color-main-background, 4%);
$color-background-darker: lighten($color-main-background, 8%);
$color-text-maxcontrast: darken($color-main-text, 30%);
$color-text-light: darken($color-main-text, 10%);
$color-text-lighter: darken($color-main-text, 20%);
$color-loading-light: #777;
$color-loading-dark: #ccc;
$color-box-shadow: rgba(darken($color-main-background, 70%), 0.5);
$color-border: lighten($color-main-background, 7%);
$color-border-dark: lighten($color-main-background, 14%);
#app-navigation > ul > li > a:first-child,
#app-navigation > ul > li > ul > li > a:first-child,
#expanddiv a {
img {
filter: invert(100%);
}
}
.bubble,
.app-navigation-entry-menu,
.popovermenu {
li {
> button,
> a,
> .menuitem {
> img {
filter: invert(100%);
}
}
}
}
.federation-menu .icon-federation-menu {
filter: invert(100%);
}
.bubble,
.app-navigation-entry-menu,
.popovermenu,
#header .menu {
border: 1px solid var(--color-border);
}

@ -0,0 +1,58 @@
// Fonts
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: normal;
src: local('Open Sans'), local('OpenSans'),
url('../../../core/fonts/OpenSans-Regular.woff') format('woff');
}
/* overriding default light */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans'), local('OpenSans'),
url('../../../core/fonts/OpenSans-Regular.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 500 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('../../../core/fonts/OpenSans-Bold.woff') format('woff');
}
// SCSS variables
$color-main-text: #000;
$color-main-background: #fff;
$color-background-dark: darken($color-main-background, 30%);
$color-background-darker: darken($color-main-background, 30%);
$color-text-maxcontrast: $color-main-text;
$color-text-light: $color-main-text;
$color-text-lighter: $color-main-text;
$color-loading-light: #ddd;
$color-loading-dark: #000;
$color-box-shadow: $color-main-text;
$color-border: darken($color-main-background, 50%);
$color-border-dark: darken($color-main-background, 50%);
$font-face: 'Open Sans', Frutiger, Calibri, 'Myriad Pro', Myriad, sans-serif;
// Font weight reset
body {
font-weight: 400;
}
// Opacity trick
* {
opacity: 1 !important;
}

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
width="16"
height="16"
viewBox="0 0 16 16"
sodipodi:docname="app-dark.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1880"
inkscape:window-height="993"
id="namedview4"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="36.460193"
inkscape:cx="8.4752826"
inkscape:cy="18.273624"
inkscape:window-x="20"
inkscape:window-y="67"
inkscape:window-maximized="0"
inkscape:current-layer="g848"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g848"
transform="matrix(1.1307959,0,0,1.0801738,-1.0463882,-0.84269722)"
style="stroke-width:0.90481776;fill:#000000">
<circle
r="1.9587879"
cy="2.8315151"
cx="7.990303"
id="path844"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.90481776;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill" />
<path
sodipodi:nodetypes="scsssscsccccczzcccccssssccsscs"
inkscape:connector-curvature="0"
id="path843"
d="m 2.3519709,4.2557871 c -0.3126291,0 -0.4819578,0.1287194 -0.5322266,0.4028321 -0.053173,0.2899914 0.1062609,0.5159057 0.4370117,0.6274414 1.420521,0.4790277 2.4256503,0.6675279 3.8410483,0.9800416 0.327839,0.072385 0.6900295,0.2893751 0.6520998,1.0948483 C 6.6985575,8.451345 6.6372144,9.7041167 6.2631031,11.323353 6.0397526,12.290062 5.6782112,13.622766 5.4305838,14.3922 5.3416728,14.668443 5.281658,14.922355 5.281658,15.031849 c 0,0.08257 0.053443,0.249642 0.097656,0.307617 0.023823,0.03125 0.075039,0.07397 0.1147462,0.09522 0.062879,0.03361 0.096262,0.03787 0.2612304,0.03663 0.3361373,-0.0026 0.4608113,-0.08245 0.6665037,-0.429687 0.4838657,-0.939315 0.7378785,-2.132191 0.9402993,-3.014266 0.092189,-0.517235 0.2624476,-1.66347 0.6710287,-1.66347 0.4085811,0 0.4979093,0.95008 0.6812062,1.687228 0.1832969,0.737148 0.6122738,2.295202 0.7738717,2.704862 0.2137476,0.54186 0.5742238,0.836985 0.8764648,0.717774 0.03316,-0.01301 0.116937,-0.04526 0.187989,-0.0708 0.147435,-0.053 0.186715,-0.07824 0.219726,-0.156249 0.0655,-0.154793 -0.01013,-0.454047 -0.349121,-1.41114 C 9.7721847,11.597192 9.2651806,9.3990255 9.3334308,7.2974743 9.3630343,6.3859259 9.6837054,6.305369 10.075725,6.2075087 c 1.17285,-0.2927798 2.00228,-0.379973 3.387085,-0.8579717 0.468513,-0.1617184 0.727539,-0.2608264 0.727539,-0.5590819 0,-0.2554049 -0.153084,-0.4346541 -0.437011,-0.5102538 -0.116851,-0.031101 -0.318383,-0.028905 -0.632326,0.00243 -1.085189,0.10834 -2.939683,0.5153868 -4.0234365,0.6710819 -0.529944,0.076133 -1.7229392,0.094015 -2.2729489,0 C 5.6763224,4.7574297 4.1363176,4.4147025 2.9061699,4.2997326 2.6560099,4.2763537 2.4068766,4.2557871 2.3519709,4.2557871 Z"
style="fill:#000000;stroke-width:0.9048177" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
width="16"
height="16"
viewBox="0 0 16 16"
sodipodi:docname="app.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1880"
inkscape:window-height="993"
id="namedview4"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="36.460193"
inkscape:cx="8.4752826"
inkscape:cy="18.273624"
inkscape:window-x="20"
inkscape:window-y="67"
inkscape:window-maximized="0"
inkscape:current-layer="g848"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g848"
transform="matrix(1.1307959,0,0,1.0801738,-1.0463882,-0.84269722)"
style="stroke-width:0.90481776;fill:#000000">
<circle
r="1.9587879"
cy="2.8315151"
cx="7.990303"
id="path844"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.90481776;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke markers fill" />
<path
sodipodi:nodetypes="scsssscsccccczzcccccssssccsscs"
inkscape:connector-curvature="0"
id="path843"
d="m 2.3519709,4.2557871 c -0.3126291,0 -0.4819578,0.1287194 -0.5322266,0.4028321 -0.053173,0.2899914 0.1062609,0.5159057 0.4370117,0.6274414 1.420521,0.4790277 2.4256503,0.6675279 3.8410483,0.9800416 0.327839,0.072385 0.6900295,0.2893751 0.6520998,1.0948483 C 6.6985575,8.451345 6.6372144,9.7041167 6.2631031,11.323353 6.0397526,12.290062 5.6782112,13.622766 5.4305838,14.3922 5.3416728,14.668443 5.281658,14.922355 5.281658,15.031849 c 0,0.08257 0.053443,0.249642 0.097656,0.307617 0.023823,0.03125 0.075039,0.07397 0.1147462,0.09522 0.062879,0.03361 0.096262,0.03787 0.2612304,0.03663 0.3361373,-0.0026 0.4608113,-0.08245 0.6665037,-0.429687 0.4838657,-0.939315 0.7378785,-2.132191 0.9402993,-3.014266 0.092189,-0.517235 0.2624476,-1.66347 0.6710287,-1.66347 0.4085811,0 0.4979093,0.95008 0.6812062,1.687228 0.1832969,0.737148 0.6122738,2.295202 0.7738717,2.704862 0.2137476,0.54186 0.5742238,0.836985 0.8764648,0.717774 0.03316,-0.01301 0.116937,-0.04526 0.187989,-0.0708 0.147435,-0.053 0.186715,-0.07824 0.219726,-0.156249 0.0655,-0.154793 -0.01013,-0.454047 -0.349121,-1.41114 C 9.7721847,11.597192 9.2651806,9.3990255 9.3334308,7.2974743 9.3630343,6.3859259 9.6837054,6.305369 10.075725,6.2075087 c 1.17285,-0.2927798 2.00228,-0.379973 3.387085,-0.8579717 0.468513,-0.1617184 0.727539,-0.2608264 0.727539,-0.5590819 0,-0.2554049 -0.153084,-0.4346541 -0.437011,-0.5102538 -0.116851,-0.031101 -0.318383,-0.028905 -0.632326,0.00243 -1.085189,0.10834 -2.939683,0.5153868 -4.0234365,0.6710819 -0.529944,0.076133 -1.7229392,0.094015 -2.2729489,0 C 5.6763224,4.7574297 4.1363176,4.4147025 2.9061699,4.2997326 2.6560099,4.2763537 2.4068766,4.2557871 2.3519709,4.2557871 Z"
style="fill:#ffffff;stroke-width:0.9048177" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,83 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility;
use OCP\IL10N;
use OCP\IURLGenerator;
class AccessibilityProvider {
/** @var string */
protected $appName;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IL10N */
private $l;
/**
* Account constructor.
*
* @param string $appName
* @param IURLGenerator $urlGenerator
* @param IL10N $l
*/
public function __construct(string $appName,
IURLGenerator $urlGenerator,
IL10N $l) {
$this->appName = $appName;
$this->urlGenerator = $urlGenerator;
$this->l = $l;
}
public function getThemes() {
return array(
[
'id' => 'themehighcontrast',
'img' => $this->urlGenerator->imagePath($this->appName, 'theme-highcontrast.jpg'),
'title' => $this->l->t('High Contrast theme'),
'text' => $this->l->t('A high contrast theme to ease your navigation. Visual quality will be reduced but clarity will be increased.')
], [
'id' => 'themedark',
'img' => $this->urlGenerator->imagePath($this->appName, 'theme-dark.jpg'),
'title' => $this->l->t('Dark theme'),
'text' => $this->l->t('A dark theme to ease your eyes by reducing the overall luminosity and brightness of your navigation. This is suitable for people who use computes a lot or in low luminosity spaces.')
]
);
}
public function getFonts() {
return array(
[
'id' => 'fontdyslexic',
'img' => $this->urlGenerator->imagePath($this->appName, 'font-opendyslexic.jpg'),
'title' => $this->l->t('Dyslexia font'),
'text' => $this->l->t('OpenDyslexic is a free typeface/font designed to mitigate some of the common reading errors caused by dyslexia. The typeface was created by Abelardo Gonzalez, who released it through an open-source license.')
]
);
}
}

@ -0,0 +1,63 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility\AppInfo;
use OCP\AppFramework\App;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\IURLGenerator;
class Application extends App {
/** @var string */
protected $appName = 'accessibility';
/** @var IConfig */
private $config;
/** @var IUserSession */
private $userSession;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct() {
parent::__construct($this->appName);
$this->config = \OC::$server->getConfig();
$this->userSession = \OC::$server->getUserSession();
$this->urlGenerator = \OC::$server->getURLGenerator();
}
public function injectCss() {
// Inject the fake css on all pages if enabled and user is logged
$loggedUser = $this->userSession->getUser();
if (!is_null($loggedUser)) {
$userValues = $this->config->getUserKeys($loggedUser->getUID(), $this->appName);
if (count($userValues) > 0) {
$linkToCSS = $this->urlGenerator->linkToRoute($this->appName . '.accessibility.getCss', ['md5' => md5(implode('-', $userValues))]);
\OCP\Util::addHeader('link', ['rel' => 'stylesheet', 'href' => $linkToCSS]);
}
}
}
}

@ -0,0 +1,202 @@
<?php
declare (strict_types = 1);
/**
* @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility\Controller;
use Leafo\ScssPhp\Compiler;
use Leafo\ScssPhp\Exception\ParserException;
use Leafo\ScssPhp\Formatter\Crunched;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
class AccessibilityController extends Controller {
/** @var string */
protected $appName;
/** @var string */
protected $serverRoot;
/** @var IConfig */
private $config;
/** @var IUserManager */
private $userManager;
/** @var ILogger */
private $logger;
/** @var IURLGenerator */
private $urlGenerator;
/** @var ITimeFactory */
protected $timeFactory;
/** @var IUserSession */
private $userSession;
/** @var IAppManager */
private $appManager;
/**
* Account constructor.
*
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param IUserManager $userManager
* @param ILogger $logger
* @param IURLGenerator $urlGenerator
* @param ITimeFactory $timeFactory
* @param IUserSession $userSession
* @param IAppManager $appManager
*/
public function __construct(string $appName,
IRequest $request,
IConfig $config,
IUserManager $userManager,
ILogger $logger,
IURLGenerator $urlGenerator,
ITimeFactory $timeFactory,
IUserSession $userSession,
IAppManager $appManager) {
parent::__construct($appName, $request);
$this->appName = $appName;
$this->config = $config;
$this->userManager = $userManager;
$this->logger = $logger;
$this->urlGenerator = $urlGenerator;
$this->timeFactory = $timeFactory;
$this->userSession = $userSession;
$this->appManager = $appManager;
$this->serverRoot = \OC::$SERVERROOT;
$this->appRoot = $this->appManager->getAppPath($this->appName);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return DataDisplayResponse
*/
public function getCss(): DataDisplayResponse {
$css = '';
$imports = '';
$userValues = $this->getUserValues();
foreach ($userValues as $key => $scssFile) {
if ($scssFile !== false) {
$imports .= '@import "' . $scssFile . '";';
}
}
if ($imports !== '') {
$scss = new Compiler();
$scss->setImportPaths([
$this->appRoot . '/css/',
$this->serverRoot . '/core/css/'
]);
// Continue after throw
$scss->setIgnoreErrors(true);
$scss->setFormatter(Crunched::class);
// Import theme, variables and compile css4 variables
try {
$css .= $scss->compile(
$imports .
'@import "variables.scss";' .
'@import "css-variables.scss";'
);
} catch (ParserException $e) {
$this->logger->error($e->getMessage(), ['app' => 'core']);
}
}
// We don't want to override vars with url since path is different
$css = $this->filterOutRule('/--[a-z-:]+url\([^;]+\)/mi', $css);
// Rebase all urls
$appWebRoot = substr($this->appRoot, strlen($this->serverRoot) - strlen(\OC::$WEBROOT));
$css = $this->rebaseUrls($css, $appWebRoot . '/css');
$response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
// Set cache control
$ttl = 31536000;
$response->addHeader('Cache-Control', 'max-age=' . $ttl . ', immutable');
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
$expires->add(new \DateInterval('PT' . $ttl . 'S'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC1123));
$response->addHeader('Pragma', 'cache');
return $response;
}
/**
* Return an array with the user theme & font settings
*
* @return array
*/
private function getUserValues(): array{
$userTheme = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'theme', false);
$userFont = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'font', false);
return [$userTheme, $userFont];
}
/**
* Remove all matches from the $rule regex
*
* @param string $rule regex to match
* @param string $css string to parse
* @return string
*/
private function filterOutRule(string $rule, string $css): string {
return preg_replace($rule, '', $css);
}
/**
* Add the correct uri prefix to make uri valid again
*
* @param string $css
* @param string $webDir
* @return string
*/
private function rebaseUrls(string $css, string $webDir): string {
$re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
$subst = 'url(\'' . $webDir . '/$1\')';
return preg_replace($re, $subst, $css);
}
}

@ -0,0 +1,121 @@
<?php
declare (strict_types = 1);
/**
* @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility\Controller;
use OCA\Accessibility\AccessibilityProvider;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
class ConfigController extends OCSController {
/** @var string */
protected $appName;
/** @var string */
protected $serverRoot;
/** @var IConfig */
private $config;
/** @var IUserSession */
private $userSession;
/** @var AccessibilityProvider */
private $accessibilityProvider;
/**
* Config constructor.
*
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param IUserSession $userSession
* @param AccessibilityProvider $accessibilityProvider
*/
public function __construct(string $appName,
IRequest $request,
IConfig $config,
IUserSession $userSession,
AccessibilityProvider $accessibilityProvider) {
parent::__construct($appName, $request);
$this->appName = $appName;
$this->config = $config;
$this->userSession = $userSession;
$this->accessibilityProvider = $accessibilityProvider;
}
/**
* @NoAdminRequired
*
* Get user accessibility config
*
* @param string $key theme or font
* @return DataResponse
*/
public function getConfig(): DataResponse {
return new DataResponse([
'theme' => $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'theme', false),
'font' => $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'font', false)
]);
}
/**
* @NoAdminRequired
*
* Set theme or font config
*
* @param string $key theme or font
* @return DataResponse
* @throws Exception
*/
public function setConfig(string $key, $value): DataResponse {
if ($key === 'theme' || $key === 'font') {
$themes = $this->accessibilityProvider->getThemes();
$fonts = $this->accessibilityProvider->getFonts();
if ($value === false) {
$this->config->deleteUserValue($this->userSession->getUser()->getUID(), $this->appName, $key);
return new DataResponse();
}
$availableOptions = array_map(function($option) {
return $option['id'];
}, array_merge($themes, $fonts));
if (in_array($value, $availableOptions)) {
$this->config->setUserValue($this->userSession->getUser()->getUID(), $this->appName, $key, $value);
return new DataResponse();
}
throw new OCSBadRequestException('Invalid value: ' . $value);
}
throw new OCSBadRequestException('Invalid key: ' . $key);
}
}

@ -0,0 +1,113 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility\Settings;
use OCA\Accessibility\AccessibilityProvider;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Settings\ISettings;
class Personal implements ISettings {
/** @var string */
protected $appName;
/** @var IConfig */
private $config;
/** @var IUserSession */
private $userSession;
/** @var IL10N */
private $l;
/** @var IURLGenerator */
private $urlGenerator;
/** @var AccessibilityProvider */
private $accessibilityProvider;
/**
* Settings constructor.
*
* @param string $appName
* @param IConfig $config
* @param IUserSession $userSession
* @param IL10N $l
* @param IURLGenerator $urlGenerator
* @param AccessibilityProvider $accessibilityProvider
*/
public function __construct(string $appName,
IConfig $config,
IUserSession $userSession,
IL10N $l,
IURLGenerator $urlGenerator,
AccessibilityProvider $accessibilityProvider) {
$this->appName = $appName;
$this->config = $config;
$this->userSession = $userSession;
$this->l = $l;
$this->urlGenerator = $urlGenerator;
$this->accessibilityProvider = $accessibilityProvider;
}
/**
* @return TemplateResponse returns the instance with all parameters set, ready to be rendered
* @since 9.1
*/
public function getForm() {
$serverData = [
'themes' => $this->accessibilityProvider->getThemes(),
'fonts' => $this->accessibilityProvider->getFonts(),
'theme' => $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'theme', false),
'font' => $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'font', false)
];
return new TemplateResponse($this->appName, 'settings-personal', ['serverData' => $serverData]);
}
/**
* @return string the section ID, e.g. 'sharing'
* @since 9.1
*/
public function getSection() {
return $this->appName;
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* E.g.: 70
* @since 9.1
*/
public function getPriority() {
return 40;
}
}

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Accessibility\Settings;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class PersonalSection implements IIconSection {
/** @var string */
protected $appName;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IL10N */
private $l;
/**
* Personal Section constructor.
*
* @param string $appName
* @param IURLGenerator $urlGenerator
* @param IL10N $l
*/
public function __construct(string $appName,
IURLGenerator $urlGenerator,
IL10N $l) {
$this->appName = $appName;
$this->urlGenerator = $urlGenerator;
$this->l = $l;
}
/**
* returns the relative path to an 16*16 icon describing the section.
* e.g. '/core/img/places/files.svg'
*
* @returns string
* @since 13.0.0
*/
public function getIcon() {
return $this->urlGenerator->imagePath($this->appName, 'app-dark.svg');
}
/**
* returns the ID of the section. It is supposed to be a lower case string,
* e.g. 'ldap'
*
* @returns string
* @since 9.1
*/
public function getID() {
return $this->appName;
}
/**
* returns the translated name as it should be displayed, e.g. 'LDAP / AD
* integration'. Use the L10N service to translate it.
*
* @return string
* @since 9.1
*/
public function getName() {
return $this->l->t('Accessibility');
}
/**
* @return int whether the form should be rather on the top or bottom of
* the settings navigation. The sections are arranged in ascending order of
* the priority values. It is required to return a value between 0 and 99.
*
* E.g.: 70
* @since 9.1
*/
public function getPriority() {
return 15;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,35 @@
{
"name": "accessibility",
"description": "Provides multiple accessibilities options to ease your use of nextcloud",
"version": "1.0.0",
"author": "John Molakvoæ <skjnldsv@protonmail.com>",
"license": "agpl",
"private": true,
"scripts": {
"dev": "webpack --config webpack.dev.js",
"watch": "webpack --progress --watch --config webpack.dev.js",
"build": "webpack --progress --hide-modules --config webpack.prod.js"
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.16"
},
"browserslist": [
"last 2 versions",
"not ie <= 11"
],
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.7.0",
"css-loader": "^0.28.11",
"file-loader": "^1.1.11",
"node-sass": "^4.9.0",
"sass-loader": "^7.0.3",
"vue-loader": "^15.2.4",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.4",
"webpack-merge": "^4.1.2"
}
}

@ -0,0 +1,101 @@
<template>
<div id="accessibility">
<div id="themes" class="section">
<h2>{{t('accessibility', 'Themes')}}</h2>
<div class="themes-list preview-list">
<preview v-for="preview in themes" :preview="preview"
:key="preview.id" :selected="selected.theme"
v-on:select="selectTheme"></preview>
</div>
</div>
<div id="fonts" class="section">
<h2>{{t('accessibility', 'Fonts')}}</h2>
<div class="fonts-list preview-list">
<preview v-for="preview in fonts" :preview="preview"
:key="preview.id" :selected="selected.font"
v-on:select="selectFont"></preview>
</div>
</div>
</div>
</template>
<script>
import preview from './components/itemPreview';
import axios from 'axios';
export default {
name: 'app',
components: { preview },
beforeMount() {
// importing server data into the app
const serverDataElmt = document.getElementById('serverData');
if (serverDataElmt !== null) {
this.serverData = JSON.parse(
document.getElementById('serverData').dataset.server
);
}
},
data() {
return {
serverData: []
};
},
computed: {
themes() {
return this.serverData.themes;
},
fonts() {
return this.serverData.fonts;
},
selected() {
return {
theme: this.serverData.theme,
font: this.serverData.font
};
},
tokenHeaders() {
return { headers: { requesttoken: OC.requestToken } }
}
},
methods: {
selectTheme(id) {
this.selectItem('theme', id);
},
selectFont(id) {
this.selectItem('font', id);
},
/**
* Commit a change and force reload css
* Fetching the file again will trigger the server update
*
* @param {string} type type of the change (font or theme)
* @param {string} id the data of the change
*/
selectItem(type, id) {
axios
.post(OC.linkToOCS('apps/accessibility/api/v1/config', 2) + type, {value: id}, this.tokenHeaders)
.then(response => {
this.serverData[type] = id;
// Remove old link
let link = document.querySelector('link[rel=stylesheet][href*=accessibility][href*=user-]');
if (!link) {
// insert new css
let link = document.createElement('link');
link.rel = 'stylesheet';
link.href = OC.generateUrl('/apps/accessibility/css/user-style.css');
document.head.appendChild(link)
} else {
// force update
link.href = link.href.split('?')[0] + '?v=' + new Date().getTime();
}
})
.catch(err => {
console.log(err, err.response);
OC.Notification.showTemporary(t('accessibility', err.response.data.ocs.meta.message + '. Unable to apply the setting.'));
});
}
}
};
</script>

@ -0,0 +1,27 @@
<template>
<a :class="{preview: true, selected: preview.id === selected}"
href="#" @click="selectItem">
<div class="preview-image" :style="{backgroundImage: 'url(' + preview.img + ')'}"></div>
<h3>
<span>{{preview.title}}</span>
<div class="icon-checkmark-color">{{t('accessibility', 'enabled')}}</div>
</h3>
<p>{{preview.text}}</p>
</a>
</template>
<script>
export default {
name: 'itemPreview',
props: ['preview', 'selected'],
methods: {
selectItem() {
this.$emit(
'select',
// if we clicked the already selected one: disable it
this.preview.id === this.selected ? false : this.preview.id
);
}
}
};
</script>

@ -0,0 +1,12 @@
import Vue from 'vue';
import App from './App.vue';
/* global t */
// bind to window
Vue.prototype.OC = OC;
Vue.prototype.t = t;
new Vue({
el: '#accessibility',
render: h => h(App)
});

@ -0,0 +1,29 @@
<?php
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
script('accessibility', 'accessibility');
style('accessibility', 'style');
?>
<span id="serverData" data-server="<?php p(json_encode($_['serverData']));?>"></span>
<span id="accessibility"></span>

@ -0,0 +1,46 @@
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: path.join(__dirname, 'src', 'main.js'),
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/js/',
filename: 'accessibility.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
plugins: [new VueLoaderPlugin()],
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
}
};

@ -0,0 +1,12 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map'
});

@ -0,0 +1,7 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: '#source-map'
});

@ -325,6 +325,7 @@ Feature: provisioning
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And apps returned are
| accessibility |
| comments |
| dav |
| federatedfilesharing |

@ -475,13 +475,26 @@ nav[role='navigation'] {
}
}
/* focused app visual feedback */
li:hover a,
li a:focus,
li a.active {
opacity: 1;
}
li:hover a,
li a:focus {
+ span {
display: inline-block;
}
}
li:hover span,
li:focus span {
display: inline-block;
}
/* hidden apps menu */
li img,
.icon-more-white {
display: inline-block;
@ -489,6 +502,7 @@ nav[role='navigation'] {
height: 20px;
}
/* app title popup */
li span {
display: none;
position: absolute;
@ -509,13 +523,8 @@ nav[role='navigation'] {
z-index: 100;
}
li:hover span {
display: inline-block;
}
/* show triangle below active app */
li:hover a:before,
li a.active:before {
li a::before {
content: ' ';
height: 0;
width: 0;
@ -529,20 +538,24 @@ nav[role='navigation'] {
bottom: 0;
display: none;
}
li a.active:before,
li:hover a:before,
li:hover a.active:before {
/* triangle focus feedback */
li a.active::before,
li:hover a::before,
li:hover a.active::before,
li a:focus::before {
display: block;
}
li a.active:before {
li a.active::before {
z-index: 99;
}
li:hover a:before,
li a.active:hover:before {
li:hover a::before,
li:hover a::before,
li a.active:hover::before,
li a:focus::before {
z-index: 101;
}
&.menu-open li:hover a:before,
&.menu-open li a.active:before,
&.menu-open li:hover a::before,
&.menu-open li a.active::before,
&.menu-open li:hover span {
display: none !important;
}

@ -84,7 +84,7 @@ ul {
body {
background-color: var(--color-main-background);
font-weight: 400;
font-weight: 300;
font-size: .8em;
line-height: 1.6em;
font-family: var(--font-face);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,5 +1,6 @@
{
"shippedApps": [
"accessibility",
"activity",
"admin_audit",
"comments",