Compare commits

...
Sign in to create a new pull request.

420 commits

Author SHA1 Message Date
e159aff3e7 release v4.1.1 2025-03-12 18:35:34 +01:00
b8998d356d Merge pull request 'bugfix/issue369' (#399) from bugfix/issue369 into develop
Reviewed-on: deblan/side_menu#399
2025-03-12 18:21:36 +01:00
20946bcb06 update changelog 2025-03-12 18:21:18 +01:00
2bdea0d828 fix(SideMenu): ncApps must be an array (#369) 2025-03-12 18:20:15 +01:00
b2fc5340be Merge pull request 'bugfix/issue397' (#398) from bugfix/issue397 into develop
Reviewed-on: deblan/side_menu#398
2025-03-12 17:59:23 +01:00
ba737434e5 update changelog 2025-03-12 17:58:29 +01:00
788affe386 fix: add missing NoCSRFRequired import in CssController (#397) 2025-03-12 17:57:49 +01:00
45c7bd361f update changelog 2025-03-10 19:17:04 +01:00
4c4540e2a2 Merge pull request 'feature/nc31' (#394) from feature/nc31 into develop
Reviewed-on: deblan/side_menu#394
2025-03-10 19:14:21 +01:00
0edde4b9b9 fix(ci): add woodpecker 3 syntax 2025-03-10 19:08:16 +01:00
a15c281aaa fix(appinfo): add cdata and fix urls 2025-03-10 19:05:42 +01:00
214cdaa330 apply lint (php) 2025-03-10 18:45:26 +01:00
7ab3269fbe fix(settings): remove non-existing and unused ILogger service 2025-03-10 18:42:12 +01:00
44fd4fab52 fix(service): add service constructor arguments 2025-03-10 18:41:09 +01:00
3058c225e0 style: apply php linter 2025-03-05 21:55:53 +01:00
b03a7f7bad refactor(controller): usage of attributes instead of annotations 2025-03-05 21:54:59 +01:00
37b9beb8f7 update app version 2024-10-30 19:07:47 +01:00
0402e00eac update changelog 2024-10-30 19:07:31 +01:00
8c9dc83a59 Merge pull request 'fix #369: The menu is displayed even if there are no apps' (#370) from bugfix/issue369 into develop
Reviewed-on: deblan/side_menu#370
2024-10-30 19:00:43 +01:00
66716ec53b Merge pull request 'fix top menu labels (fix #368)' (#371) from bugfix/issue368-top-menu-labels into develop
Reviewed-on: deblan/side_menu#371
2024-10-30 19:00:28 +01:00
879f9f092b fix op menu labels (fix #368) 2024-10-30 18:33:16 +01:00
66a2843a14 remove logs 2024-10-30 18:19:13 +01:00
c517adcb81 remove logs 2024-10-30 18:19:01 +01:00
84efcbd36c fix the menu is displayed even if there are no apps (fix #369) 2024-10-30 18:18:01 +01:00
6be15c269b remove useless command in ci 2024-10-27 18:56:34 +01:00
a94e65c279 fix imports 2024-10-27 18:50:27 +01:00
cef15c82c0 [debug] ci build 2024-10-27 18:21:57 +01:00
bae53b8e04 [debug] ci build 2024-10-27 18:17:13 +01:00
ef446085ea update ci 2024-10-27 18:12:59 +01:00
cc17c48889 change path to nextcloud/vue 2024-10-27 18:10:25 +01:00
6ea1a4e6c6 fix build: remove ts, replace hash with chunkhash on webpack conf 2024-10-27 18:06:10 +01:00
894e4408f9 update build ci 2024-10-27 17:36:29 +01:00
df4a94fa6b update build ci 2024-10-27 17:36:01 +01:00
bb9ed8fe9c update node version for build 2024-10-27 17:29:37 +01:00
6bce9981ba Merge pull request 'feature/issue359-nc30' (#362) from feature/issue359-nc30 into develop
Reviewed-on: deblan/side_menu#362
2024-10-27 17:19:44 +01:00
7388fb3fc0 update version 2024-10-27 17:09:27 +01:00
ea759e95e4 show more app opener in mobile view (#359) 2024-10-27 17:06:50 +01:00
b1284fe4dd add more app opener in mobile view (#359) 2024-10-27 17:04:43 +01:00
4e62b2e7dc show apps on top menu 2024-10-27 16:43:23 +01:00
5cc8569277 [WIP] #359: update dependencies 2024-10-06 16:55:57 +02:00
0ecd828a5d #359: fix menu category appearance 2024-10-06 16:11:46 +02:00
0df8b97686
update PHP requirement
update app version
2024-07-10 19:51:51 +02:00
83cc15b1bb
update changelog 2024-07-10 19:51:00 +02:00
f2101f452d Merge pull request 'fix #354 and fix extra margin between the logo and the opener' (#355) from bugfix/issue354-opener into develop
Reviewed-on: deblan/side_menu#355
2024-07-10 19:50:18 +02:00
9ed36b6944
fix extra margin between the logo and the opener 2024-07-10 19:47:09 +02:00
827bbd7c70
fix #354: remove the opener when the menu is always displayed 2024-07-10 19:39:52 +02:00
73b21039b9
rewrite of the script to import config
allow mysql and sqlite
2024-07-10 19:29:51 +02:00
b6275c8070 fix link to the documentation in forgejo issue template
Signed-off-by: deblan <contact@deblan.fr>
2024-07-09 16:43:04 +02:00
3b992dc06f
release v3.13.0 2024-07-05 12:57:15 +02:00
ad9bd7c3f3 Merge pull request 'fix #348: remove .app-navigation--close translationX for always-displayed menu' (#352) from bugfix/issue348-buttonvue into develop
Reviewed-on: deblan/side_menu#352
2024-07-05 12:51:29 +02:00
df525f9858
fix #348: remove .app-navigation--close translationX for always-displayed menu 2024-07-05 12:40:38 +02:00
bacd1af9a6 Merge pull request 'fix #349 + refactoring' (#351) from feature/issue349-tables-apps into develop
Reviewed-on: deblan/side_menu#351
2024-06-27 20:33:09 +02:00
7c5654f3bc
add constructor property promotion
add return type of methods
2024-06-27 20:27:31 +02:00
05c35b9a63
retrieve tables apps 2024-06-27 20:08:56 +02:00
38f400b24c
fix app version number 2024-04-08 15:30:20 +02:00
012c660fed
ci: rollback tag check 2024-04-08 14:33:22 +02:00
9c5be63f16
ci: fix signature step (volumes) 2024-04-08 14:14:16 +02:00
71f5dfef9a
add compatibility with NC29 2024-04-08 11:41:26 +02:00
afa48c6239
ci: update deblan/php version 2024-02-21 16:14:05 +01:00
a94a94b564
update forgejo templates 2024-02-21 15:45:08 +01:00
3e7dcac307
add forgejo templates 2024-02-21 12:35:47 +01:00
54b64cc54e Merge pull request 'refactoring ci steps' (#337) from feature/ci-refactoring into develop
Reviewed-on: deblan/side_menu#337
2024-02-21 12:22:18 +01:00
45d4e5f24c refactorint ci steps
use gitnet.fr/deblan/woodpecker-cache to generate cache
2024-02-21 12:10:02 +01:00
56904c5aa0
release v3.11.8 2024-02-20 21:22:05 +01:00
32cd25b736
update changelog 2024-02-20 21:18:18 +01:00
a9a3ba36c1 Merge pull request 'move the logo inside #nextcloud element (fix #278 #239) [NC26]' (#335) from bugfix/issue278-239 into develop
Reviewed-on: deblan/side_menu#335
2024-02-20 21:17:39 +01:00
2cecd0d6f4
move the logo inside #nextcloud element (fix #278 #239) [NC26] 2024-02-20 21:08:46 +01:00
28085d9aa7
remove line breaks 2024-02-20 21:08:12 +01:00
9d7c98ddf0
fix gl translations 2024-02-14 18:59:25 +01:00
c34bd19159
release v3.11.è 2024-02-14 18:57:33 +01:00
d9d4306ed3
update changelog 2024-02-14 13:31:10 +01:00
b027f1e558 Merge pull request 'upgrade css-loader' (#330) from feature/dependencies-upgrade into develop
Reviewed-on: deblan/side_menu#330
2024-02-14 13:30:30 +01:00
cb44d1eb2a
update changelog 2024-02-14 13:29:36 +01:00
4c76cea7c6
update translations 2024-02-14 13:25:31 +01:00
16b166b1cf Merge branch 'develop' into translations 2024-02-14 13:21:58 +01:00
db9c99b8f2
upgrade css-loader 2024-02-14 13:18:59 +01:00
01c782d844 Merge pull request 'fix #301 #301 #311' (#329) from bugfix/issue326-menudark2 into develop
Reviewed-on: deblan/side_menu#329
2024-02-14 12:25:17 +01:00
1271571427
upgrade axios 2024-02-14 12:22:13 +01:00
951dd742d8
add accessibility to open and close buttons (#311)
refactor the way to focus apps when the menu is opened (#301)
2024-02-14 12:12:08 +01:00
b8aa312a14
fully apply Nextcloud AppMenu.vue updated (#326) 2024-02-14 11:26:54 +01:00
774c314e7b
add missing label on the 'save' button in personal settings (fix #318) 2024-02-14 11:16:25 +01:00
10b91991a9
update ci steps names 2024-02-12 23:29:58 +01:00
3de47aa3e3
release v3.11.6 2024-02-12 23:21:30 +01:00
cd64c0dd93
update changelog 2024-02-12 23:21:12 +01:00
13fdd6c0d6 Merge pull request 'add --background-invert-if-bright in top menu (fix #326)' (#327) from bugfix/issue326-menudark into develop
Reviewed-on: deblan/side_menu#327
2024-02-12 23:20:34 +01:00
83a2261838
add --background-invert-if-bright in top menu (fix #326) 2024-02-12 23:19:43 +01:00
c11c4ee197 Merge pull request 'chore: Configure Renovate' (#314) from renovate/configure into develop
Reviewed-on: deblan/side_menu#314
2024-02-06 22:10:22 +01:00
d8aa018f5a
Added translation using Weblate (Galician) 2024-02-06 20:04:43 +00:00
c3c5db8a37 add translations 2024-01-28 23:20:13 +01:00
0aa6767090 update app info 2024-01-28 23:07:01 +01:00
238a1a905e update changelog 2024-01-28 23:05:46 +01:00
4221f81860 Merge pull request 'bugfix/issue311' (#315) from bugfix/issue311 into develop
Reviewed-on: deblan/side_menu#315
2024-01-28 23:05:02 +01:00
8baaa51b8a fix #311: add missing aria-label attribute 2024-01-28 23:02:35 +01:00
fc6ebbaa2e add translation item for "Toggle menu" 2024-01-28 23:02:04 +01:00
Renovate Bot
2c83789813 chore(deps): add renovate.json 2024-01-27 22:59:29 +00:00
0c143bdb88 release v3.11.3 2024-01-27 10:23:23 +01:00
9e1ded6376 [ci] check if the app version is same as the tag 2024-01-27 10:23:10 +01:00
dbc480a1a7 update changelog 2024-01-27 10:15:10 +01:00
cf2e28960b Merge pull request 'add labels on buttons (fix #311)' (#312) from feature/issue311-btnlabel into develop
Reviewed-on: deblan/side_menu#312
2024-01-26 23:16:03 +01:00
4448412843 add labels on buttons (fix #311) 2024-01-26 23:09:55 +01:00
c5e126667d
fix appinfo version 2024-01-16 20:39:44 +01:00
197a6dc413
update changelog 2024-01-16 20:13:14 +01:00
fbe686c526 Merge pull request '[Bug] Menu-Icon in Decks broken - caused of side_menu #302' (#306) from bugfix/issue302 into develop
Reviewed-on: deblan/side_menu#306
2024-01-16 20:11:14 +01:00
b20f5a471a
fix #302: remove "!important" on the rule that moves elements 2024-01-15 21:12:24 +01:00
f76deeb416
add default translations (fix #298) 2023-11-12 20:30:19 +01:00
93a4eccb41 Merge pull request 'Translated using Weblate (Slovak)' (#299) from translations into develop
Reviewed-on: deblan/side_menu#299
2023-11-12 20:29:01 +01:00
f8458ed488
Translated using Weblate (Slovak)
Currently translated at 43.4% (40 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/sk/
2023-11-12 19:27:51 +00:00
c35ecd7444
release v3.11.1 2023-11-12 16:58:24 +01:00
2edb771273
release v3.11.1 2023-11-12 16:58:09 +01:00
48a2224e3d Merge pull request 'translations' (#296) from translations into develop
Reviewed-on: deblan/side_menu#296
2023-11-12 16:56:07 +01:00
a05ef44ede Merge pull request 'add width to .side-menu-categories for side menu with categories display' (#295) from bugfix/issue294 into develop
Reviewed-on: deblan/side_menu#295
2023-11-12 16:54:58 +01:00
f6d6e263b6
add width to .side-menu-categories for side menu with categories display 2023-11-12 16:49:24 +01:00
igorfreire
b2e0898d5b
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/pt_BR/
2023-11-11 20:13:37 +00:00
igorfreire
4af28c303b
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/pt_BR/
2023-11-07 15:15:14 +00:00
Simon Vieille
e199019d3d
update changelogs 2023-11-05 18:46:31 +01:00
Simon Vieille
a18732b043 Merge branch 'develop' into translations 2023-11-05 18:40:51 +01:00
Simon Vieille
9ed3e93bf4
release v3.11.0 2023-11-05 18:37:51 +01:00
320f4cca3c Merge pull request 'To add option to filter left menu (fix #282)' (#291) from feature/issue282 into develop
Reviewed-on: deblan/side_menu#291
2023-11-05 18:34:47 +01:00
Simon Vieille
b1ae62ba2a fix view of side menu headers with search 2023-11-05 18:30:24 +01:00
Simon Vieille
5d91b44c20
add translations 2023-11-05 16:04:44 +01:00
Simon Vieille
3ec823c856
add search component 2023-11-05 16:04:33 +01:00
c9bae926f1 Merge pull request 'Parameter option not located at the same position menu button is (fix #283)' (#290) from bugfix/issue283 into develop
Reviewed-on: deblan/side_menu#290
2023-11-05 14:59:49 +01:00
Simon Vieille
7b1d31d1db
fix ci 2023-11-04 13:39:32 +01:00
Simon Vieille
c5cac45f7e
fix ci 2023-11-04 13:38:28 +01:00
Simon Vieille
9a35734657
remove label of the link to personal settings (fix #283) 2023-11-04 13:33:34 +01:00
3144f56b80
Added translation using Weblate (Slovak) 2023-10-16 08:16:34 +00:00
6248e8ccef Merge pull request 'fix ci syntax' (#280) from bugfix/ci into develop
Reviewed-on: deblan/side_menu#280
2023-09-29 16:48:06 +02:00
Simon Vieille
648bdeae04
fix ci syntax 2023-09-29 16:46:56 +02:00
mschmidm
d6a8dee1b2
Translated using Weblate (German)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/de/
2023-09-19 12:53:08 +00:00
Simon Vieille
1e6d241e5c
release v3.10.3 2023-07-24 21:32:13 +02:00
eeac15d578 Merge pull request 'fix #274' (#275) from feature/issue274 into develop
Reviewed-on: deblan/side_menu#275
2023-07-24 21:30:58 +02:00
Simon Vieille
89986cd604
update changelog 2023-07-24 21:12:43 +02:00
Simon Vieille
3c1bd99319 update @nexcloud/* packages
change the way to load nextcloud components (NcActionLink/NcActions)

fix #274
2023-07-24 21:10:30 +02:00
Simon Vieille
33bb8646f4
release v3.10.2 2023-07-15 15:22:09 +02:00
Simon Vieille
e571be5fcf
release v3.10.1 2023-07-15 14:58:43 +02:00
432e0f175f Merge pull request 'use php7 syntax' (#270) from feature/issue269 into develop
Reviewed-on: deblan/side_menu#270
2023-07-15 14:57:47 +02:00
Simon Vieille
022e8af6a2
update changelog 2023-07-15 14:53:04 +02:00
Simon Vieille
b8a5a82fd2
fix #269: use php7 syntax 2023-07-15 14:50:15 +02:00
Simon Vieille
8cd54eea19
release v3.10.0 2023-07-13 22:26:34 +02:00
Simon Vieille
072707772d
update changelog 2023-07-11 17:48:45 +02:00
0b3d2d9781 Merge pull request 'Remove deprecated method' (#267) from vitormattos/side_menu:feature/remove-deprecated-method into develop
Reviewed-on: deblan/side_menu#267
2023-07-11 17:46:38 +02:00
04af12691c
Remove deprecated method
Fix the follow error:

```
Message: Call to undefined method OC_App::getNavigation() in file '/var/www/html/apps-extra/side_menu/lib/Service/AppRepository.php' line 54
```

Reference: 167763d312

Signed-off-by: Vitor Mattos <vitor@php.rio>
2023-07-10 15:15:41 -03:00
Simon Vieille
0ea5a579a6
release v3.9.1 2023-06-29 12:54:49 +02:00
b9c3543c61 Merge pull request 'fix top position of always opened menu' (#264) from fix/issue262 into develop
Reviewed-on: deblan/side_menu#264
2023-06-29 12:53:02 +02:00
Simon Vieille
49979c858b
fix top position of always opened menu
fix dashboard menu when using always opened menu
2023-06-23 15:37:03 +02:00
Simon Vieille
def1ec4aa7
release v3.8.0 2023-06-23 12:28:02 +02:00
Simon Vieille
40441a61ae
update changelog 2023-06-23 12:27:34 +02:00
ea3820904c Merge pull request 'fix app redirect' (#261) from fix/issue257 into develop
Reviewed-on: deblan/side_menu#261
2023-06-17 15:21:54 +02:00
dad7d8a964 Merge pull request 'add compatibility with nextcloud 27' (#260) from feature/issue256 into develop
Reviewed-on: deblan/side_menu#260
2023-06-17 15:17:24 +02:00
Simon Vieille
32f1869828
add compatibility with nextcloud 27 2023-06-17 13:04:25 +02:00
Simon Vieille
dd9fbcb654
fix default menu redirect (#257) 2023-06-17 12:59:45 +02:00
Simon Vieille
a96e6e3c70
fix default menu redirect (#257) 2023-06-17 12:53:10 +02:00
Simon Vieille
a5effe1fd3
release v3.8.0 2023-05-25 13:39:45 +02:00
Simon Vieille
13b6ae23c2
ci: remove check-code-quality 2023-05-25 13:39:21 +02:00
79ddb5fa47 Merge pull request 'add option to show hovered label only on top menu' (#254) from feature/issue253 into develop
Reviewed-on: deblan/side_menu#254
2023-05-23 20:13:11 +02:00
Simon Vieille
1454dbba4d
update changelog 2023-05-23 19:56:35 +02:00
Simon Vieille
ee8ca7134c
refactoring choices 2023-05-23 14:17:45 +02:00
Simon Vieille
93fef37033
add option to show hovered label only on top menu 2023-05-23 14:09:55 +02:00
Simon Vieille
135f27cc32
release v3.7.4 2023-04-16 18:05:25 +02:00
c5e1a8d37b Merge pull request 'fix Integrity failed' (#248) from fix/issue247 into develop
Reviewed-on: deblan/side_menu#248
2023-04-16 18:03:04 +02:00
Simon Vieille
27f8888a90
update signature generation 2023-04-16 14:24:17 +02:00
Simon Vieille
6583883e2d
update signature generation 2023-04-16 14:17:09 +02:00
Simon Vieille
0a556670b1
update signature generation 2023-04-16 14:16:18 +02:00
Simon Vieille
b9264d7e05
release v3.7.3 2023-04-14 21:12:35 +02:00
88c14b6aec Merge pull request 'use app href for redirection' (#245) from fix/issue244 into develop
Reviewed-on: deblan/side_menu#245
2023-04-14 21:11:29 +02:00
2be53211b8 Merge pull request 'add signature generation' (#243) from feature/issue240 into develop
Reviewed-on: deblan/side_menu#243
2023-04-14 21:00:37 +02:00
Simon Vieille
dca727c120
use app href for redirection (fix #244) 2023-04-14 20:58:56 +02:00
Simon Vieille
361badff6b
update changelog 2023-03-31 17:06:30 +02:00
Simon Vieille
917288eb54
add signature generation
rename steps
2023-03-31 14:07:19 +02:00
2ff4fee927 Merge pull request 'changelog' (#237) from changelog into develop
Reviewed-on: deblan/side_menu#237
2023-03-27 18:36:34 +02:00
Simon Vieille
b65c0c650a
update app version 2023-03-27 18:35:59 +02:00
22335700ce Merge pull request 'update pipeline conditions allowing fix/*' (#234) from feature/ci into develop
Reviewed-on: deblan/side_menu#234
2023-03-27 18:35:12 +02:00
Simon Vieille
7ab2816bd9
update changelog 2023-03-27 18:34:50 +02:00
396d3cfbc5 Merge pull request 'load configuration and then retrieve apps in default side menu display' (#235) from feature/issue233 into develop
Reviewed-on: deblan/side_menu#235
2023-03-27 18:34:21 +02:00
Simon Vieille
66e4d2989a
update pipeline conditions allowing fix/* 2023-03-23 22:22:36 +01:00
Simon Vieille
9ccd11b6dc
load configuration and then retrieve apps in default side menu display
fix #233
2023-03-23 22:17:05 +01:00
a7ec95da7c Merge pull request 'Fix build process' (#231) from fix/issue230 into develop
Reviewed-on: deblan/side_menu#231
2023-03-19 09:58:11 +01:00
Simon Vieille
42043c5390
update changelog 2023-03-19 09:52:37 +01:00
Simon Vieille
019f79d45f
update ci conf 2023-03-19 09:45:51 +01:00
Simon Vieille
3a80215657
update ci conf 2023-03-19 09:35:59 +01:00
Simon Vieille
7d0b55243e
add debug version 2023-03-19 09:34:52 +01:00
Simon Vieille
097ccc9dc9
update ci conf 2023-03-19 09:34:23 +01:00
Simon Vieille
7227f59dab
add tag on event restriction for build steps 2023-03-17 17:25:22 +01:00
Simon Vieille
3a51e6ecd5
add debug version 2023-03-17 17:22:59 +01:00
Simon Vieille
112e669723
reduce release task as packaging (ci)
move translation build in specific step (ci)
2023-03-17 17:22:35 +01:00
Simon Vieille
fc5a6a4c64
fix typo in ci configuration 2023-03-17 17:13:31 +01:00
Simon Vieille
02afac5267
add debug version 2023-03-17 17:11:16 +01:00
Simon Vieille
1eb392834c
use specifics images in ci 2023-03-17 17:10:56 +01:00
Codeberg Translate
08b78a2c64
Merge branch 'origin/translations' into Weblate. 2023-03-16 06:11:25 +00:00
8f86cbe188
Added translation using Weblate (Portuguese (Brazil)) 2023-03-16 06:11:24 +00:00
Simon Vieille
5df389dd42
add badge of downloads 2023-03-11 00:29:32 +01:00
Simon Vieille
dbd0de7679 release v3.7.0 2023-03-09 16:46:03 +01:00
07a1d356de Merge pull request 'set the app compatible with nextcloud 26' (#225) from feature/nc26 into develop
Reviewed-on: deblan/side_menu#225
2023-03-09 15:55:35 +01:00
d3359337f9 Merge pull request 'translations' (#226) from translations into develop
Reviewed-on: deblan/side_menu#226
2023-03-09 15:54:50 +01:00
Simon Vieille
4b9378f923
set the app compatible with nextcloud 26 2023-03-09 14:05:30 +01:00
AHOHNMYC
117e33692b
Translated using Weblate (Russian)
Currently translated at 84.7% (78 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2023-03-06 19:08:20 +00:00
Simon Vieille
18e8f42dc3
update issue template 2023-02-20 13:50:53 +01:00
Simon Vieille
01669f83c2
release v3.6.0 2023-02-17 11:46:31 +01:00
Simon Vieille
f1c33cc626
release v3.6.0 2023-02-17 11:45:57 +01:00
87ff20aa19 Merge pull request 'add hidden apps compatible with default menu' (#220) from feature/issue219 into develop
Reviewed-on: deblan/side_menu#220
2023-02-17 11:44:41 +01:00
Simon Vieille
ce969b4061 Merge branch 'develop' into translations 2023-02-17 10:54:42 +01:00
Simon Vieille
ed4ed62fb6
update admin (hidden apps compatible with defaul) 2023-02-17 10:51:39 +01:00
Simon Vieille
5a586c40a1
add hidden apps compatible with default menu (fix #219) 2023-02-17 10:51:11 +01:00
Simon Vieille
b141d7ba7c
update readme 2023-02-12 16:56:22 +01:00
Simon Vieille
0ee10e08e9
update readme 2023-02-12 00:31:47 +01:00
Simon Vieille
1093042a17
update readme 2023-02-12 00:29:18 +01:00
Simon Vieille
9566d32a89
update readme 2023-02-12 00:28:51 +01:00
3861d78975 Merge pull request 'login error (#210)' (#212) from feature/login_error into develop
Reviewed-on: deblan/side_menu#212
2023-01-21 22:26:04 +01:00
Simon Vieille
571706986e
release v3.5.2 2023-01-21 22:18:45 +01:00
Simon Vieille
ed31ed3623
add check if menu exists before adding event listeners 2023-01-21 22:12:17 +01:00
Simon Vieille
59af6a9068
release v3.5.1 2023-01-07 10:23:19 +01:00
Simon Vieille
1965aedc43
release v3.5.1 2023-01-07 10:22:56 +01:00
Simon Vieille
ab46bd7341
docs(changelog): add v3.5.1 2023-01-07 10:21:46 +01:00
3aace8cbdd Merge pull request 'translations' (#209) from translations into develop
Reviewed-on: deblan/side_menu#209
2023-01-07 10:19:00 +01:00
Codeberg Translate
6425546cd1
Merge branch 'origin/translations' into Weblate. 2023-01-01 23:31:48 +00:00
gallegonovato
3664b8979d
Translated using Weblate (Spanish)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/es/
2023-01-01 23:31:47 +00:00
83732994bb
Translated using Weblate (Czech)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/cs/
2023-01-01 23:31:47 +00:00
c65d09e268 Merge pull request 'ci: add osv detector' (#208) from feature/osv into develop
Reviewed-on: deblan/side_menu#208
2023-01-01 14:59:05 +01:00
Simon Vieille
2da2e10017
ci: add osv detector 2023-01-01 14:52:30 +01:00
Simon Vieille
8da90bbc06 Merge branch 'gitea' into develop 2022-12-29 22:53:50 +01:00
Simon Vieille
d4adda19b3
add gitea ISSUE/FEATURE templates 2022-12-29 22:45:36 +01:00
Simon Vieille
ae3db9cf7f
add gitea ISSUE/FEATURE templates 2022-12-29 22:42:03 +01:00
Simon Vieille
05ff6d483b
add gitea ISSUE/FEATURE templates 2022-12-29 22:41:06 +01:00
Simon Vieille
dfc61140ec
add gitea ISSUE/FEATURE templates 2022-12-29 22:30:33 +01:00
Simon Vieille
4918049270
update changelog 2022-12-28 11:14:09 +01:00
4001f162b6 Merge pull request 'Fix custom menu sorting is not applied on Mobile Screens' (#199) from feature/issue189 into develop
Reviewed-on: deblan/side_menu#199
2022-12-27 23:20:21 +01:00
Simon Vieille
7a43ca2c75
add sort on in appList and fix appLabel function 2022-12-27 07:02:07 +01:00
bc6470a440 Merge pull request 'develop' (#197) from develop into master
Reviewed-on: deblan/side_menu#197
2022-12-26 14:00:48 +01:00
65e21ab4c5 Merge pull request 'next release' (#191) from develop into master
Reviewed-on: deblan/side_menu#191
2022-12-26 13:59:24 +01:00
Simon Vieille
15ac74d8e9 update version 2022-12-26 13:53:40 +01:00
Simon Vieille
40229b93f6 update changelog 2022-12-26 13:53:40 +01:00
9b24f714ce Merge pull request 'translations' (#196) from translations into develop
Reviewed-on: deblan/side_menu#196
2022-12-26 13:53:06 +01:00
f0f6c4206a
Translated using Weblate (Spanish)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/es/
2022-12-26 12:44:07 +00:00
3b6478edbb
Translated using Weblate (Russian)
Currently translated at 80.4% (74 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-12-26 12:44:06 +00:00
df7c94fe24
Translated using Weblate (Dutch)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/nl/
2022-12-26 12:44:06 +00:00
7b808d87b1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/zh_Hans/
2022-12-26 12:44:06 +00:00
39e3cf9dd3
Translated using Weblate (French)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/fr/
2022-12-26 12:44:06 +00:00
5598af213d
Translated using Weblate (German)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/de/
2022-12-26 12:44:06 +00:00
367bd72bfb
Translated using Weblate (Czech)
Currently translated at 100.0% (92 of 92 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/cs/
2022-12-26 12:44:06 +00:00
Simon Vieille
c46713dc56
add missing translations 2022-12-26 13:40:45 +01:00
Simon Vieille
1594f3583c Merge branch 'develop' into translations 2022-12-26 13:33:45 +01:00
Codeberg Translate
ff7f423dcb
Merge branch 'origin/translations' into Weblate. 2022-12-26 12:31:12 +00:00
thejenja
f3e1784577
Translated using Weblate (Russian)
Currently translated at 80.2% (73 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-12-26 12:31:12 +00:00
Simon Vieille
9264b0f10d add translation configuration 2022-12-26 13:07:59 +01:00
Simon Vieille
08b418d55d add 'top-menu-mouse-over-hidden-label' as window property and use it in the AppMenu component 2022-12-26 13:07:59 +01:00
Simon Vieille
47af9088d4 add the 'top-menu-mouse-over-hidden-label' 2022-12-26 13:07:59 +01:00
Simon Vieille
690c437476 add hiddenLabels variables to show or hide of top menu items on mouse over 2022-12-26 13:07:59 +01:00
be66c27452 Merge pull request '[FEATURE] Option to disable the display of Icon-Names in the top left Menu-Bar (#194)' (#195) from feature/issue194 into develop
Reviewed-on: deblan/side_menu#195
2022-12-26 13:05:25 +01:00
Simon Vieille
785edbf5c5
add translation configuration 2022-12-26 12:59:19 +01:00
Simon Vieille
4258327a70
add 'top-menu-mouse-over-hidden-label' as window property and use it in the AppMenu component 2022-12-26 12:57:07 +01:00
Simon Vieille
37e00c51c3
add the 'top-menu-mouse-over-hidden-label' 2022-12-26 12:56:32 +01:00
Simon Vieille
f7d463e734
add hiddenLabels variables to show or hide of top menu items on mouse over 2022-12-26 00:56:27 +01:00
cae4d8ab66 Merge pull request 'translations' (#190) from translations into develop
Reviewed-on: deblan/side_menu#190
2022-12-06 13:27:35 +01:00
66a1c1eecb Merge branch 'develop' into translations 2022-12-06 06:49:23 +01:00
cc799060af
update chanlog 2022-12-06 06:49:16 +01:00
a3802ef298
update ci config 2022-12-06 06:48:27 +01:00
901668236f Merge branch 'develop' into translations 2022-12-05 23:44:49 +01:00
gallegonovato
fef81eab0f
Translated using Weblate (Spanish)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/es/
2022-12-05 22:39:36 +00:00
21e0f4cd89 Merge pull request 'feature/dependency_check' (#188) from feature/dependency_check into develop
Reviewed-on: deblan/side_menu#188
2022-12-05 10:18:23 +01:00
5411267960
CI: use nodejs for dependency check 2022-12-04 22:52:02 +01:00
b50cd5c4d0
CI: update dependency check 2022-12-04 22:42:50 +01:00
709ace5d4f
CI: add dependency check 2022-12-04 22:25:04 +01:00
b7595a371d Merge pull request 'improve code quality' (#187) from feature/code_quality into develop
Reviewed-on: deblan/side_menu#187
2022-12-04 20:25:56 +01:00
a7ab4573c5
update makefile run-code-quality-analysis command: use current branch and envvars 2022-12-04 20:23:48 +01:00
62c4333278 Merge branch 'develop' into feature/code_quality 2022-12-04 19:49:20 +01:00
8a8d8f2910 Merge pull request 'sonar-scanner configuration' (#186) from feature/ci into develop
Reviewed-on: deblan/side_menu#186
2022-12-04 19:47:44 +01:00
7c72198736
sonar-scanner configuration 2022-12-04 19:06:19 +01:00
d1b2a1ee3c
sonar-scanner configuration 2022-12-04 18:59:21 +01:00
355c73185b
Added translation using Weblate (Spanish) 2022-12-04 14:45:47 +00:00
e1797e1e03
split long lines 2022-12-04 13:01:56 +01:00
3f04546a6e
split long lines 2022-12-04 00:57:17 +01:00
0bd1a5baaf
split long lines 2022-12-04 00:56:05 +01:00
24c87c0104
add missing img alt 2022-12-04 00:47:30 +01:00
4d05079c70
split long lines 2022-12-04 00:45:29 +01:00
0382e1c61d
replace dupplicated string with variables 2022-12-04 00:19:54 +01:00
0f34275d04
remove duplicated selector ".side-menu-always-displayed body" 2022-12-04 00:14:38 +01:00
1c2f04f63f
replace border-size with border-width 2022-12-04 00:13:28 +01:00
81bcad348e
remove this unused import of 'trim' 2022-12-04 00:09:11 +01:00
053615c2c4 remove this unused "$topMenuAppsOrder" local variable 2022-12-04 00:08:20 +01:00
113e4db9b6 Merge pull request 'add code_quality in ci' (#185) from feature/ci into develop
Reviewed-on: deblan/side_menu#185
2022-12-04 00:06:59 +01:00
89cf28f9a8
add code_quality in ci 2022-12-03 23:47:55 +01:00
Codeberg Translate
127fb45ece
Merge branch 'origin/translations' into Weblate. 2022-12-03 21:18:59 +00:00
a1d3df6018
Translated using Weblate (German)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/de/
2022-12-03 21:18:58 +00:00
9c3d9ccbc4 Merge pull request 'release v3.4.1' (#184) from develop into master
Reviewed-on: deblan/side_menu#184
2022-12-02 12:33:30 +01:00
45a68cbfc8
release v3.4.1 2022-12-02 12:25:31 +01:00
f42cb368db Merge pull request 'hide custom categories list when empty' (#183) from feature/issue182 into develop
Reviewed-on: deblan/side_menu#183
2022-12-02 12:23:18 +01:00
6d940cc7c4 Merge pull request 'translations' (#181) from translations into develop
Reviewed-on: deblan/side_menu#181
2022-12-02 12:19:04 +01:00
2d30a1f1ea
hide custom categories list when empty 2022-12-02 12:16:49 +01:00
599e70bf8a Merge branch 'develop' into translations 2022-11-21 08:43:19 +01:00
d012e464a1
add translations 2022-11-21 08:43:08 +01:00
Codeberg Translate
ae2b52cb36
Merge branch 'origin/translations' into Weblate. 2022-11-20 22:26:39 +00:00
zonorti
5444bbf987
Translated using Weblate (Russian)
Currently translated at 67.0% (61 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-11-20 22:26:38 +00:00
jorisvandijk
ed7d4f4750
Translated using Weblate (Dutch)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/nl/
2022-11-20 22:26:38 +00:00
fe5ffcb6bf Merge pull request 'release v3.4.0' (#179) from develop into master
Reviewed-on: deblan/side_menu#179
2022-11-20 15:17:23 +01:00
f61f05c1c9
release v3.4.0 2022-11-20 15:10:50 +01:00
c63ac1556e
update ci 2022-11-20 15:04:54 +01:00
c3cd78971b
update changelog 2022-11-20 14:47:09 +01:00
0f24133b99
fix merge 2022-11-20 14:43:44 +01:00
887aa7f7f5 Added translation using Weblate (Dutch) 2022-11-20 14:41:02 +01:00
Codeberg Translate
ac30ac25e9
Merge branch 'origin/develop' into Weblate. 2022-11-20 13:33:32 +00:00
Codeberg Translate
445b9d6986
Merge branch 'origin/develop' into Weblate. 2022-11-20 13:33:31 +00:00
nier
98d72a1e95
Translated using Weblate (Russian)
Currently translated at 64.8% (59 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-11-20 13:25:04 +00:00
5ad6af0b0d
Translated using Weblate (Russian)
Currently translated at 64.8% (59 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-11-20 13:25:04 +00:00
0f93fbe7ea
Translated using Weblate (Dutch)
Currently translated at 0.0% (0 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/nl/
2022-11-20 13:25:04 +00:00
Timur
8db686b3a2
Translated using Weblate (Russian)
Currently translated at 60.4% (55 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/ru/
2022-11-20 13:25:04 +00:00
e208e19b1c
Added translation using Weblate (Russian) 2022-11-20 13:25:04 +00:00
d78bb6d93b
update changelog 2022-11-20 14:13:54 +01:00
229d25cb44 Merge pull request 'feature/issue177' (#178) from feature/issue177 into develop
Reviewed-on: deblan/side_menu#178
2022-11-20 14:12:11 +01:00
3ccf754e0d
add possibility to define side_menu as default app and redirect to the first top menu app (#177) 2022-11-20 14:00:33 +01:00
a4060c8db7
add possibility to define side_menu as default app and redirect to the first top menu app (#177) 2022-11-20 14:00:26 +01:00
b474eeb29a update changelog 2022-11-11 21:20:20 +01:00
e640343df9 Merge pull request 'translations' (#176) from translations into develop
Reviewed-on: deblan/side_menu#176
2022-11-11 19:41:16 +01:00
84d1dff0ff
Translated using Weblate (Czech)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/cs/
2022-11-10 21:05:00 +00:00
13b19c407a
Added translation using Weblate (Dutch) 2022-11-10 21:05:00 +00:00
fa15824dad Merge pull request 'release v3.3.2' (#175) from develop into master
Reviewed-on: deblan/side_menu#175
2022-11-10 13:08:27 +01:00
474387ff43
release v3.3.2 2022-11-10 13:07:35 +01:00
45ae1b3579 Merge pull request 'reduce the height of categories list (fix #173)' (#174) from feature/issue173 into develop
Reviewed-on: deblan/side_menu#174
2022-11-09 13:04:12 +01:00
c10d0e8442
reduce the height of categories list (fix #173) 2022-11-09 12:51:56 +01:00
f5cabe9708 Merge pull request 'release v3.3.1' (#171) from develop into master
Reviewed-on: deblan/side_menu#171
2022-11-06 17:01:43 +01:00
00e8a56e15
release v3.3.1 2022-11-06 16:55:23 +01:00
0ea7fe04bf Merge branch 'feature/issue162' into develop 2022-11-06 16:54:25 +01:00
0ac55b7ba4
fix #162: top and side app is broken 2022-11-05 15:40:52 +01:00
1794997ca0 Merge pull request 'Translated using Weblate (French)' (#169) from translations into develop
Reviewed-on: deblan/side_menu#169
2022-11-05 12:25:05 +01:00
3c7cd2812f
Translated using Weblate (French)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/fr/
2022-11-05 11:04:27 +00:00
2c75ebb3f7 Merge pull request 'translations' (#168) from translations into develop
Reviewed-on: deblan/side_menu#168
2022-11-05 12:02:29 +01:00
Anonymous
8bf7c7a8ce
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/zh_Hans/
2022-11-05 10:49:52 +00:00
Anonymous
5ed9e1e449
Translated using Weblate (French)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/fr/
2022-11-05 10:49:52 +00:00
Anonymous
2b4feb908e
Translated using Weblate (German)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/de/
2022-11-05 10:49:51 +00:00
Anonymous
572f05ed52
Translated using Weblate (Czech)
Currently translated at 100.0% (91 of 91 strings)

Translation: Custom menu/Application
Translate-URL: https://translate.codeberg.org/projects/custom-menu/application/cs/
2022-11-05 10:49:51 +00:00
dbf1c4316a Merge pull request 'release v3.3.0' (#167) from develop into master
Reviewed-on: deblan/side_menu#167
2022-11-04 22:43:18 +01:00
e9fb46b7b7
release v3.3.0 2022-11-04 22:36:54 +01:00
1357571104
update screenshots 2022-11-04 22:26:20 +01:00
0cec77154a Merge pull request 'fix #164: open apps in new tab does not work' (#166) from feature/issue164 into develop
Reviewed-on: deblan/side_menu#166
2022-11-04 22:17:48 +01:00
6d7543f381
fix #164: open apps in new tab does not work 2022-11-04 22:14:28 +01:00
1d06c1b212
fix issue with top and side apps (#162 #159) 2022-11-04 21:43:18 +01:00
712777470c Merge pull request 'Sort menu on user level' (#165) from feature/issue160 into develop
Reviewed-on: deblan/side_menu#165
2022-11-04 21:12:16 +01:00
52d88771d9
add apps sorter is user side (fix #160) 2022-11-04 21:07:44 +01:00
c8ac0b5922 Merge pull request 'feature/doc' (#163) from feature/doc into develop
Reviewed-on: deblan/side_menu#163
2022-11-04 19:32:40 +01:00
78162f9a1e
add translations 2022-11-04 18:53:47 +01:00
81f5dfcf2d
add compatibility badges for each options 2022-11-04 18:50:45 +01:00
7f6f606d75
fix changelog 2022-10-29 11:41:48 +02:00
39fd049642 Merge pull request 'release v3.2.1' (#157) from develop into master
Reviewed-on: deblan/side_menu#157
2022-10-29 10:43:56 +02:00
227f26679a
release v3.2.1 2022-10-29 10:43:08 +02:00
6198a2abc2 Merge pull request 'develop' (#156) from develop into master
Reviewed-on: deblan/side_menu#156
2022-10-28 22:44:23 +02:00
6e8d3ce245 Merge pull request 'fix #151: opener position' (#155) from feature/issue151 into develop
Reviewed-on: deblan/side_menu#155
2022-10-28 22:35:11 +02:00
c83dfef021 Merge pull request 'fix #150: Active app is not visible has active in menu (except in default menu).' (#153) from feature/issue150 into develop
Reviewed-on: deblan/side_menu#153
2022-10-28 22:35:04 +02:00
8b7163b3d5
fix #151: opener position 2022-10-28 22:23:17 +02:00
bec53f0486
fix #150: Active app is not visible has active in menu (except in default menu). 2022-10-28 22:16:49 +02:00
0acb576995 Merge pull request 'release v3.2.0' (#149) from develop into master
Reviewed-on: deblan/side_menu#149
2022-10-27 22:07:47 +02:00
131c4b643a
release v3.2.0 2022-10-27 22:01:35 +02:00
c0b187e55e update SideMenu with apps order and changelog 2022-10-27 21:58:21 +02:00
f09eda85a6 Merge branch 'feature/issue147' into develop 2022-10-27 21:52:34 +02:00
1971c6f500
add ordered apps for all displays (#147) 2022-10-27 21:52:29 +02:00
07c7221ca2
rename 'top-menu-apps-order' with 'apps-order' 2022-10-27 21:51:58 +02:00
d42593d203
removed unused translations 2022-10-27 21:50:13 +02:00
90fb46fb0e
add effect of reset button 2022-10-27 21:49:55 +02:00
4b3bb0c5d8
update changelog 2022-10-27 20:32:13 +02:00
6533acdd60
use custom app names using 'app.navigation.name' (fix #148) 2022-10-27 20:30:14 +02:00
2cdb5d93a2 Merge pull request 'develop' (#146) from develop into master
Reviewed-on: deblan/side_menu#146
2022-10-22 17:22:13 +02:00
f20dc7282e
add selfhosted notice 2022-10-21 23:59:27 +02:00
4dc07ed8fc
add selfhosted notice 2022-10-21 23:58:36 +02:00
e48d8d1114 Merge pull request 'update readme' (#145) from develop into master
Reviewed-on: deblan/side_menu#145
2022-10-21 21:29:25 +02:00
36c8cb59bb
update readme 2022-10-21 21:29:10 +02:00
bc6ac9c7d3 Merge pull request 'develop' (#144) from develop into master
Reviewed-on: deblan/side_menu#144
2022-10-21 21:27:27 +02:00
fbe1b4f0bc
update readme 2022-10-21 21:17:02 +02:00
8d9b12683a
add translation badge 2022-10-21 21:16:24 +02:00
c7bcc4a844 Merge pull request 'add template for translations' (#143) from develop into master
Reviewed-on: deblan/side_menu#143
2022-10-21 20:25:49 +02:00
07ad842911
add template for translations 2022-10-21 20:25:35 +02:00
874332abf1 Merge pull request 'develop' (#142) from develop into master
Reviewed-on: deblan/side_menu#142
2022-10-21 20:24:04 +02:00
5208b5d574
add template for translations 2022-10-21 20:20:23 +02:00
fcd082cc0a
update translations using translate.codeberg.org 2022-10-21 20:12:04 +02:00
7472e4050a Merge pull request 'release v3.1.0' (#141) from develop into master
Reviewed-on: deblan/side_menu#141
2022-10-21 19:43:38 +02:00
be0731f2d5
release v3.1.0 2022-10-21 19:36:55 +02:00
6a6792401e
add base for translations 2022-10-21 19:34:48 +02:00
a0c0cc0aa5
add custom apps sorting (fix SideMenu and admin ui) 2022-10-21 19:33:43 +02:00
1eb5e18d5c
update changelog 2022-10-21 18:01:49 +02:00
84c620637e
add custom apps sorting 2022-10-21 17:59:41 +02:00
7d82c78581 Merge branch 'feature/ui' into develop 2022-10-21 17:08:02 +02:00
83c2ccb5b4
update changelog 2022-10-21 17:08:00 +02:00
62e4a7df5b
fix admin modale 2022-10-21 17:07:43 +02:00
16b8326fae
update changelog 2022-10-21 16:50:39 +02:00
57f3a0d252 Merge branch 'feature/ui' into develop 2022-10-21 16:49:02 +02:00
5fac655856
remove unused translation keys 2022-10-21 16:48:59 +02:00
8a92b2d803
fix admin list 2022-10-21 16:48:36 +02:00
1ec9a3f2ff Merge pull request 'release v3.0.1' (#139) from develop into master
Reviewed-on: deblan/side_menu#139
2022-10-18 22:33:36 +02:00
345f165aef
release v3.0.1 2022-10-18 22:28:28 +02:00
f6d9a9f71c
refactoring of SideMenuWithCategories.vue 2022-10-18 22:27:50 +02:00
2a67038ff4
refactoring of SideMenuBig.vue 2022-10-18 22:22:26 +02:00
a5ca86f573
add screenshots 2022-10-18 22:07:55 +02:00
501a1b23e1
update changelog 2022-10-18 22:07:46 +02:00
c01db7c9e1
remove the gap between the window's top and menu categories (large menu) 2022-10-18 19:35:35 +02:00
c0e24e39b6 Merge pull request 'release v3.0.0' (#138) from develop into master
Reviewed-on: deblan/side_menu#138
2022-10-17 18:28:25 +02:00
e31da3af6f
release v3.0.0 2022-10-17 18:27:46 +02:00
8e5193417d Merge pull request 'comptability with nc25' (#137) from develop into master
Reviewed-on: deblan/side_menu#137
2022-10-17 18:11:24 +02:00
2189d5c2c8
release v3.0.0 2022-10-17 18:06:18 +02:00
1adbfb33c9
fix CI 2022-10-17 17:55:50 +02:00
842776c2fd
[ci] debug 2022-10-16 23:13:52 +02:00
719075db6c
[ci] debug 2022-10-16 23:11:41 +02:00
af95ab6265
[ci] debug 2022-10-16 23:09:29 +02:00
78e71dce97
update makefile 2022-10-16 23:00:26 +02:00
d177bdbaeb
update makefile 2022-10-16 22:54:28 +02:00
f67a489130
update js conf 2022-10-16 22:18:56 +02:00
23fc3b9e5b
update js conf 2022-10-16 22:14:10 +02:00
e889d1e3d8
update js conf 2022-10-16 22:09:14 +02:00
39faeb70dc
update js conf 2022-10-16 22:04:51 +02:00
433c480637
fix js dependecies 2022-10-16 20:10:55 +02:00
0321f6e7f4
update ci config 2022-10-16 20:04:11 +02:00
ec8cf06082
update gitignore and add yarn lock file 2022-10-16 20:00:06 +02:00
f30142abe6
update indentation 2022-10-16 19:59:18 +02:00
7c86d53da7
fix issue with loader and refactoring 2022-10-16 19:57:32 +02:00
f0988b93bd
fix fixed menu view 2022-10-16 19:19:03 +02:00
a647f05fa4
update indentation 2022-10-16 19:01:46 +02:00
8aa07efaf9
update default view 2022-10-16 19:00:16 +02:00
9cdb008a72
update indentation (4 spaces -> 2 spaces) 2022-10-16 18:33:50 +02:00
585aec5ee2
update indentation (4 spaces -> 2 spaces) 2022-10-16 18:33:06 +02:00
1c1fe946cf
update rules of makefile, webpack, stylelintrc 2022-10-16 18:23:20 +02:00
1a550f066f
replace the top menu component with a custom one 2022-10-16 18:21:23 +02:00
01de6d998f
lint admin.css 2022-10-14 19:35:49 +02:00
cf87747924
fix position of reset buttons 2022-10-14 19:35:01 +02:00
e5b3499077
add vuejs key on loops and apply a lint 2022-10-11 22:24:59 +02:00
78 changed files with 4112 additions and 2484 deletions

View file

@ -1,16 +0,0 @@
---
name: "New feature"
about: "Use this template if you want to request a feature"
title: "[FEATURE] "
labels:
- enhancement
---
## Feature
### Description
...
### Benefits
...

View file

@ -0,0 +1,34 @@
name: New feature
about: Use this template if you want to request a feature
title: "[FEATURE] "
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature.
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Benefits
description: Describe the benefits of this feature.
validations:
required: true
- type: textarea
id: extra
attributes:
label: More informations
description: If you want to share more things, this is here!
validations:
required: false

View file

@ -1,32 +0,0 @@
---
name: "New issue"
about: "Use this template if you have a bug"
title: "[BUG] "
labels:
- bug
---
## Issue
### Environment
* Custom menu version:
* Nextcloud version:
* PHP version:
* Web server (Nginx, Apache2):
* Web browser and version (Firefox 80, Google Chrome 74, etc):
```
Insert your configuration here. You can export the configuration using the admin page.
```
### Steps to reproduce
...
### Observed Results
...
### Expected Results
...

View file

@ -0,0 +1,69 @@
name: New issue
about: Use this template if you have a bug
title: "[Bug] "
labels:
- bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: environment
attributes:
label: Environment
value: |
* Custom menu version:
* Nextcloud version:
* PHP version:
* Web server (Nginx, Apache2):
* Web browser and version (Firefox 80, Google Chrome 74, etc):
validations:
required: true
- type: textarea
id: configuration
attributes:
label: Configuration
description: Export the configuration using the admin page and copy/paste here ([documentation](https://deblan.gitnet.page/side_menu_doc/docs/FAQ/export-config/)).
value: |
```
{
...
}
```
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: How reproduce the bug?
validations:
required: false
- type: textarea
id: resuts
attributes:
label: Observed Results
description: What happened?
validations:
required: false
- type: textarea
id: expected
attributes:
label: Expected Results
description: What should happen?
validations:
required: false
- type: textarea
id: extra
attributes:
label: More informations
description: If you want to share more things, this is here!
validations:
required: false

View file

@ -0,0 +1,30 @@
name: New question
about: Use this template when you don't know how to do something
title: "[Question] "
labels:
- question
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill information.
- type: textarea
id: environment
attributes:
label: Environment
value: |
* Custom menu version:
* Nextcloud version:
* PHP version:
* Web server (Nginx, Apache2):
* Web browser and version (Firefox 80, Google Chrome 74, etc):
validations:
required: true
- type: textarea
id: question
attributes:
label: Question
validations:
required: true

View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://deblan.gitnet.page/side_menu_doc/
about: Official documentation web site
- name: Ask a question in our Matrix room
about: If you prefer a chat-like conversation or in need for quick help, this might be an alternative to opening an issue.
url: https://matrix.to/#/#custommenu:neutralnetwork.org

1
.gitignore vendored
View file

@ -4,3 +4,4 @@
/releases
/package-lock.json
!/l10n/.gitkeep
/yarn*.log

View file

@ -1,5 +1,5 @@
{
"rules": {
"indentation": 4
"indentation": 2
}
}

View file

@ -1,42 +0,0 @@
pipeline:
dependencies:
image: deblan/devenv
commands:
- npm install
when:
event: [tag, push, pull_request]
branch: [master, develop, feature/*]
build:
image: deblan/devenv
commands:
- make npm-build
when:
event: [push, pull_request]
package:
image: deblan/devenv
volumes:
- /var/www/html/artifacts:/var/www/html/artifacts
secrets: [app_certificate]
commands:
- mkdir -p "$HOME/.nextcloud/certificates"
- echo "$APP_CERTIFICATE" > "$HOME/.nextcloud/certificates/side_menu.key"
- export VERSION=$(grep "<version>" appinfo/info.xml | grep -o "[0-9]*\.[0-9]*\.[0-9]*" --color=never)
- export RELEASE_DIRECTORY="/var/www/html/artifacts/deblan/side_menu"
- make release
when:
event: [tag]
release:
image: plugins/gitea-release
volumes:
- /var/www/html/artifacts:/var/www/html/artifacts
settings:
api_key:
from_secret: gitnet_api_key
base_url: https://gitnet.fr
note: ${CI_COMMIT_MESSAGE}
files: /var/www/html/artifacts/deblan/side_menu/${CI_COMMIT_TAG/v//}/*
when:
event: [tag]

22
.woodpecker/.build.yml Normal file
View file

@ -0,0 +1,22 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
when:
event: [tag, push, pull_request, manual]
branch: [master, develop, feature/*, fix/*, bugfix/*, translations]
steps:
"Build JS":
image: node:20
commands:
- make build
"Build translations":
image: deblan/php:8.3
commands:
- php bin/generate_l10n.php
"Build cache":
image: gitnet.fr/deblan/woodpecker-cache
volumes: *volumes

66
.woodpecker/.publish.yml Normal file
View file

@ -0,0 +1,66 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
- /var/www/html/artifacts:/var/www/html/artifacts
depends_on:
- build
when:
event: [tag]
steps:
"Verify tag and app version":
image: alpine
commands:
- TAG=${CI_COMMIT_TAG/v//}
- grep "<version>$TAG</version>" appinfo/info.xml
"Create signature":
image: nextcloud:25
volumes: *volumes
environment:
APP_CERTIFICATE:
from_secret: app_certificate
APP_PUBLIC_CERTIFICATE:
from_secret: app_public_certificate
SQLITE_DATABASE: /var/www/data/data.db
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: admin
commands:
- cd "/builds/$CI_COMMIT_SHA"
- echo "$APP_CERTIFICATE" > "/tmp/side_menu.key"
- echo "$APP_PUBLIC_CERTIFICATE" > "/tmp/side_menu.crt"
- mkdir /tmp/app
- cp -r README.md CHANGELOG.md appinfo css lib img l10n js src templates screenshots vendor /tmp/app
- /usr/src/nextcloud/occ integrity:sign-app
--privateKey=/tmp/side_menu.key
--certificate=/tmp/side_menu.crt
--path=/tmp/app
- mv /tmp/app/appinfo/signature.json appinfo/
"Create package":
image: deblan/php:8.3
volumes: *volumes
environment:
APP_CERTIFICATE:
from_secret: app_certificate
commands:
- cd "/builds/$CI_COMMIT_SHA"
- apt-get update
- apt-get install -y zip make
- mkdir -p "$HOME/.nextcloud/certificates"
- echo "$APP_CERTIFICATE" > "$HOME/.nextcloud/certificates/side_menu.key"
- export VERSION=$(grep "<version>" appinfo/info.xml | grep -o "[0-9]*\.[0-9]*\.[0-9]*" --color=never)
- export RELEASE_DIRECTORY="/var/www/html/artifacts/deblan/side_menu"
- make release
"Push release":
image: plugins/gitea-release
volumes: *volumes
settings:
api_key:
from_secret: gitnet_api_key
base_url: https://gitnet.fr
note: ${CI_COMMIT_MESSAGE}
files: /var/www/html/artifacts/deblan/side_menu/${CI_COMMIT_TAG/v//}/*

17
.woodpecker/.security.yml Normal file
View file

@ -0,0 +1,17 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
depends_on:
- build
skip_clone: true
steps:
"Check dependencies":
image: gitnet.fr/deblan/osv-detector:v0.10
volumes: *volumes
commands:
- cd "/builds/$CI_COMMIT_SHA"
- osv-detector package-lock.json
failure: ignore

View file

@ -1,10 +1,232 @@
## [Unreleased]
## 4.1.1
### Fixed
* fix(CssController): add missing NoCSRFRequired import (#397)
* fix(SideMenu): ncApps must be an array (#369)
## 4.1.0
### Added
* add compatibility with NC31
### Fixed
* fix(service): add service constructor arguments
* fix(settings): remove non-existing and unused ILogger service
### Changed
* refactor(controller): usage of attributes instead of annotations
## 4.0.1
### Fixed
* fix top menu labels (fix #368)
* fix #369: The menu is displayed even if there are no apps
## 4.0.0
### Added
* add compatibility with NC30
## 3.13.1
### Fixed
* fix #354: remove the opener when the menu is always displayed
* fix extra margin between the logo and the opener
## 3.13.0
### Added
* show apps generated with Tables (fix #349)
* add constructor property promotion
### Fixed
* remove .app-navigation--close translationX for always-displayed menu (fix #348)
## 3.12.0
### Added
* add compatibility with NC29
## 3.11.8
### Fixed
* move the logo inside #nextcloud element (fix #278 #239) [NC26]
## 3.11.7
### Added
* update translations
* update ci steps names
### Fixed
* add accessibility to open and close buttons (#311)
* fully apply Nextcloud AppMenu.vue updated (#326)
* add missing label on the 'save' button in personal settings (fix #318)
### Changed
* upgrade axios
* upgrade css-loader
## 3.11.6
### Fixed
* add --background-invert-if-bright in top menu (fix #326)
## 3.11.5
### Fixed
* add missing label on buttons for accessiblity (fix #311)
## 3.11.4
### Fixed
* add label on buttons for accessiblity (fix #311)
## 3.11.3
### Fixed
* fix menu icon in decks, collectives and other apps (#302)
## 3.11.2
### Fixed
* add default translations for Slovak - fix #298
## 3.11.1
### Added
* add Portuguese (Brazil) translations - Thanks to igorfreire
### Fixed
* add width to .side-menu-categories for side menu with categories display - fix #294
## 3.11.0
### Added
* add a search component in menus - fix #282
### Fixed
* remove the label of the link to personal settings - fix #283
## 3.10.3
### Fixed
* change the way to load nextcloud components (NcActionLink/NcActions) - fix #274
* update @nexcloud/* packages
## 3.10.2
### Fixed
* add missing properties
## 3.10.1
### Fixed
* fix #269: use php7 syntax
## 3.10.0
### Added
* add compatibility with NC28
### Fixed
* fix NC28 error: remove deprecated method `OC_App::getNavigation()`
## 3.9.1
### Fixed
* fix fixed menu on dashboard (#262)
## 3.9.0
### Added
* add compatibility with NC27
### Fixed
* fix app redirect (#261)
## 3.8.0
### Added
* add option to show hovered label only on top menu (fix #253)
## 3.7.4
### Fixed
* fix Integrity failed (#247)
## 3.7.3
### Fixed
* fix #244: use app href for redirection
### Added
* add signature on build
## 3.7.2
### Added
* update pipeline conditions allowing `fix/*`
### Fixed
* fix #233: load configuration and then retrieve apps in default side menu display
## 3.7.1
### Fixed
* fix build process (#230)
## 3.7.0
### Added
* add translations (thanks to AHOHNMYC)
* add compatibility with NC26
## 3.6.0
### Added
* add hidden apps compatible with default menu (#219)
## 3.5.2
### Fixed
* add check if menu exists before adding event listeners (#210)
## 3.5.1
### Added
* add translations (thanks to p-bo adn gallegonovato)
### Fixed
* fix #189: sorting not applied on mobile
## 3.5.0
### Added
* add dependency check (ci)
* add code quality check (ci)
* add translations (thanks to gallegonovato)
* add option to disable the display labels in the top menu (#194)
### Fixed
* fix missing img alt (settings image)
* fix code quality alerts
## 3.4.1
### Added
* add translations (thanks to zonorti, jorisvandijk, jak2k)
### Fixed
* fix #183: hide custom categories list when empty (admin page)
## 3.4.0
### Added
* add translations (thanks to Pavelb, nier, Timur, p-bo)
* add possibility to define Custom Menu as default app and redirect to the first top menu app (#177)
## 3.3.2
### Fixed
* fix #173: reduce the height of categories list
## 3.3.1
### Fixed
* fix #162: top and side apps does work correctly
## 3.3.0
### Added
* add documentation in admin page
* add app sorter in user config side (#160)
### Fixed
* fix #164: open apps in new tab does not work
* fix #162 #159: top and side apps does work correctly
## 3.2.1
### Fixed
* fix #150: active app is not visible has active in menu (except in default menu)
* fix #151: opener position
## 3.2.0
### Added
* use custom app names using 'app.navigation.name' (#148)
* app sorting with all displays (#147)
## 3.1.0
### Added
* add global custom app sorting for the top menu
### Fixed
* fix admin list/modal look
## 3.0.1
### Fixed
* Remove the gap between the window's top and menu categories (large menu)
## 3.0.0
### Added
* Add compatibility with NC25 (#136/#135)
### Removed
* Nextcloud 20-24 are not supported anymore
* AppOrder is not supported anymore
## 2.5.1
### Fixed
* fix icon render (#133)
## 2.5.0
### Changed
* upgrade dependencies

View file

@ -1,11 +1,15 @@
npm-build:
build: dep
npm run build
npm-watch:
watch: dep
npm run watch
dep:
npm i
npm link @nextcloud/vue || sudo npm link @nextcloud/vue
.ONESHELL:
release: npm-build translations
release:
if [ -z "$$VERSION" ]; then
echo "VERSION required"
exit 1
@ -30,4 +34,4 @@ translations:
.ONESHELL:
run-code-quality-analysis:
export SONAR_TOKEN="$$SONAR_TOKEN_DEBLAN_SIDE_MENU"
sonar-scanner -Dsonar.projectKey=deblan-side_menu -Dsonar.sources=. -Dsonar.host.url=https://cq.gitnet.fr
sonar-scanner -Dsonar.projectKey=deblan-side_menu -Dsonar.sources=. -Dsonar.host.url=$$SONAR_SERVER -Dsonar.branch.name=$$(git branch --show-current)

View file

@ -1,12 +1,16 @@
🤙 Nextcloud app / Custom menu 🎨
===============================
[![Build Status](https://ci.gitnet.fr/api/badges/deblan/side_menu/status.svg)](https://ci.gitnet.fr/deblan/side_menu)
[![Translations](https://translate.codeberg.org/widgets/custom-menu/-/application/svg-badge.svg)](https://translate.codeberg.org/engage/custom-menu/)
![Downloads](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.K_downloads&suffix=K&url=https%3A%2F%2Fapi-side-menu.deblan.org%2Fdownloads.php)
Allows you to modify the position of the main menu by creating a panel on the left of the interface or with a big menu on the top.
You can also add and sort custom categories, define apps that must be displayed in the top menu, etc. Fully customisable.
This application is rather suitable for instances that activate a lot of applications.
You can customize colors depending of the theme (Dark theme and Breeze Dark). Comptatible with AppOrder.
You can customize colors depending of the theme (Dark theme and Breeze Dark).
* [Installation and upgrade](#installation-and-upgrade)
* [How to contribute?](#how-to-contribute)
@ -15,12 +19,12 @@ You can customize colors depending of the theme (Dark theme and Breeze Dark). Co
You like this app and you want to support me? ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan) or [Donate with liberapay](https://liberapay.com/deblan)
[![Build Status](https://ci.gitnet.fr/api/badges/deblan/side_menu/status.svg)](https://ci.gitnet.fr/deblan/side_menu)
## [📘 Read the documentation](https://deblan.gitnet.page/side_menu_doc/)
Requirements
------------
* PHP >= 7.4
* PHP >= 8.0
* App `theming` enabled
Installation and upgrade
@ -46,19 +50,33 @@ Users can disable the menu using the page of personal settings.
Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
### Use first top menu app as default app
You can easily let Custom Menu redirect to the first app in the top menu by changing the following parameter in your `config/config.php`:
```
'defaultapp' => 'side_menu',
```
If the top menu is empty then it redirects to files.
How to contribute?
------------------
You can report a bug or request a feature by opening an issue: https://gitnet.fr/deblan/side_menu/issues
If you are a developer:
### You are a translator
Translations are managed from [translate.codeberg.org](https://translate.codeberg.org/projects/custom-menu/application/).
### You are a developer
* fork the repository
* install an instance of Nextcloud
* go to `apps/` and clone your repository
* go to `apps/side_menu` and run `npm install`
* go to `apps/side_menu` and run `make dep`
Build javascripts using `make npm-build` (or `make npm-watch` to build them in real time).
Build javascripts using `make build` (or `make watch` to build them in real time).
Then commit and create a pull request.
@ -66,3 +84,10 @@ Support
-------
You can join the official room on Matrix: [#custommenu:neutralnetwork.org](https://matrix.to/#/#custommenu:neutralnetwork.org).
Notice
------
Because I believe in a free and decentralized Internet, [Gitnet](https://gitnet.fr) is **self-hosted at home**.
In case of downtime, you can download **Custom Menu** from [here](https://kim.deblan.fr/~side_menu/).

View file

@ -1,6 +1,5 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<?xml version="1.0" encoding="UTF-8" ?>
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>side_menu</id>
<name>Custom menu</name>
<summary>Modify the display of the menu.</summary>
@ -11,13 +10,13 @@ This application is rather suitable for instances that activate a lot of applica
Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
You can customize colors depending of the theme (Dark theme and Breeze Dark). Comptatible with AppOrder.
You can customize colors depending of the theme (Dark theme and Breeze Dark).
You can report a bug or request a feature by opening an issue.
Requirements:
* PHP >= 7.4
* PHP >= 8.1
* App `theming` enabled
If you like this application and if you want to support the development:
@ -25,29 +24,37 @@ If you like this application and if you want to support the development:
* [Buy me a coffee](https://www.buymeacoffee.com/deblan)
* [Donate with liberapay](https://liberapay.com/deblan)
* [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments)
Notice
------
Because I believe in a free and decentralized Internet, [Gitnet](https://gitnet.fr) is **self-hosted at home**.
In case of downtime, you can download **Custom Menu** from [here](https://kim.deblan.fr/~side_menu/).
]]></description>
<version>2.5.1</version>
<version>4.1.1</version>
<licence>agpl</licence>
<author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author>
<author mail="contact@deblan.fr" homepage="https://www.deblan.fr/">Simon Vieille</author>
<namespace>SideMenu</namespace>
<documentation>
<admin>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</admin>
<admin>https://deblan.gitnet.page/side_menu_doc/</admin>
<developer>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</developer>
</documentation>
<category>customization</category>
<website>https://gitnet.fr/deblan/side_menu</website>
<discussion>https://matrix.to/#/!TFPucDATKODpHNVAtu:neutralnetwork.org?via=neutralnetwork.org</discussion>
<discussion><![CDATA[https://matrix.to/#/!TFPucDATKODpHNVAtu:neutralnetwork.org?via=neutralnetwork.org]]></discussion>
<bugs>https://gitnet.fr/deblan/side_menu/issues</bugs>
<repository type="git">https://gitnet.fr/deblan/side_menu</repository>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png</screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_big_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_default_menu.png]]></screenshot>
<dependencies>
<nextcloud min-version="20" max-version="24"/>
<php min-version="7.4"/>
<php min-version="8.1" max-version="8.4" />
<nextcloud min-version="30" max-version="32"/>
</dependencies>
<settings>
<admin>OCA\SideMenu\Settings\Admin</admin>

View file

@ -1,30 +0,0 @@
<?php
/**
* @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' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'],
['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'],
['name' => 'Js#config', 'url' => '/js/config', 'verb' => 'GET'],
['name' => 'Nav#items', 'url' => '/nav/items', 'verb' => 'GET'],
['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'],
['name' => 'AdminSetting#removeCache', 'url' => '/admin/cache/remove', 'verb' => 'GET'],
['name' => 'AdminSetting#exportConfiguration', 'url' => '/admin/config/export', 'verb' => 'GET'],
],
];

View file

@ -1,21 +1,75 @@
<?php
/**
* Imports a json configuration into a sqlite database.
*
* Usage:
* php bin/import_config.php /path/to/config.json /path/to/owncloud.db
*/
$configFile = $argv[1];
$databaseFile = $argv[2];
function showUsageAndExit(int $code)
{
global $argv;
$content = file_get_contents($configFile);
$config = json_decode($content, true);
echo "${argv[0]} [--help] --config /path/to/config/config.php --file /path/to/config.json\n";
$pdo = new \Pdo(sprintf('sqlite:%s', $databaseFile));
$stmt = $pdo->prepare('UPDATE oc_appconfig SET configvalue=:value WHERE configkey=:key and appid=:appId');
exit($code);
}
foreach ($config as $key => $value) {
function value(string $shortName, string $longName, array $options, bool $required = true): ?string
{
$value = $options[$shortName] ?? $options[$longName] ?? null;
if (is_array($value)) {
echo "To much --{$longName}\n";
showUsageAndExit(1);
}
if (empty($value) && $required) {
echo "--{$longName} is missing\n";
showUsageAndExit(1);
}
return $value;
}
$options = getopt('t:f:c:h', [
'type:',
'file:',
'config:',
'help',
]);
$help = value('h', 'help', $options, false);
$config = value('c', 'config', $options);
$file = value('f', 'file', $options);
if (!is_readable($config) && !is_file($config)) {
echo "No such file: {$config}\n";
exit(1);
}
if (!is_readable($file) && !is_file($file)) {
echo "No such file: {$file}\n";
exit(1);
}
$appConfig = json_decode(file_get_contents($file), true);
require $config;
if ('mysql' === $CONFIG['dbtype']) {
$pdo = new \PDO(
'mysql:host='.$CONFIG['dbhost'].';dbname='.$CONFIG['dbname'],
$CONFIG['dbuser'],
$CONFIG['dbpassword']
);
} elseif ($CONFIG['dbtype']) {
$pdo = new \PDO(sprintf('sqlite:%s', $CONFIG['datadirectory'].'/owncloud.db'));
} else {
echo "dbtype is not valid\n";
exit(1);
}
$stmt = $pdo->prepare('UPDATE '.$CONFIG['dbtableprefix'].'appconfig SET configvalue=:value WHERE configkey=:key and appid=:appId');
foreach ($appConfig as $key => $value) {
$stmt->execute([
'appId' => 'side_menu',
'key' => $key,

View file

@ -16,131 +16,205 @@
*/
#side-menu-section input[type="color"] {
width: 100px;
margin: 10px 0 10px 0;
width: 100px;
margin: 10px 0 10px 0;
padding: 0;
border-radius: 0;
}
#-dropside-menu-section input[type="checkbox"] {
vertical-align: middle;
vertical-align: middle;
}
#side-menu-section input[type="range"] {
vertical-align: middle;
vertical-align: middle;
}
#side-menu-section select {
margin: 10px 0 10px 0;
margin: 10px 0 10px 0;
}
.keyboard-key {
padding: 1px 9px;
margin: 0 2px;
background: #eee;
border: 1px solid #aaa;
color: #555;
border-radius: 3px;
padding: 1px 9px;
margin: 0 2px;
background: #eee;
border: 1px solid #aaa;
color: #555;
border-radius: 3px;
}
.side-menu-display {
padding: 10px;
border: 2px solid transparent;
max-width: 100%;
cursor: pointer;
padding: 10px;
border: 2px solid transparent;
max-width: 100%;
cursor: pointer;
}
.side-menu-display.is-active {
border: 2px solid #91cb7f;
border: 2px solid #91cb7f;
}
.info {
margin-top: 8px;
padding: 5px;
background: #91cb7f;
color: #fff;
border-radius: var(--border-radius);
margin-top: 8px;
padding: 5px;
background: #91cb7f;
color: #fff;
border-radius: var(--border-radius);
}
#side-menu-section h2 small {
font-size: 11px;
font-weight: normal;
font-size: 11px;
font-weight: normal;
}
.side-menu-toggler {
cursor: pointer;
cursor: pointer;
}
.side-menu-setting-list {
margin: 10px 4px 4px 0px;
margin: 10px 4px 4px 0px;
border: 2px solid var(--color-border-dark);
border-radius: 15px;
}
.side-menu-setting-list-item {
padding: 5px 10px;
border: 1px solid var(--color-border-dark);
max-width: 300px;
margin: -1px 0 0 0;
cursor: pointer;
padding: 5px 10px;
border-bottom: 1px solid var(--color-border-dark);
max-width: 300px;
margin: -1px 0 0 0;
cursor: pointer;
line-height: 32px;
}
.side-menu-setting-list-item:last-child {
border-bottom: 0;
}
.side-menu-setting-list-drop {
background: yellow;
border-color: yellow;
height: 34px;
background: yellow;
border-color: yellow;
height: 34px;
}
.side-menu-setting.arrow {
color: #ccc;
padding-right: 5px;
color: #ccc;
padding-right: 5px;
}
.side-menu-setting-list-item input {
min-height: auto;
margin-top: -1px;
margin-top: 0;
height: 21px !important;
min-height: auto !important;
}
#apps-categories-custom-list select {
width: 100%;
width: 100%;
}
.side-menu-setting-table {
display: table;
width: 100%;
display: table;
width: 100%;
}
.side-menu-setting-row {
display: table;
display: table;
margin-bottom: 10px;
}
.side-menu-setting-row code {
margin-left: 2px;
margin-bottom: 1px;
padding: 3px 10px;
border-radius: 5px;
display: inline-block;
right: 2px;
border: 1px solid var(--color-border-dark);
}
.side-menu-setting-label {
display: table-cell;
width: 400px;
padding-right: 20px;
display: table-cell;
width: 430px;
padding-right: 20px;
}
.side-menu-setting-label--top {
vertical-align: top;
}
.side-menu-setting-form {
display: table-cell;
min-width: 300px;
display: table-cell;
min-width: 300px;
}
.side-menu-setting-label-short {
width: 300px;
width: 300px;
}
.side-menu-setting-form-long {
width: 400px;
width: 400px;
}
#side-menu-save-progress {
display: inline-block;
width: 0;
height: 15px;
background: #fff;
display: inline-block;
width: 0;
height: 15px;
background: #fff;
}
.btn-reset {
display: inline-block;
cursor: pointer;
position: absolute;
margin-top: 17px;
margin-left: 5px;
display: inline-block;
cursor: pointer;
position: relative;
top: -8px;
left: 5px;
transition-duration: 0.8s;
transition-property: transform;
transform: rotate(360deg);
}
.btn-reset--down {
top: 2px;
}
.btn-reset--progress {
transform: rotate(-359deg);
}
.badges {
margin-bottom: 14px;
margin-top: 4px;
}
.badge {
border-width: 1px;
padding: 2px 8px;
margin-right: 2px;
margin-bottom: 5px;
display: inline-block;
border-radius: 4px;
font-size: 13px;
}
.badge-1 {
background: #d4ce14;
border-color: #cad413;
color: #373a05;
}
.badge-2 {
background: #96d47f;
border-color: #7ed49b;
color: #333;
}
.badge-3 {
background: #d4540a;
border-color: #d4700c;
color: #fff;
}
.badge-4 {
background: #9d81d4;
border-color: #c681d4;
color: #fff;
}

View file

@ -16,326 +16,371 @@
*/
#side-menu {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100%;
max-width: 250px;
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
z-index: 3000;
color: var(--side-menu-text-color, #fff);
box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px;
display: none;
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100%;
max-width: 290px;
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
z-index: 3000;
color: var(--side-menu-text-color, #fff);
box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px;
display: none;
}
#side-menu a {
transition: 0.2s;
transition: 0.2s;
}
#side-menu.open {
display: block;
display: block;
}
#header .side-menu-opener {
margin-left: 0px;
margin-left: 0px;
margin-top: -1px;
}
.side-menu-settings {
margin-right: 9px;
margin-top: 2px;
float: right;
line-height: 34px;
height: 28px;
display: none;
margin-right: 9px;
margin-top: 2px;
float: right;
line-height: 34px;
height: 42px;
display: none;
}
.side-menu-settings a {
color: var(--side-menu-text-color, #fff);
display: block;
padding: 4px 7px;
color: var(--side-menu-text-color, #fff);
display: block;
padding: 4px 7px;
}
.side-menu-settings:hover a, .side-menu-settings a:active, .side-menu-settings a:focus {
background: var(--side-menu-current-app-background-color, #444);
background: var(--side-menu-current-app-background-color, #444);
}
.side-menu-settings img {
vertical-align: bottom;
margin-left: 3px;
width: 32px;
height: 32px;
vertical-align: bottom;
margin-left: 3px;
width: 32px;
height: 32px;
}
#side-menu.open .side-menu-settings {
display: block;
display: block;
}
.side-menu-opener {
background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
background-color: transparent !important;
height: 40px !important;
width: 40px !important;
border-radius: 0 !important;
border: 0 !important;
padding-right: 12px !important;
padding-left: 12px !important;
margin-left: 5px !important;
margin-left: 3px !important;
background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
background-color: transparent !important;
height: 40px !important;
width: 40px !important;
border-radius: 0 !important;
border: 0 !important;
padding-right: 12px !important;
padding-left: 12px !important;
margin-left: 5px !important;
margin-left: 3px !important;
overflow: hidden;
}
.side-menu-opener span {
position: relative;
left: 50px;
display: block;
width: 1px;
height: 1px;
overflow: hidden;
}
.side-menu-opener:active, .side-menu-opener:focus {
background-color: var(--side-menu-current-app-background-color, #444) !important;
background-color: var(--side-menu-current-app-background-color, #444) !important;
}
.side-menu-closer {
background: url('../img/side-menu-opener-closer.svg');
display: none;
background: url('../img/side-menu-opener-closer.svg');
display: none;
}
#side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide {
display: none !important;
display: none !important;
}
.side-menu-apps-list {
height: calc(100vh - 150px);
z-index: 2200;
position: fixed;
top: 150px;
width: 100%;
max-width: 250px;
overflow: auto;
height: calc(100vh - 150px);
z-index: 2200;
position: fixed;
top: 150px;
width: 100%;
max-width: 290px;
overflow: auto;
}
.side-menu-app-icon {
width: 20px;
vertical-align: top;
margin-right: 10px;
filter: invert(var(--side-menu-icon-invert-filter, 0%));
opacity: var(--side-menu-icon-opacity, 1);
}
.side-menu-app-icon svg {
vertical-align: middle;
margin-top: -3px;
}
.side-menu-app-icon .app-icon-notification {
display: none;
width: 20px;
vertical-align: middle;
margin-top: -4px;
margin-right: 10px;
filter: invert(var(--side-menu-icon-invert-filter, 0%));
opacity: var(--side-menu-icon-opacity, 1);
}
.side-menu-app a {
line-height: 30px;
color: var(--side-menu-text-color, #fff);
display: block;
padding: 7px 0 5px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 30px;
color: var(--side-menu-text-color, #fff);
display: block;
padding: 7px 0 5px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.side-menu-app a:hover, .side-menu-app.active, .side-menu-app a:focus {
background: var(--side-menu-current-app-background-color, #444);
background: var(--side-menu-current-app-background-color, #444);
}
.side-menu-logo {
text-align: center;
text-align: center;
}
.side-menu-logo img {
max-width: 60%;
max-height: 100px;
max-width: 60%;
max-height: 100px;
}
.side-menu-header {
height: 150px;
width: 100%;
z-index: 2300;
max-width: 250px;
position: fixed;
padding-top: 2px;
top: 0;
.enu-header {
height: 150px;
width: 100%;
z-index: 2300;
max-width: 290px;
position: fixed;
padding-top: 2px;
top: 0;
}
#side-menu.side-menu-with-categories .side-menu-header {
max-width: 295px;
max-width: 295px;
}
#side-menu.hide-opener .side-menu-logo {
margin-top: 20px;
margin-top: 10px;
}
#side-menu-loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 3001;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 3001;
}
#side-menu-loader-bar {
height: 4px;
background: var(--side-menu-loader-color, #0e75ac);
width: 0;
transition-property: width;
height: 4px;
background: var(--side-menu-loader-color, #0e75ac);
width: 0;
transition-property: width;
}
#side-menu.side-menu-big, #side-menu.side-menu-with-categories {
max-width: 100%;
height: auto;
max-width: 100%;
height: auto;
}
.side-menu-big .side-menu-header, .side-menu-with-categories .side-menu-header {
height: auto;
height: auto;
}
.side-menu-big .side-menu-apps-list, .side-menu-with-categories .side-menu-apps-list {
height: auto;
position: static;
max-width: 100vw;
overflow: auto;
height: auto;
position: static;
max-width: 100vw;
overflow: auto;
}
.side-menu-big .side-menu-app a, .side-menu-with-categories .side-menu-app a {
padding: 7px 0 7px 7px;
padding: 7px 0 7px 7px;
}
.side-menu-categories-wrapper {
padding-bottom: 70px;
padding-bottom: 70px;
}
.side-menu-categories {
max-height: calc(100vh - 50px);
overflow: auto;
position: relative;
top: 50px;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 0 10% 0 10%;
max-height: calc(100vh - 55px);
overflow: auto;
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 0 10% 0 10%;
}
.side-menu-category {
padding: 10px 20px;
flex: 1 1 auto;
padding: 10px 20px;
flex: 1 1 auto;
}
.side-menu-category-title {
padding-left: 10px;
color: var(--side-menu-text-color, #fff);
padding-left: 10px;
color: var(--side-menu-text-color, #fff);
font-weight: bold;
font-size: 20px;
margin-bottom: 12px;
line-height: 30px;
margin-top: 0;
}
.side-menu-loader {
text-align: center;
text-align: center;
}
.side-menu-loader svg {
width: 38px;
margin: auto;
stroke: var(--side-menu-text-color, #fff);
width: 45px;
margin: auto;
stroke: var(--side-menu-text-color, #fff);
}
.side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon {
vertical-align: middle;
margin-top: -2px;
}
.side-menu-always-displayed #header,
.side-menu-always-displayed body {
width: calc(100% - 50px) !important;
vertical-align: middle;
margin-top: -2px;
}
.side-menu-always-displayed body {
position: absolute;
left: 50px;
width: calc(100% - 50px) !important;
position: absolute;
left: 50px;
}
.side-menu-always-displayed #header {
position: absolute !important;
}
.side-menu-always-displayed #side-menu {
display: block;
display: block;
}
.side-menu-always-displayed .side-menu-apps-list {
height: calc(100vh - 49px);
top: 49px;
overflow: hidden;
height: 100vh;
top: 0;
overflow: hidden;
}
.side-menu-always-displayed .side-menu-apps-list--with-settings {
height: calc(100vh - 49px);
top: 49px;
}
.side-menu-always-displayed .side-menu-apps-list:hover {
overflow: auto;
overflow: auto;
}
.side-menu-always-displayed #side-menu,
.side-menu-always-displayed .side-menu-header,
.side-menu-always-displayed .side-menu-apps-list {
width: 50px;
width: 50px;
}
.side-menu-always-displayed #side-menu .side-menu-app-text,
.side-menu-always-displayed #header .side-menu-opener,
.side-menu-always-displayed .side-menu-logo {
display: none;
display: none;
}
.side-menu-always-displayed #side-menu .side-menu-header {
height: 49px;
height: 49px;
}
.side-menu-always-displayed #side-menu.open,
.side-menu-always-displayed #side-menu.open .side-menu-apps-list,
.side-menu-always-displayed #side-menu.open .side-menu-header {
width: 100%;
width: 100%;
}
.side-menu-always-displayed #side-menu.open .side-menu-app-text {
display: inline;
display: inline;
}
.side-menu-always-displayed .app-navigation--close {
transform: translateX(calc(-100% + 50px)) !important;
.side-menu-always-displayed .app-navigation-toggle-wrapper {
right: 0 !important;
margin-left: 0 !important;
}
#side-menu.side-menu-with-categories {
max-width: 290px;
height: 100vh;
max-width: 290px;
height: 100vh;
}
.side-menu-with-categories .side-menu-categories {
display: block;
padding: 0;
display: block;
padding: 0;
width: 100%;
}
.side-menu-with-categories .side-menu-category {
padding: 10px 0;
padding: 10px 0;
}
.side-menu-always-displayed #body-settings, #body-settings.body-settings-side-menu {
overflow-x: visible;
overflow-x: visible;
}
.app-menu {
visibility: hidden;
}
.app-menu.show {
visibility: visible;
}
.side-menu-search {
float: right;
}
.side-menu-search input {
background: none;
border: 0;
border-radius: 0;
color: var(--side-menu-text-color);
}
.side-menu-search input::placeholder {
color: var(--side-menu-text-color);
}
.side-menu-always-displayed .side-menu-search {
display: none;
}
@media screen and (max-width: 1024px) {
#side-menu.side-menu-big {
max-width: 290px;
height: 100vh;
}
#side-menu.side-menu-big {
max-width: 290px;
height: 100vh;
}
.side-menu-categories {
display: block;
padding: 0;
}
#side-menu.hide-opener.side-menu-big .side-menu-search {
float: none;
}
.side-menu-category {
padding: 10px 0;
}
.side-menu-categories {
display: block;
padding: 0;
}
.side-menu-category {
padding: 10px 0;
}
}
@media screen and (min-width: 1024px) {
.side-menu-closer {
display: block;
float: right;
margin-right: 9px;
}
.side-menu-closer {
display: block;
float: right;
margin-right: 9px;
}
.side-menu-big .side-menu-header {
max-width: 100%;
}
.side-menu-big .side-menu-header {
max-width: 100%;
}
}

View file

@ -3,6 +3,7 @@
namespace OCA\SideMenu\AppInfo;
use OC;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\User\User;
use OCA\SideMenu\Service\AppRepository;
@ -12,7 +13,11 @@ use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\INavigationManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Util;
use Psr\Container\ContainerInterface;
@ -26,6 +31,7 @@ class Application extends App implements IBootstrap
public const APP_ID = 'side_menu';
public const APP_NAME = 'Custom menu';
/**
* @var OC\AllConfig
*/
@ -41,14 +47,55 @@ class Application extends App implements IBootstrap
*/
protected $user;
/**
* {@inheritdoc}
*/
public function __construct(array $urlParams = [])
{
parent::__construct(self::APP_ID, $urlParams);
}
public function register(IRegistrationContext $context): void
{
$context->registerService(CategoryRepository::class, function (ContainerInterface $c) {
return new CategoryRepository(
$c->get(CategoryFetcher::class),
$c->get(ConfigProxy::class),
$c->get(IConfig::class),
$c->get(IFactory::class),
$c->get(IUserSession::class)
);
});
$context->registerService(AppRepository::class, function (ContainerInterface $c) {
return new AppRepository(
$c->get(\OC_App::class),
$c->get(INavigationManager::class),
$c->get(IFactory::class),
$c->get(ConfigProxy::class),
$c->get(CategoryRepository::class),
$c->get(IEventDispatcher::class),
$c->get(IUserSession::class)
);
});
$context->registerService(ConfigProxy::class, function (ContainerInterface $c) {
return new ConfigProxy(
$c->get(IConfig::class),
);
});
}
public function boot(IBootContext $context): void
{
$this->config = \OC::$server->getConfig();
$this->cspnm = \OC::$server->getContentSecurityPolicyNonceManager();
$this->user = \OC::$server[IUserSession::class]->getUser();
if (!$this->isEnabled()) {
return;
}
$this->addAssets();
}
protected function isEnabled(): bool
{
$enabled = true;
@ -97,38 +144,14 @@ class Application extends App implements IBootstrap
$cache = $this->config->getAppValue(self::APP_ID, 'cache', '0');
foreach ($assets as $value) {
$route = OC::$server->getURLGenerator()->linkToRoute($value['route'], ['v' => $cache]);
$route = \OC::$server->getURLGenerator()->linkToRoute(
$value['route'],
['v' => $cache]
);
$value['attr'][$value['route_attr']] = $route;
Util::addHeader($value['type'], $value['attr'], '');
}
}
public function register(IRegistrationContext $context): void
{
$context->registerService('AppRepository', function () {
return new AppRepository();
});
$context->registerService('CategoryRepository', function () {
return new CategoryRepository();
});
$context->registerService('ConfigProxy', function () {
return new ConfigProxy();
});
}
public function boot(IBootContext $context): void
{
$this->config = OC::$server->getConfig();
$this->cspnm = OC::$server->getContentSecurityPolicyNonceManager();
$this->user = OC::$server[IUserSession::class]->getUser();
if (!$this->isEnabled()) {
return;
}
$this->addAssets();
}
}

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -20,39 +21,28 @@ namespace OCA\SideMenu\Controller;
use OCA\SideMenu\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
class AdminSettingController extends Controller
{
/**
* @var IConfig
*/
protected $config;
/**
* @var IURLGenerator
*/
protected $urlGenerator;
public function __construct($appName, IRequest $request, IConfig $config, IURLGenerator $urlGenerator)
{
public function __construct(
$appName,
IRequest $request,
protected IConfig $config,
protected IURLGenerator $urlGenerator
) {
parent::__construct($appName, $request);
$this->config = $config;
$this->urlGenerator = $urlGenerator;
}
/**
* @NoCSRFRequired
*
* @return RedirectResponse
*/
public function removeCache()
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/admin/cache/remove')]
public function removeCache(): RedirectResponse
{
$this->config->setAppValue(Application::APP_ID, 'cache-categories', '[]');
@ -61,12 +51,9 @@ class AdminSettingController extends Controller
]).'#more');
}
/**
* @NoCSRFRequired
*
* @return Response
*/
public function exportConfiguration()
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/admin/config/export')]
public function exportConfiguration(): DataDownloadResponse
{
$keys = $this->config->getAppKeys(Application::APP_ID);
$appConfig = [];

View file

@ -0,0 +1,98 @@
<?php
/**
* @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\SideMenu\Controller;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
class AppController extends Controller
{
public function __construct(
string $appName,
IRequest $request,
protected AppRepository $appRepository,
protected IURLGenerator $urlGenerator,
protected ConfigProxy $config
) {
parent::__construct($appName, $request);
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/')]
public function index(): RedirectResponse
{
$user = \OC::$server[IUserSession::class]->getUser();
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$hiddenApps = $this->config->getAppValueArray('big-menu-hidden-apps', '[]');
$isForced = $this->config->getAppValueBool('force', '0');
$userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]');
$apps = $this->appRepository->getOrderedApps($user);
if (!$isForced && !empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
foreach ($apps as $app) {
$inTopMenuApps = in_array($app['id'], $topMenuApps);
$inHiddenApps = in_array($app['id'], $hiddenApps);
if (!$inTopMenuApps && $inHiddenApps) {
continue;
}
return $this->redirectToApp($app, true);
}
return $this->redirectToApp('files');
}
protected function redirectToApp($app, bool $isHref = false): RedirectResponse
{
if (!$isHref) {
$isIgnoreFrontController = true === \OC::$server->getConfig()->getSystemValue(
'htaccess.IgnoreFrontController',
false
);
$isFrontControllerActive = 'true' === getenv('front_controller_active');
if ($isIgnoreFrontController || $isFrontControllerActive) {
$path = '/apps/%s/';
} else {
$path = '/index.php/apps/%s/';
}
$url = $this->urlGenerator->getAbsoluteURL(sprintf($path, $app));
} else {
$url = $app['href'];
}
return new RedirectResponse($url);
}
}

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -18,64 +19,40 @@
namespace OCA\SideMenu\Controller;
use OC;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
use OCP\IUserSession;
use OCA\Theming\ThemingDefaults;
use OCA\SideMenu\Service\Color;
class CssController extends Controller
{
/**
* @var ConfigProxy
*/
protected $config;
/**
* @var User
*/
protected $user;
/**
* @var ThemingDefaults
*/
protected $theming;
/**
* @var Color
*/
protected $color;
protected ?User $user;
public function __construct(
string $appName,
IRequest $request,
ConfigProxy $config,
ThemingDefaults $theming,
Color $color
)
{
protected ConfigProxy $config,
protected ThemingDefaults $theming,
protected Color $color
) {
parent::__construct($appName, $request);
$this->user = OC::$server[IUserSession::class]->getUser();
$this->config = $config;
$this->theming = $theming;
$this->color = $color;
$this->user = \OC::$server[IUserSession::class]->getUser();
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return Response
*/
public function stylesheet()
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/css/stylesheet')]
public function stylesheet(): TemplateResponse
{
$response = new TemplateResponse(Application::APP_ID, 'css/stylesheet', $this->getConfig(), 'blank');
$response->addHeader('Content-Type', 'text/css');
@ -105,16 +82,18 @@ class CssController extends Controller
$topSideMenuApps = $userTopSideMenuApps;
}
$isDarkThemeUserEnabled = $this->config->getUserValue($this->user, 'theme', '', 'accessibility') === 'dark';
$isDarkThemeUserEnabled = 'dark' === $this->config->getUserValue($this->user, 'theme', '', 'accessibility');
$isBreezeDarkUserEnabled = $this->config->getUserValue($this->user, 'theme_enabled', '', 'breezedark');
$isBreezeDarkUserEnabled = $isBreezeDarkUserEnabled === '1' || ($isBreezeDarkGlobalEnabled && $isBreezeDarkUserEnabled === '');
$isBreezeDarkUserEnabled = '1' === $isBreezeDarkUserEnabled
|| ($isBreezeDarkGlobalEnabled && '' === $isBreezeDarkUserEnabled);
} else {
$isDarkThemeUserEnabled = false;
$isBreezeDarkUserEnabled = false;
}
$isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled) || ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled);
$isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled)
|| ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled);
$primaryColor = $this->theming->getColorPrimary();
$lightenPrimaryColor = $this->color->adjustBrightness($primaryColor, 0.2);
@ -125,25 +104,33 @@ class CssController extends Controller
if ($isDarkMode) {
$backgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkenPrimaryColor);
$currentAppBackgroundColor = $this->config->getAppValue('dark-mode-current-app-background-color', $darkenPrimaryColor2);
$currentAppBackgroundColor = $this->config->getAppValue(
'dark-mode-current-app-background-color',
$darkenPrimaryColor2
);
$loaderColor = $this->config->getAppValue('dark-mode-loader-color', $lightenPrimaryColor);
$textColor = $this->config->getAppValue('dark-mode-text-color', $textColor);
$iconInvertFilter = abs($this->config->getAppValueInt('dark-mode-icon-invert-filter', '0')).'%';
$iconOpacity = abs($this->config->getAppValueInt('dark-mode-icon-opacity', '100') / 100);
$opener = $this->config->getAppValue('dark-mode-opener', 'side-menu-opener');
$backgroundOpacity = dechex($this->config->getAppValueInt('dark-mode-background-color-opacity', '100') * 255 / 100);
$opacity = $this->config->getAppValueInt('dark-mode-background-color-opacity', '100');
$backgroundOpacity = dechex($opacity * 255 / 100);
} else {
$backgroundColor = $this->config->getAppValue('background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('background-color-to', $darkenPrimaryColor);
$currentAppBackgroundColor = $this->config->getAppValue('current-app-background-color', $darkenPrimaryColor2);
$currentAppBackgroundColor = $this->config->getAppValue(
'current-app-background-color',
$darkenPrimaryColor2
);
$loaderColor = $this->config->getAppValue('loader-color', $lightenPrimaryColor);
$textColor = $this->config->getAppValue('text-color', $textColor);
$iconInvertFilter = abs($this->config->getAppValueInt('icon-invert-filter', '0')).'%';
$iconOpacity = abs($this->config->getAppValueInt('icon-opacity', '100') / 100);
$opener = $this->config->getAppValue('opener', 'side-menu-opener');
$backgroundOpacity = dechex($this->config->getAppValueInt('background-color-opacity', '100') * 255 / 100);
$opacity = $this->config->getAppValueInt('background-color-opacity', '100');
$backgroundOpacity = dechex($opacity * 255 / 100);
}
$backgroundColor .= $backgroundOpacity;

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -18,12 +19,15 @@
namespace OCA\SideMenu\Controller;
use OC;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
@ -32,47 +36,28 @@ use OCP\L10N\IFactory;
class JsController extends Controller
{
/**
* @var ConfigProxy
*/
protected $config;
/**
* @var User
*/
protected $user;
/**
* @var ThemingDefaults
*/
protected $themingDefaults;
/**
* @var IFactory
*/
protected $l10nFactory;
protected ?User $user;
public function __construct(
string $appName,
IRequest $request,
ConfigProxy $config,
ThemingDefaults $themingDefaults,
IFactory $l10nFactory
protected ConfigProxy $config,
protected ThemingDefaults $themingDefaults,
protected IFactory $l10nFactory
) {
parent::__construct($appName, $request);
$this->themingDefaults = $themingDefaults;
$this->user = OC::$server[IUserSession::class]->getUser();
$this->user = \OC::$server[IUserSession::class]->getUser();
$this->config = $config;
$this->l10nFactory = $l10nFactory;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*/
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/js/script')]
public function script(): TemplateResponse
{
$response = new TemplateResponse(Application::APP_ID, 'js/script', $this->getConfig(), 'blank');
@ -81,11 +66,10 @@ class JsController extends Controller
return $response;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*/
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/js/config')]
public function config(): JSONResponse
{
return new JSONResponse($this->getConfig());
@ -99,10 +83,12 @@ class JsController extends Controller
$useAvatar = $this->config->getAppValueBool('use-avatar', '0');
$isForced = $this->config->getAppValueBool('force', '0');
$addLogoLink = $this->config->getAppValueBool('add-logo-link', '1');
$appsOrder = $this->config->getAppValueArray('apps-order', '[]');
$avatar = null;
$settings = null;
if ($this->user) {
$userAppsOrder = $this->config->getUserValueArray($this->user, 'apps-order', '[]');
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($this->user, 'top-side-menu-apps', '[]');
@ -114,6 +100,10 @@ class JsController extends Controller
$topSideMenuApps = $userTopSideMenuApps;
}
if (!empty($userAppsOrder) && !$isForced) {
$appsOrder = $userAppsOrder;
}
$userTargetBlankMode = $this->config->getUserValueInt($this->user, 'target-blank-mode', '1');
$userTargetBlankApps = $this->config->getUserValueArray($this->user, 'target-blank-apps', '[]');
@ -121,10 +111,10 @@ class JsController extends Controller
$targetBlankApps = $userTargetBlankApps;
}
$isAvatarSet = OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists();
$isAvatarSet = \OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists();
if ($useAvatar && $isAvatarSet) {
$avatar = OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
$avatar = \OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
'userId' => $this->user->getUid(),
'size' => 128,
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
@ -132,13 +122,13 @@ class JsController extends Controller
}
if ($this->config->getAppValueBool('show-settings', '0')) {
$settingsNav = OC::$server->getNavigationManager()->getAll('settings');
$settingsNav = \OC::$server->getNavigationManager()->getAll('settings');
if (isset($settingsNav['settings'])) {
$settings = [
'href' => $settingsNav['settings']['href'],
'name' => $settingsNav['settings']['name'],
'avatar' => OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
'avatar' => \OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
'userId' => $this->user->getUid(),
'size' => 32,
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
@ -148,7 +138,7 @@ class JsController extends Controller
}
}
$indexUrl = OC::$server->getURLGenerator()->linkTo('', 'index.php');
$indexUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php');
return [
'opener-position' => $this->config->getAppValue('opener-position', 'before'),
@ -161,9 +151,14 @@ class JsController extends Controller
'side-with-categories' => $this->config->getAppValueBool('side-with-categories', '0'),
'big-menu' => $this->config->getAppValueBool('big-menu', '0'),
'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'),
'apps-order' => $appsOrder,
'avatar' => $avatar,
'top-menu-apps' => $topMenuApps,
'top-side-menu-apps' => $topSideMenuApps,
'top-menu-mouse-over-hidden-label' => $this->config->getAppValueInt(
'top-menu-mouse-over-hidden-label',
'0'
),
'target-blank-apps' => $targetBlankApps,
'settings' => $settings,
'logo' => $this->themingDefaults->getLogo(),

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -18,13 +19,15 @@
namespace OCA\SideMenu\Controller;
use OC;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\URLGenerator;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
@ -32,59 +35,25 @@ use OCP\L10N\IFactory;
class NavController extends Controller
{
/**
* @var ConfigProxy
*/
protected $config;
/**
* @var AppRepository
*/
protected $appRepository;
/**
* @var IFactory
*/
protected $l10nFactory;
/**
* @var CategoryFetcher
*/
protected $categoryFetcher;
/**
* @var URLGenerator
*/
protected $router;
public function __construct(
string $appName,
IRequest $request,
ConfigProxy $config,
AppRepository $appRepository,
CategoryRepository $categoryRepository,
URLGenerator $router,
IFactory $l10nFactory
protected ConfigProxy $config,
protected AppRepository $appRepository,
protected CategoryRepository $categoryRepository,
protected URLGenerator $router,
protected IFactory $l10nFactory
) {
parent::__construct($appName, $request);
$this->config = $config;
$this->appRepository = $appRepository;
$this->categoryRepository = $categoryRepository;
$this->l10nFactory = $l10nFactory;
$this->router = $router;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return JSONResponse
*/
public function items()
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/nav/items')]
public function items(): JSONResponse
{
$user = OC::$server[IUserSession::class]->getUser();
$user = \OC::$server[IUserSession::class]->getUser();
$items = [];
if (!$user) {
@ -93,11 +62,13 @@ class NavController extends Controller
]);
}
$apps = $this->appRepository->getVisibleApps();
$apps = $this->appRepository->getOrderedApps($user);
$categoriesLabels = $this->categoryRepository->getOrderedCategories();
$hiddenApps = $this->config->getAppValueArray('big-menu-hidden-apps', '[]');
$isForced = $this->config->getAppValueBool('force', '0');
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($user, 'top-side-menu-apps', '[]');
$userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]');
$appsCategories = [];
$categoriesAppsCount = [];
@ -106,12 +77,16 @@ class NavController extends Controller
$topMenuApps = $userTopMenuApps;
}
foreach ($apps as $app) {
if (in_array($app['id'], $topMenuApps)) {
continue;
}
if (!$isForced && !empty($userTopSideMenuApps)) {
$topSideMenuApps = $userTopSideMenuApps;
}
if (in_array($app['id'], $hiddenApps)) {
foreach ($apps as $app) {
$inTopMenuApps = in_array($app['id'], $topMenuApps);
$inTopSideMenuApps = in_array($app['id'], $topSideMenuApps);
$inHiddenApps = in_array($app['id'], $hiddenApps);
if (($inTopMenuApps && !$inTopSideMenuApps) || $inHiddenApps) {
continue;
}
@ -178,20 +153,16 @@ class NavController extends Controller
foreach ($items as $category => $value) {
if (empty($items[$category]['apps'])) {
unset($items[$category]);
} else {
uasort($items[$category]['apps'], function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
}
}
usort($items, function ($a, $b) use ($categoriesLabels) {
foreach ($categoriesLabels as $key => $value) {
if ($a['categoryId'] === 'other') {
if ('other' === $a['categoryId']) {
return -1;
}
if ($b['categoryId'] === 'other') {
if ('other' === $b['categoryId']) {
return 1;
}

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -21,47 +22,29 @@ namespace OCA\SideMenu\Controller;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
class PersonalSettingController extends Controller
{
/**
* @var IConfig
*/
protected $config;
/**
* @var ConfigProxy
*/
protected $configProxy;
/**
* @var IUserSession
*/
protected $userSession;
public function __construct($appName, IRequest $request, IConfig $config, ConfigProxy $configProxy, IUserSession $userSession)
{
public function __construct(
$appName,
IRequest $request,
protected IConfig $config,
protected ConfigProxy $configProxy,
protected IUserSession $userSession
) {
parent::__construct($appName, $request);
$this->config = $config;
$this->configProxy = $configProxy;
$this->userSession = $userSession;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @param mixed $name
* @param mixed $value
*
* @return Response
*/
public function valueSet($name, $value)
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/personalSetting/valueSet')]
public function valueSet($name, $value): array
{
$doSave = false;
$user = $this->userSession->getUser();
@ -97,7 +80,7 @@ class PersonalSettingController extends Controller
}
}
if (in_array($name, ['top-menu-apps', 'top-side-menu-apps'])) {
if (in_array($name, ['top-menu-apps', 'top-side-menu-apps', 'apps-order'])) {
$doSave = true;
$data = json_decode($value, true);

View file

@ -2,6 +2,13 @@
namespace OCA\SideMenu\Service;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\INavigationManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
/**
@ -11,47 +18,27 @@ use OCP\L10N\IFactory;
*/
class AppRepository
{
/**
* @var \OC_App
*/
protected $ocApp;
/**
* @var IFactory
*/
protected $l10nFactory;
/**
* @var ConfigProxy
*/
protected $config;
/**
* @var CategoryRepository
*/
protected $categoryRepository;
public function __construct(
\OC_App $ocApp,
IFactory $l10nFactory,
ConfigProxy $config,
CategoryRepository $categoryRepository
)
{
$this->ocApp = $ocApp;
$this->l10nFactory = $l10nFactory;
$this->config = $config;
$this->categoryRepository = $categoryRepository;
protected \OC_App $ocApp,
protected INavigationManager $navigationManager,
protected IFactory $l10nFactory,
protected ConfigProxy $config,
protected CategoryRepository $categoryRepository,
protected IEventDispatcher $dispatcher,
protected IUserSession $userSession,
) {
$this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent(
$this->userSession->isLoggedIn(),
new TemplateResponse(Application::APP_NAME, '')
));
}
/**
* Retrieves visibles apps.
*
* @return array
*/
public function getVisibleApps()
public function getVisibleApps(): array
{
$navigation = $this->ocApp->getNavigation();
$navigation = $this->navigationManager->getAll();
$appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]');
$categories = $this->categoryRepository->getOrderedCategories();
$apps = $this->ocApp->listAllApps();
@ -62,7 +49,7 @@ class AppRepository
foreach ([$app['id'], $app['id'].'_index'] as $id) {
if (isset($navigation[$id])) {
$app['name'] = $this->l10nFactory->get($id)->t($app['name']);
$app['name'] = $this->getAppName($app);
$app['href'] = $navigation[$id]['href'];
$app['icon'] = $navigation[$id]['icon'];
@ -75,17 +62,25 @@ class AppRepository
if ('external_index' === substr($app['id'], 0, 14)) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'name' => $this->getAppName($app),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [
'external_links',
],
];
} elseif ('tables_application' === substr($app['id'], 0, 18)) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->getAppName($app),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [],
];
} elseif ('files' === $app['id']) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'name' => $this->getAppName($app),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [],
@ -99,10 +94,42 @@ class AppRepository
}
}
usort($visibleApps, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
return $visibleApps;
}
public function getAppName($app): string
{
return $this->config->getAppValue(
'app.navigation.name',
$this->l10nFactory->get($app['id'])->t($app['name']),
$app['id']
);
}
public function getOrderedApps(?User $user = null): array
{
$apps = $this->getVisibleApps();
$orders = $this->config->getAppValueArray('apps-order', '[]');
if (null !== $user && !$this->config->getAppValueBool('force', '0')) {
$userOrders = $this->config->getUserValueArray($user, 'apps-order', '[]');
if (!empty($userOrders)) {
$orders = $userOrders;
}
}
usort($apps, function ($a, $b) use ($orders) {
$ak = array_keys($orders, $a['id'])[0] ?? null;
$bk = array_keys($orders, $b['id'])[0] ?? null;
if (null === $ak || null === $bk) {
return ($a['name'] < $b['name']) ? -1 : 1;
}
return $ak < $bk ? -1 : 1;
});
return $apps;
}
}

View file

@ -15,51 +15,18 @@ use OCP\L10N\IFactory;
*/
class CategoryRepository
{
/**
* @var CategoryFetcher
*/
protected $categoryFetcher;
/**
* @var IFactory
*/
protected $l10nFactory;
/**
* @var ConfigProxy
*/
protected $config;
/**
* @var IConfig
*/
protected $iConfig;
/**
* @var IUserSession
*/
protected $userSession;
public function __construct(
CategoryFetcher $categoryFetcher,
ConfigProxy $config,
IConfig $iConfig,
IFactory $l10nFactory,
IUserSession $userSession
) {
$this->categoryFetcher = $categoryFetcher;
$this->l10nFactory = $l10nFactory;
$this->config = $config;
$this->iConfig = $iConfig;
$this->userSession = $userSession;
}
protected CategoryFetcher $categoryFetcher,
protected ConfigProxy $config,
protected IConfig $iConfig,
protected IFactory $l10nFactory,
protected IUserSession $userSession
) {}
/**
* Retrieves categories.
*
* @return array
*/
public function getOrderedCategories()
public function getOrderedCategories(): array
{
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$type = $this->config->getAppValue('categories-order-type', 'default');
@ -74,7 +41,8 @@ class CategoryRepository
}
foreach ($categoriesLabels as $k => $category) {
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'];
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ??
$category['translations']['en']['name'];
unset($categoriesLabels[$k]);
}

View file

@ -13,12 +13,7 @@ use OCP\IConfig;
*/
class ConfigProxy
{
/**
* @var IConfig
*/
protected $config;
public function __construct(IConfig $config)
public function __construct(protected IConfig $config)
{
$this->config = $config;
}

View file

@ -11,12 +11,7 @@ use OCP\IDBConnection;
*/
class LangRepository
{
/**
* @var IDBConnection
*/
protected $db;
public function __construct(IDBConnection $db)
public function __construct(protected IDBConnection $db)
{
$this->db = $db;
}

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -21,76 +22,25 @@ namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\SideMenu\Service\LangRepository;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N;
use OCP\ILogger;
use OCP\Settings\ISettings;
use OCA\Theming\ThemingDefaults;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\LangRepository;
class Admin implements ISettings
{
/**
* @var IL10N
*/
private $l;
/**
* @var ILogger
*/
private $logger;
/**
* @var ConfigProxy
*/
private $config;
/**
* @var AppRepository
*/
private $appRepository;
/**
* @var CategoryRepository
*/
private $categoryRepository;
/**
* @var ThemingDefaults
*/
protected $theming;
/**
* @var Color
*/
protected $color;
/**
* @var LangRepository
*/
protected $langRepository;
public function __construct(
IL10N $l,
ILogger $logger,
ConfigProxy $config,
AppRepository $appRepository,
CategoryRepository $categoryRepository,
ThemingDefaults $theming,
Color $color,
LangRepository $langRepository
) {
$this->l = $l;
$this->logger = $logger;
$this->config = $config;
$this->appRepository = $appRepository;
$this->categoryRepository = $categoryRepository;
$this->theming = $theming;
$this->color = $color;
$this->langRepository = $langRepository;
}
protected IL10N $l,
protected ConfigProxy $config,
protected AppRepository $appRepository,
protected CategoryRepository $categoryRepository,
protected ThemingDefaults $theming,
protected Color $color,
protected LangRepository $langRepository
) {}
/**
* @return TemplateResponse
@ -125,15 +75,24 @@ class Admin implements ISettings
'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo,
'background-color-opacity' => $this->config->getAppValueInt('background-color-opacity', '100'),
'current-app-background-color' => $this->config->getAppValue('current-app-background-color', $darkenPrimaryColor2),
'current-app-background-color' => $this->config->getAppValue(
'current-app-background-color',
$darkenPrimaryColor2
),
'loader-color' => $this->config->getAppValue('loader-color', $lightenPrimaryColor),
'icon-invert-filter' => $this->config->getAppValueInt('icon-invert-filter', '0'),
'icon-opacity' => $this->config->getAppValueInt('icon-opacity', '100'),
'text-color' => $this->config->getAppValue('text-color', $textColor),
'dark-mode-background-color' => $darkModeBackgroundColor,
'dark-mode-background-color-to' => $darkModeBackgroundColorTo,
'dark-mode-background-color-opacity' => $this->config->getAppValueInt('dark-mode-background-color-opacity', '100'),
'dark-mode-current-app-background-color' => $this->config->getAppValue('dark-mode-current-app-background-color', $darkenPrimaryColor2),
'dark-mode-background-color-opacity' => $this->config->getAppValueInt(
'dark-mode-background-color-opacity',
'100'
),
'dark-mode-current-app-background-color' => $this->config->getAppValue(
'dark-mode-current-app-background-color',
$darkenPrimaryColor2
),
'dark-mode-loader-color' => $this->config->getAppValue('dark-mode-loader-color', $textColor),
'dark-mode-icon-invert-filter' => $this->config->getAppValueInt('dark-mode-icon-invert-filter', '0'),
'dark-mode-icon-opacity' => $this->config->getAppValueInt('dark-mode-icon-opacity', '100'),
@ -159,6 +118,12 @@ class Admin implements ISettings
'force' => $this->config->getAppValue('force', '0'),
'target-blank-apps' => $this->config->getAppValueArray('target-blank-apps', '[]'),
'top-menu-apps' => $this->config->getAppValueArray('top-menu-apps', '[]'),
'top-menu-mouse-over-hidden-label' => $this->config->getAppValue(
'top-menu-mouse-over-hidden-label',
'0'
),
'apps-order' => $this->config->getAppValueArray('apps-order', '[]'),
'ordered-apps' => $this->appRepository->getOrderedApps(),
'top-side-menu-apps' => $this->config->getAppValueArray('top-side-menu-apps', '[]'),
'default-enabled' => $this->config->getAppValue('default-enabled', '1'),
'apps' => $this->appRepository->getVisibleApps(),

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -25,59 +26,26 @@ use OCP\Settings\IIconSection;
class AdminSection implements IIconSection
{
/**
* @var IL10N
*/
private $l;
public function __construct(
protected IURLGenerator $url,
protected IL10N $l
) {}
/**
* @var IURLGenerator
*/
private $url;
public function __construct(IURLGenerator $url, IL10N $l)
{
$this->url = $url;
$this->l = $l;
}
/**
* returns the ID of the section. It is supposed to be a lower case string,
* e.g. 'ldap'.
*
* @returns string
*/
public function getID()
{
return Application::APP_ID;
}
/**
* returns the translated name as it should be displayed, e.g. 'LDAP / AD
* integration'. Use the L10N service to translate it.
*
* @return string
*/
public function getName()
{
return $this->l->t(Application::APP_NAME);
}
/**
* @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
*/
public function getPriority()
{
return 70;
}
/**
* {@inheritdoc}
*/
public function getIcon()
{
return $this->url->imagePath(Application::APP_ID, 'icon.svg');

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -23,45 +24,17 @@ use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IUserSession;
use OCP\Settings\ISettings;
class Personal implements ISettings
{
/**
* @var IL10N
*/
private $l;
/**
* @var ILogger
*/
private $logger;
/**
* @var ConfigProxy
*/
private $config;
/**
* @var IUserSession
*/
private $userSession;
/**
* @var AppRepository
*/
private $appRepository;
public function __construct(IL10N $l, ILogger $logger, ConfigProxy $config, IUserSession $userSession, AppRepository $appRepository)
{
$this->l = $l;
$this->logger = $logger;
$this->config = $config;
$this->userSession = $userSession;
$this->appRepository = $appRepository;
}
public function __construct(
protected IL10N $l,
protected ConfigProxy $config,
protected IUserSession $userSession,
protected AppRepository $appRepository
) {}
/**
* @return TemplateResponse
@ -81,7 +54,9 @@ class Personal implements ISettings
'top-side-menu-apps' => $this->config->getUserValueArray($user, 'top-side-menu-apps', '[]'),
'target-blank-mode' => $this->config->getUserValue($user, 'target-blank-mode', '1'),
'target-blank-apps' => $this->config->getUserValueArray($user, 'target-blank-apps', '[]'),
'apps-order' => $this->config->getUserValueArray($user, 'apps-order', '[]'),
'apps' => $this->appRepository->getVisibleApps(),
'ordered-apps' => $this->appRepository->getOrderedApps($user),
];
return new TemplateResponse(Application::APP_ID, 'settings/personal-form', $parameters, '');

View file

@ -1,4 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -26,34 +27,12 @@ use OCP\Settings\IIconSection;
class PersonalSection implements IIconSection
{
/**
* @var IL10N
*/
private $l;
public function __construct(
protected IURLGenerator $url,
protected IL10N $l,
protected ConfigProxy $configProxy
) {}
/**
* @var IURLGenerator
*/
private $url;
/**
* @var ConfigProxy
*/
private $configProxy;
public function __construct(IURLGenerator $url, IL10N $l, ConfigProxy $configProxy)
{
$this->url = $url;
$this->l = $l;
$this->configProxy = $configProxy;
}
/**
* returns the ID of the section. It is supposed to be a lower case string,
* e.g. 'ldap'.
*
* @returns string
*/
public function getID()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
@ -63,12 +42,6 @@ class PersonalSection implements IIconSection
return Application::APP_ID;
}
/**
* returns the translated name as it should be displayed, e.g. 'LDAP / AD
* integration'. Use the L10N service to translate it.
*
* @return string
*/
public function getName()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
@ -78,13 +51,6 @@ class PersonalSection implements IIconSection
return $this->l->t(Application::APP_NAME);
}
/**
* @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
*/
public function getPriority()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
@ -94,9 +60,6 @@ class PersonalSection implements IIconSection
return 70;
}
/**
* {@inheritdoc}
*/
public function getIcon()
{
return $this->url->imagePath(Application::APP_ID, 'icon.svg');

View file

@ -2,20 +2,24 @@
"license": "agpl",
"private": true,
"scripts": {
"build": "NODE_ENV=production webpack --progress --config webpack.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
"lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix",
"stylelint": "stylelint src",
"stylelint:fix": "stylelint src --fix"
"build": "NODE_ENV=production ./node_modules/.bin/webpack-cli --progress --config webpack.js",
"dev": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --config webpack.js",
"watch": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --watch --config webpack.js",
"lint": "./node_modules/.bin/eslint --ext .js,.vue src",
"lint:fix": "./node_modules/.bin/eslint --ext .js,.vue src --fix",
"stylelint": "./node_modules/.bin/stylelint src",
"stylelint:fix": "./node_modules/.bin/stylelint src --fix"
},
"dependencies": {
"@nextcloud/axios": "^1.8.0",
"@nextcloud/vue": "^1.5.0",
"axios": "^0.24.0",
"trim": "^1.0.1",
"vue": "^2.6.11"
"@nextcloud/axios": "^2.5.1",
"@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/event-bus": "^3.3.1",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^3.1.0",
"@nextcloud/vue": "^8.19.0",
"@vueuse/core": "^11.1.0",
"axios": "^1.6.7",
"trim": "^1.0.1"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
@ -24,37 +28,46 @@
"node": ">=16.0.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.0",
"@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-config": "^8.1.2",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"css-loader": "^3.4.2",
"eslint": "^8.0.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-nextcloud": "^0.3.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^9.0.0",
"eslint-webpack-plugin": "^3.0.0",
"file-loader": "^6.0.0",
"sass": "^1.49.9",
"sass-loader": "^13.0.2",
"stylelint": "^14.0.0",
"stylelint-config-recommended-scss": "^7.0.0",
"stylelint-scss": "^4.0.0",
"stylelint-webpack-plugin": "^3.3.0",
"url-loader": "^4.0.0",
"vue-loader": "^15.9.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2"
"@babel/node": "^7.25.7",
"@babel/plugin-transform-private-methods": "^7.25.7",
"@babel/preset-typescript": "^7.24.7",
"@cypress/vue2": "^2.1.1",
"@cypress/webpack-preprocessor": "^6.0.2",
"@nextcloud/babel-config": "^1.2.0",
"@nextcloud/eslint-config": "^8.4.1",
"@nextcloud/stylelint-config": "^3.0.1",
"@nextcloud/typings": "^1.9.1",
"@nextcloud/webpack-vue-config": "^6.0.1",
"@simplewebauthn/types": "^10.0.0",
"@types/dockerode": "^3.3.29",
"@types/wait-on": "^5.3.4",
"@vue/tsconfig": "^0.5.1",
"babel-loader": "^9.2.1",
"babel-loader-exclude-node-modules-except": "^1.2.1",
"babel-plugin-module-resolver": "^5.0.2",
"colord": "^2.9.3",
"eslint-plugin-cypress": "^3.5.0",
"eslint-plugin-es": "^4.1.0",
"exports-loader": "^5.0.0",
"file-loader": "^6.2.0",
"handlebars-loader": "^1.7.3",
"jasmine-core": "~2.5.2",
"jasmine-sinon": "^0.4.0",
"jsdoc": "^4.0.2",
"raw-loader": "^4.0.2",
"sass": "^1.79.3",
"stylelint": "^16.9.0",
"stylelint-use-logical": "^2.1.2",
"ts-loader": "^9.5.0",
"ts-node": "^10.9.1",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.7.16",
"wait-on": "^8.0.1",
"webpack": "^5.94.0",
"webpack-cli": "^5.0.2",
"webpack-merge": "^6.0.1",
"workbox-webpack-plugin": "^7.1.0"
}
}

3
renovate.json Normal file
View file

@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Before After
Before After

View file

@ -15,168 +15,167 @@ 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/>.
-->
<template>
<div>
<ul class="side-menu-setting-list">
<li v-for="item in values" class="side-menu-setting-list-item" v-on:click="showEditForm(item)">
<span v-text="item.en"></span>
</li>
</ul>
<div>
<ul class="side-menu-setting-list" :class="{hide: values.length === 0}">
<li v-for="item in values" class="side-menu-setting-list-item" v-on:click="showEditForm(item)">
<span v-text="item.en"></span>
</li>
</ul>
<Actions>
<ActionButton @click="showAddForm" icon="icon-add"></ActionButton>
</Actions>
<NcActions>
<NcActionButton @click="showAddForm" icon="icon-add"></NcActionButton>
</NcActions>
<Modal v-if="addForm" @close="hideAddForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-text="lang"></span>
<input type="text" v-model="newValue[lang]" required>
</div>
<NcModal v-if="addForm" @close="hideAddForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-text="lang"></span>
<input type="text" v-model="newValue[lang]" required style="width: calc(100% - 100px)">
</div>
<Actions>
<ActionButton @click="saveAdd" icon="icon-checkmark"></ActionButton>
</Actions>
</div>
</Modal>
<NcActions>
<NcActionButton @click="saveAdd" icon="icon-checkmark"></NcActionButton>
</NcActions>
</div>
</NcModal>
<Modal v-if="editForm" @close="hideEditForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-text="lang"></span>
<input type="text" v-model="editValue[lang]" required>
</div>
<NcModal v-if="editForm" @close="hideEditForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-text="lang"></span>
<input type="text" v-model="editValue[lang]" required style="width: calc(100% - 100px)">
</div>
<div class="pull-right">
<Actions>
<ActionButton @click="removeEdit" icon="icon-delete"></ActionButton>
</Actions>
</div>
<div class="pull-right">
<NcActions>
<NcActionButton @click="removeEdit" icon="icon-delete"></NcActionButton>
</NcActions>
</div>
<Actions>
<ActionButton @click="saveEdit" icon="icon-checkmark"></ActionButton>
</Actions>
</div>
</Modal>
</div>
<NcActions>
<NcActionButton @click="saveEdit" icon="icon-checkmark"></NcActionButton>
</NcActions>
</div>
</NcModal>
</div>
</template>
<style>
.modal__content {
padding: 10px;
}
.modal__content .lang {
width: 60px;
display: inline-block;
padding: 4px;
box-sizing: border-box;
}
.pull-right {
float: right;
}
</style>
<style scoped>
.modal__content {
width: 200px;
padding: 10px;
}
.modal__content .lang {
width: 60px;
display: inline-block;
padding: 4px;
box-sizing: border-box;
}
.modal__content input[type=text] {
width: calc(100% - 85px);
display: inline-block;
margin: 0;
}
.pull-right {
float: right;
}
.hide {
display: none;
}
</style>
<script>
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
export default {
name: 'AdminCategoriesCustom',
components: {
Modal,
Actions,
ActionButton,
},
data() {
return {
input: null,
values: [],
langs: [],
addForm: false,
editForm: false,
newValue: {},
editValue: {},
}
},
methods: {
init() {
this.values = JSON.parse(this.input.value)
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
},
update() {
this.input.value = JSON.stringify(this.values)
},
showAddForm() {
this.newValue = {id: 'cat' + Math.random().toString().replace('0.', '')}
this.addForm = true
},
showEditForm(value) {
this.editValue = {id: value.id}
for (let i of this.langs) {
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
}
this.editForm = true
},
saveAdd() {
for (let i of this.langs) {
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
return
}
}
this.values.push(this.newValue)
this.update()
this.hideAddForm()
this.newValue = {}
},
saveEdit() {
for (let i of this.langs) {
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
return
}
}
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values[i] = this.editValue
}
}
this.update()
this.hideEditForm()
},
removeEdit() {
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values.splice(i, 1);
}
}
this.update()
this.hideEditForm()
},
hideAddForm() {
this.addForm = false
},
hideEditForm() {
this.editForm = false
},
},
mounted() {
this.input = document.querySelector('input[name="categories-custom"]')
this.init()
name: 'AdminCategoriesCustom',
components: {
NcModal,
NcActions,
NcActionButton,
},
data() {
return {
input: null,
values: [],
langs: [],
addForm: false,
editForm: false,
newValue: {},
editValue: {},
}
},
methods: {
init() {
this.values = JSON.parse(this.input.value)
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
},
update() {
this.input.value = JSON.stringify(this.values)
},
showAddForm() {
this.newValue = {id: 'cat' + Math.random().toString().replace('0.', '')}
this.addForm = true
},
showEditForm(value) {
this.editValue = {id: value.id}
for (let i of this.langs) {
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
}
this.editForm = true
},
saveAdd() {
for (let i of this.langs) {
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
return
}
}
this.values.push(this.newValue)
this.update()
this.hideAddForm()
this.newValue = {}
},
saveEdit() {
for (let i of this.langs) {
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
return
}
}
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values[i] = this.editValue
}
}
this.update()
this.hideEditForm()
},
removeEdit() {
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values.splice(i, 1);
}
}
this.update()
this.hideEditForm()
},
hideAddForm() {
this.addForm = false
},
hideEditForm() {
this.editForm = false
},
},
mounted() {
this.input = document.querySelector('input[name="categories-custom"]')
this.init()
}
}
</script>

393
src/AppMenu.vue Normal file
View file

@ -0,0 +1,393 @@
<!--
- @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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/>.
-->
<template>
<nav
class="app-menu show"
:aria-label="t('core', 'Applications menu')"
>
<ul
class="app-menu-main"
:class="{ 'app-menu-main__hidden-label': hiddenLabels === 1, 'app-menu-main__show-hovered': hiddenLabels === 2 }"
v-if="appList.length"
>
<li v-for="app in mainAppList(state)"
:key="app.id"
:data-app-id="app.id"
class="app-menu-entry"
:class="{ 'app-menu-entry__active': app.active, 'app-menu-entry__hidden-label': hiddenLabels === 1, 'app-menu-main__show-hovered': hiddenLabels === 2 }"
:style="makeStyle(app)"
>
<a :href="app.href"
:class="{ 'has-unread': app.unread > 0 }"
:aria-label="appLabel(app)"
:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
:aria-current="app.active ? 'page' : false">
<img :src="app.icon" alt="">
<div class="app-menu-entry--label">
{{ app.name }}
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
</div>
</a>
</li>
</ul>
<NcActions class="app-menu-more" :aria-label="t('core', 'More apps')">
<NcActionLink v-for="app in popoverAppList(state)"
:key="app.id"
:aria-label="appLabel(app)"
:aria-current="app.active ? 'page' : false"
:href="app.href"
:style="makeStyle(app)"
class="app-menu-popover-entry">
<template #icon>
<div class="app-icon" :class="{ 'has-unread': app.unread > 0 }">
<img :src="app.icon" alt="">
</div>
</template>
{{ app.name }}
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
</NcActionLink>
</NcActions>
</nav>
</template>
<script>
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { n, t } from '@nextcloud/l10n'
import { useElementSize } from '@vueuse/core'
import { defineComponent, ref } from 'vue'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
export default defineComponent({
name: 'AppMenu',
components: {
NcActions,
NcActionLink,
},
setup() {
return {
t,
n,
}
},
data() {
return {
apps: null,
appList: [],
observer: null,
targetBlankApps: [],
hiddenLabels: true,
state: 1,
}
},
mounted() {
axios.get(generateOcsUrl('core/navigation', 2) + '/apps?format=json')
.then((response) => response.data)
.then((data) => {
if (data.ocs.meta.statuscode !== 200) {
return
}
this.setApps(data.ocs.data)
})
this.targetBlankApps = window.targetBlankApps
this.hiddenLabels = window.topMenuAppsMouseOverHiddenLabel
let timeout = null
window.addEventListener('resize', () => {
timeout = window.setTimeout(() => {
this.update()
}, 300)
})
},
methods: {
update() {
++this.state
},
mainAppList() {
return this.appList.slice(0, this.appLimit())
},
popoverAppList() {
return this.appList.slice(this.appLimit())
},
appLimit() {
const maxApps = Math.floor(this.$root.$el.offsetWidth / 60)
if (maxApps < this.appList.length) {
// Ensure there is space for the overflow menu
return Math.max(maxApps - 1, 0)
}
return maxApps
},
setNavigationCounter(id, counter) {
const app = this.appList.find(({ app }) => app === id)
if (app) {
this.$set(app, 'unread', counter)
} else {
logger.warn(`Could not find app "${id}" for setting navigation count`)
}
},
setApps(apps) {
this.appList = []
let orders = {}
window.menuAppsOrder.forEach((app, order) => {
orders[app] = order + 1
})
apps.forEach((app) => {
Array.from(window.topMenuApps).forEach((id) => {
if (app.id === id) {
app.order = orders[id] || null
this.appList.push(app)
}
})
})
},
appLabel(app) {
return app.name
+ (app.active ? ' (' + t('core', 'Currently open') + ')' : '')
+ (app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '')
},
makeStyle(app) {
if (app.order !== null) {
return `order: ${app.order}`
}
}
},
})
</script>
<style lang="scss" scoped>
$header-icon-size: 20px;
.app-menu {
width: 100%;
display: flex;
flex-shrink: 1;
flex-wrap: wrap;
}
.app-menu-main {
display: flex;
flex-wrap: nowrap;
.app-menu-entry {
width: 50px;
height: 50px;
position: relative;
display: flex;
&.app-menu-entry__active {
opacity: 1;
&::before {
content: " ";
position: absolute;
pointer-events: none;
border-bottom-color: var(--color-main-background);
transform: translateX(-50%);
width: 12px;
height: 5px;
border-radius: 3px;
background-color: var(--color-primary-text);
left: 50%;
bottom: 6px;
display: block;
transition: all 0.1s ease-in-out;
opacity: 1;
}
.app-menu-entry--label {
font-weight: bold;
}
}
a {
width: calc(100% - 4px);
height: calc(100% - 4px);
margin: 2px;
color: var(--color-primary-text);
position: relative;
}
img {
transition: margin 0.1s ease-in-out;
width: $header-icon-size;
height: $header-icon-size;
padding: calc((100% - $header-icon-size) / 2);
box-sizing: content-box;
filter: var(--background-image-invert-if-bright, var(--primary-invert-if-bright));
}
.app-menu-entry--label {
opacity: 0;
position: absolute;
font-size: 12px;
color: var(--color-primary-text);
text-align: center;
left: 50%;
top: 45%;
display: block;
min-width: 100%;
transform: translateX(-50%);
transition: all 0.1s ease-in-out;
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
letter-spacing: -0.5px;
}
&:not(.app-menu-entry__hidden-label):not(.app-menu-entry__show-hovered):hover,
&:not(.app-menu-entry__hidden-label):not(.app-menu-entry__show-hovered):focus-within {
opacity: 1;
.app-menu-entry--label {
opacity: 1;
font-weight: bolder;
bottom: 0;
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
// Show labels
&:hover,
&:focus-within,
.app-menu-entry:hover,
.app-menu-entry:focus {
opacity: 1;
}
&:not(.app-menu-main__hidden-label):not(.app-menu-main__show-hovered):hover,
&:not(.app-menu-main__hidden-label):not(.app-menu-main__show-hovered):focus-within,
.app-menu-entry:not(.app-menu-entry__hidden-label):hover,
.app-menu-entry:not(.app-menu-entry__hidden-label):focus {
opacity: 1;
img {
margin-top: -8px;
}
.app-menu-entry--label {
opacity: 1;
bottom: 0;
}
&::before, .app-menu-entry::before {
opacity: 0;
}
}
&.app-menu-main__show-hovered .app-menu-entry:hover,
&.app-menu-main__show-hovered .app-menu-entry:focus {
img {
margin-top: -8px;
}
.app-menu-entry--label {
opacity: 1;
bottom: 0;
}
&::before, .app-menu-entry::before {
opacity: 0;
}
}
}
::v-deep .app-menu-more .button-vue--vue-tertiary {
opacity: .7;
margin: 3px;
filter: var(--background-image-invert-if-bright, var(--primary-invert-if-bright));
&:not([aria-expanded="true"]) {
color: var(--color-main-text);
&:hover {
opacity: 1;
background-color: transparent !important;
}
}
&:focus-visible {
opacity: 1;
outline: none !important;
}
}
.app-menu-popover-entry {
.app-icon {
position: relative;
height: 44px;
width: 48px;
display: flex;
align-items: center;
justify-content: center;
filter: var(--background-invert-if-bright, var(--primary-invert-if-bright));
&.has-unread::after {
background-color: var(--color-main-text);
}
img {
width: $header-icon-size;
height: $header-icon-size;
}
}
}
.has-unread::after {
content: "";
width: 8px;
height: 8px;
background-color: var(--color-primary-element-text);
border-radius: 50%;
position: absolute;
display: block;
top: 10px;
right: 10px;
}
.unread-counter {
display: none;
}
</style>

32
src/AppSearch.vue Normal file
View file

@ -0,0 +1,32 @@
<!--
@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/>.
-->
<template>
<div class="side-menu-search">
<input type="text" :value="value" :placeholder="t('side_menu', 'Search')" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
name: 'AppSearch',
props: {
value: {
required: true
},
},
}
</script>

View file

@ -15,11 +15,18 @@ 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/>.
-->
<template>
<button class="side-menu-opener side-menu-closer"></button>
<button class="side-menu-opener side-menu-closer" :arial-label="label">
<span v-text="label"></span>
</button>
</template>
<script>
export default {
name: 'CloserButton',
name: 'CloserButton',
data() {
return {
label: t('side_menu', 'Close the menu'),
}
}
}
</script>

View file

@ -15,28 +15,28 @@ 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/>.
-->
<template>
<div class="side-menu-loader">
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>
<div class="side-menu-loader">
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
name: 'Loader',
name: 'Loader',
}
</script>

View file

@ -15,30 +15,30 @@ 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/>.
-->
<template>
<div v-bind:class="classes">
<a v-if="link !== null" v-bind:href="link">
<img v-bind:src="image" alt="Logo">
</a>
<img v-else v-bind:src="image" alt="Logo">
</div>
<div v-bind:class="classes">
<a v-if="link !== null" v-bind:href="link">
<img v-bind:src="image" alt="Logo">
</a>
<img v-else v-bind:src="image" alt="Logo">
</div>
</template>
<script>
export default {
name: 'Logo',
props: {
image: {
type: String,
required: true
},
link: {
type: String,
required: false
},
classes: {
type: Object,
required: true
},
name: 'Logo',
props: {
image: {
type: String,
required: true
},
link: {
type: String,
required: false
},
classes: {
type: Object,
required: true
},
},
}
</script>

View file

@ -15,11 +15,18 @@ 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/>.
-->
<template>
<button class="side-menu-opener"></button>
<button class="side-menu-opener" :arial-label="label">
<span v-text="label"></span>
</button>
</template>
<script>
export default {
name: 'OpenerButton',
name: 'OpenerButton',
data() {
return {
label: t('side_menu', 'Toggle the menu'),
}
}
}
</script>

20
src/PageLoader.js Normal file
View file

@ -0,0 +1,20 @@
const createElement = require('./lib/createElement')
const PageLoader = () => {
const pageLoader = createElement('div', {id: 'side-menu-loader'})
const pageLoaderBar = createElement('div', {id: 'side-menu-loader-bar'})
pageLoader.appendChild(pageLoaderBar)
document.querySelector('body').appendChild(pageLoader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25)
})
}
module.exports = PageLoader

View file

@ -15,33 +15,35 @@ 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/>.
-->
<template>
<div class="side-menu-settings">
<a v-bind:href="href">
{{ label }}
<div class="side-menu-settings">
<a v-bind:href="href">
<!--
{{ label }}
-->
<span class="avatardiv avatardiv-shown">
<img v-bind:src="avatar" v-bind:alt="name" v-bind:title="name">
</span>
</a>
</div>
<span class="avatardiv avatardiv-shown">
<img v-bind:src="avatar" :alt="label">
</span>
</a>
</div>
</template>
<script>
export default {
name: 'SettingsButton',
props: {
label: {
type: String,
required: true
},
href: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
name: 'SettingsButton',
props: {
label: {
type: String,
required: true
},
href: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
},
}
</script>

View file

@ -16,35 +16,54 @@
*/
import Vue from 'vue'
import AppMenu from './AppMenu.vue'
import SideMenu from './SideMenu.vue'
import SideMenuBig from './SideMenuBig.vue'
import SideMenuWithCategories from './SideMenuWithCategories.vue'
import PageLoader from './PageLoader'
Vue.prototype.OC = OC
Vue.prototype.t = OC.L10N.translate
window.PageLoader = PageLoader
const mountSideMenuComponent = () => {
const sideMenuContainer = document.querySelector('#side-menu')
const container = document.querySelector('#side-menu')
if (sideMenuContainer) {
let component
if (!container) {
return window.setTimeout(mountSideMenuComponent, 50)
}
if (sideMenuContainer.getAttribute('data-bigmenu')) {
component = SideMenuBig
} else if(sideMenuContainer.getAttribute('data-sidewithcategories')) {
component = SideMenuWithCategories
} else {
component = SideMenu
}
const View = Vue.extend(component)
const sideMenu = new View({})
sideMenu.$mount('#side-menu')
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready'))
const component = (() => {
if (container.getAttribute('data-bigmenu')) {
return SideMenuBig
} else if(container.getAttribute('data-sidewithcategories')) {
return SideMenuWithCategories
} else {
window.setTimeout(mountSideMenuComponent, 50)
return SideMenu
}
})()
const View = Vue.extend(component)
const App = new View({})
App.$mount('#side-menu')
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready'))
}
const mountAppMenu = () => {
const container = document.querySelector('#header .app-menu')
if (!container) {
return window.setTimeout(mountAppMenu, 50)
}
const View = Vue.extend(AppMenu)
const App = new View({})
App.$mount('#header .app-menu')
}
mountSideMenuComponent()
mountAppMenu()

View file

@ -15,154 +15,156 @@ 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/>.
-->
<template>
<div id="side-menu">
<div class="side-menu-header">
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar" />
<OpenerButton />
<Logo
v-if="!avatar && logo" v-bind:classes="{'side-menu-logo': true, 'avatardiv': false}"
v-bind:image="logo"
v-bind:link="logoLink"
/>
<Logo
v-if="avatar" v-bind:classes="{'side-menu-logo': true, 'avatardiv': true}"
v-bind:image="avatar"
v-bind:link="logoLink"
/>
</div>
<ul class="side-menu-apps-list">
<SideMenuApp
v-for="app in apps"
v-bind:classes="{'side-menu-app': true, 'active': app.active}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul>
<div id="side-menu">
<div class="side-menu-header" v-if="settings || !openerHover || (!avatar && !alwaysDisplayed && logo) || avatar">
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar" />
<AppSearch v-model:search="search" />
<OpenerButton v-if="!alwaysDisplayed" />
<Logo
v-if="!avatar && !alwaysDisplayed && logo" v-bind:classes="{'side-menu-logo': true, 'avatardiv': false}"
v-bind:image="logo"
v-bind:link="logoLink"
/>
<Logo
v-if="avatar" v-bind:classes="{'side-menu-logo': true, 'avatardiv': true}"
v-bind:image="avatar"
v-bind:link="logoLink"
/>
</div>
<ul class="side-menu-apps-list" :class="{'side-menu-apps-list--with-settings': !!settings}">
<SideMenuApp
v-for="(app, key) in apps"
v-if="searchMatch(app.name)"
v-bind:classes="{'side-menu-app': true, 'active': app.active}"
v-bind:key="key"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</template>
<script>
import trim from 'trim'
import axios from 'axios'
import OpenerButton from './OpenerButton'
import SettingsButton from './SettingsButton'
import SideMenuApp from './SideMenuApp'
import AppSearch from './AppSearch'
import Logo from './Logo'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'SideMenu',
components: {
SettingsButton,
OpenerButton,
SideMenuApp,
Logo,
},
data() {
return {
apps: [],
logo: null,
logoLink: null,
avatar: null,
forceLightIcon: false,
targetBlankApps: [],
settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
const links = document.querySelectorAll('#appmenu a')
const menu = document.querySelector('#appmenu')
let menuIsHidden = true
if (menu) {
menuIsHidden = window.getComputedStyle(menu, null).getPropertyValue('display') === 'none'
}
for (let element of links) {
let href = element.getAttribute('href')
let parent = element.parentNode
if (!parent) {
continue
}
let dataId = parent.getAttribute('data-id')
dataId = dataId !== null ? dataId : ''
if (!parent.classList.contains('app-top-side-menu') && !parent.classList.contains('app-hidden') && !menuIsHidden) {
continue
}
if (href !== '#') {
let svg = element.querySelector('svg').outerHTML
svg = svg
.replace(/(height|width)="20"/, '')
.replace('id="invertMenuMain', 'id="invertSideMenu')
.replace('url(#invertMenuMain', 'url(#invertSideMenu')
if (this.forceLightIcon) {
svg = svg.replace(/filter="url[^"]+"/, '')
}
this.apps.push({
id: dataId,
href: href,
name: trim(element.querySelector('span').innerHTML),
icon: svg,
active: element.classList.contains('active')
})
}
}
(function(apps) {
window.setTimeout(function() {
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
}, 1000)
})(this.apps)
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.forceLightIcon = config['force-light-icon']
that.avatar = config['avatar']
that.logo = config['logo']
that.logoLink = config['logo-link']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
const menu = document.querySelector('#appmenu')
if (menu) {
const config = {attributes: true, childList: true, subtree: true}
const observer = new MutationObserver(this.retrieveApps)
observer.observe(menu, config)
}
name: 'SideMenu',
components: {
SettingsButton,
OpenerButton,
SideMenuApp,
Logo,
AppSearch,
},
data() {
return {
apps: [],
logo: null,
logoLink: null,
avatar: null,
forceLightIcon: false,
targetBlankApps: [],
hiddenApps: [],
settings: null,
openerHover: false,
alwaysDisplayed: false,
search: '',
}
},
methods: {
retrieveApps() {
const ncApps = loadState('core', 'apps', [])
let orders = {}
let finalApps = []
window.menuAppsOrder.forEach((app, order) => {
orders[app] = order + 1
})
for (let app of ncApps) {
if (window.topMenuApps.includes(app.id) && !window.topSideMenuApps.includes(app.id)) {
continue
}
if (this.hiddenApps.includes(app.id)) {
continue
}
app.order = orders[app.id] || null
finalApps.push(app)
}
finalApps.sort((a, b) => {
if (a.order === null || b.order === null) {
return a.name < b.name ? -1 : 1
}
return a.order < b.order ? -1 : 1
})
this.apps = finalApps
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: this.apps},
}))
},
retrieveConfig() {
},
hasSearchMatch(apps) {
if (this.search.trim() === '') {
return true
}
for (let key in apps) {
if (this.searchMatch(apps[key].name)) {
return true
}
}
return false
},
searchMatch(name) {
if (this.search.trim() === '') {
return true
}
return name.toLowerCase().includes(this.search.toLowerCase())
},
},
mounted() {
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then((response) => {
const config = response.data
this.targetBlankApps = config['target-blank-apps']
this.forceLightIcon = config['force-light-icon']
this.avatar = config['avatar']
this.logo = config['logo']
this.logoLink = config['logo-link']
this.settings = config['settings']
this.openerHover = config['opener-hover']
this.alwaysDisplayed = config['always-displayed']
this.hiddenApps = config['big-menu-hidden-apps']
this.retrieveApps()
})
}
}
</script>

View file

@ -15,38 +15,38 @@ 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/>.
-->
<template>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<span class="side-menu-app-icon" v-html="icon"></span>
<span class="side-menu-app-text" v-text="label"></span>
</a>
</li>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" />
<span class="side-menu-app-text" v-text="label"></span>
</a>
</li>
</template>
<script>
export default {
name: 'SideMenuApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
name: 'SideMenuApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
}
</script>

View file

@ -15,41 +15,42 @@ 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/>.
-->
<template>
<div id="side-menu" class="side-menu-big">
<div class="side-menu-header">
<CloserButton />
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div>
<div id="side-menu" class="side-menu-big">
<div class="side-menu-header">
<CloserButton />
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<AppSearch v-model:search="search" />
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="(category, key) in items" v-if="hasSearchMatch(category.apps)" v-bind:key="key">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-if="searchMatch(app.name)"
v-bind:key="appId"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
@ -58,72 +59,97 @@ import OpenerButton from './OpenerButton'
import CloserButton from './CloserButton'
import SettingsButton from './SettingsButton'
import Loader from './Loader'
import AppSearch from './AppSearch'
import SideMenuBigApp from './SideMenuBigApp'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'SideMenuBig',
components: {
SettingsButton,
OpenerButton,
CloserButton,
Loader,
SideMenuBigApp,
},
data() {
return {
items: [],
activeApp: null,
targetBlank: false,
targetBlankApps: [],
settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
name: 'SideMenuBig',
components: {
SettingsButton,
OpenerButton,
CloserButton,
Loader,
SideMenuBigApp,
AppSearch,
},
data() {
return {
items: [],
activeApp: null,
targetBlank: false,
targetBlankApps: [],
settings: null,
search: '',
}
},
methods: {
retrieveApps() {
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then((response) => {
this.items = response.data.items
let apps = []
for (let category of this.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
const ncApps = loadState('core', 'apps', {})
for (let id in ncApps) {
if (ncApps[id].active) {
this.activeApp = id
}
}
},
retrieveConfig() {
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then((response) => {
const config = response.data
this.targetBlankApps = config['target-blank-apps']
this.settings = config['settings']
})
},
hasSearchMatch(apps) {
if (this.search.trim() === '') {
return true
}
for (let key in apps) {
if (this.searchMatch(apps[key].name)) {
return true
}
}
return false
},
searchMatch(name) {
if (this.search.trim() === '') {
return true
}
return name.toLowerCase().includes(this.search.toLowerCase())
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
}
}
</script>

View file

@ -15,38 +15,38 @@ 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/>.
-->
<template>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" />
<span class="side-menu-app-text" v-text="label"></span>
</a>
</li>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" />
<span class="side-menu-app-text" v-text="label"></span>
</a>
</li>
</template>
<script>
export default {
name: 'SideMenuBigApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
name: 'SideMenuBigApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
}
</script>

View file

@ -15,113 +15,138 @@ 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/>.
-->
<template>
<div id="side-menu" class="side-menu-with-categories">
<div class="side-menu-header">
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div>
<div id="side-menu" class="side-menu-with-categories">
<div class="side-menu-header">
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<AppSearch v-model:search="search" />
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="(category, key) in items" v-if="hasSearchMatch(category.apps)" v-bind:key="key">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-if="searchMatch(app.name)"
v-bind:key="appId"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import OpenerButton from './OpenerButton'
import CloserButton from './CloserButton'
import SettingsButton from './SettingsButton'
import Loader from './Loader'
import AppSearch from './AppSearch'
import SideMenuBigApp from './SideMenuBigApp'
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'SideMenuWithCategories',
components: {
SettingsButton,
OpenerButton,
CloserButton,
Loader,
SideMenuBigApp,
},
data() {
return {
items: [],
activeApp: null,
targetBlank: false,
targetBlankApps: [],
settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
name: 'SideMenuWithCategories',
components: {
SettingsButton,
OpenerButton,
Loader,
SideMenuBigApp,
AppSearch,
},
data() {
return {
items: [],
activeApp: null,
targetBlank: false,
targetBlankApps: [],
settings: null,
search: '',
}
},
methods: {
retrieveApps() {
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then((response) => {
this.items = response.data.items
let apps = []
for (let category of this.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
const ncApps = loadState('core', 'apps', {})
for (let id in ncApps) {
if (ncApps[id].active) {
this.activeApp = id
}
}
},
retrieveConfig() {
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then((response) => {
const config = response.data
this.targetBlankApps = config['target-blank-apps']
this.settings = config['settings']
})
},
hasSearchMatch(apps) {
if (this.search.trim() === '') {
return true
}
for (let key in apps) {
if (this.searchMatch(apps[key].name)) {
return true
}
}
return false
},
searchMatch(name) {
if (this.search.trim() === '') {
return true
}
return name.toLowerCase().includes(this.search.toLowerCase())
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
}
}
</script>

View file

@ -26,235 +26,257 @@ let elements = []
const selector = '#side-menu-message'
const userConfig = (name, value, callbacks) => {
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
const formData = []
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
const formData = []
formData.push('name=' + encodeURIComponent(name))
formData.push('value=' + encodeURIComponent(value))
formData.push('name=' + encodeURIComponent(name))
formData.push('value=' + encodeURIComponent(value))
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.join('&')
})
.then(callbacks.success)
.catch(callbacks.error)
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.join('&')
})
.then(callbacks.success)
.catch(callbacks.error)
}
const appConfig = (name, value, callbacks) => {
OCP.AppConfig.setValue('side_menu', name, value, callbacks)
OCP.AppConfig.setValue('side_menu', name, value, callbacks)
}
const saveSettings = (key) => {
const element = elements[key]
const element = elements[key]
if (!element) {
return
if (!element) {
return
}
let value
let name
if (element.hasAttribute('data-checkbox')) {
name = element.getAttribute('data-name')
value = []
const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
for (let input of inputs) {
value.push(input.value)
}
let value
let name
value = JSON.stringify(value)
} else {
name = element.getAttribute('name')
value = element.value
}
if (element.hasAttribute('data-checkbox')) {
name = element.getAttribute('data-name')
value = []
const size = elements.length
const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
if (name === 'cache') {
++value
}
for (let input of inputs) {
value.push(input.value)
}
const progress = document.querySelector('#side-menu-save-progress')
value = JSON.stringify(value)
} else {
name = element.getAttribute('name')
value = element.value
progress.style.width = '40px';
progress.style.marginLeft = '5px';
const callbacks = {
success: () => {
const percent = parseInt((key + 1) * 100 / size);
progress.setAttribute('value', percent)
if (key < size - 1) {
saveSettings(key + 1)
} else {
location.reload()
}
},
error: () => {
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
}
}
const size = elements.length
if (name === 'cache') {
++value
}
const progress = document.querySelector('#side-menu-save-progress')
progress.style.width = '40px';
progress.style.marginLeft = '5px';
const callbacks = {
success: () => {
const percent = parseInt((key + 1) * 100 / size);
progress.setAttribute('value', percent)
if (key < size - 1) {
saveSettings(key + 1)
} else {
location.reload()
}
},
error: () => {
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
}
}
if (element.hasAttribute('data-personal')) {
userConfig(name, value, callbacks)
} else {
appConfig(name, value, callbacks)
}
if (element.hasAttribute('data-personal')) {
userConfig(name, value, callbacks)
} else {
appConfig(name, value, callbacks)
}
}
const elementToggler = (element) => {
let display = 'none'
let display = 'none'
if (window.getComputedStyle(element).display === 'none') {
display = 'block'
}
if (window.getComputedStyle(element).display === 'none') {
display = 'block'
}
element.style.display = display
element.style.display = display
}
const updateAppsCategoriesCustom = () => {
let values = {}
let values = {}
for (let item of document.querySelectorAll('.apps-categories-custom')) {
let app = item.getAttribute('data-app')
let value = item.value
for (let item of document.querySelectorAll('.apps-categories-custom')) {
let app = item.getAttribute('data-app')
let value = item.value
if (value) {
values[app] = value
}
if (value) {
values[app] = value
}
}
document.querySelector('#apps-categories-custom').value = JSON.stringify(values)
document.querySelector('#apps-categories-custom').value = JSON.stringify(values)
}
document.addEventListener('DOMContentLoaded', () => {
$('*[data-toggle="tooltip"]').tooltip();
$('*[data-toggle="tooltip"]').tooltip();
if (document.querySelector('#side-menu-categories-custom')) {
const View = Vue.extend(AdminCategoriesCustom)
const adminCategoriesCustom = new View({})
if (document.querySelector('#side-menu-categories-custom')) {
const View = Vue.extend(AdminCategoriesCustom)
const adminCategoriesCustom = new View({})
adminCategoriesCustom.$mount('#side-menu-categories-custom')
}
adminCategoriesCustom.$mount('#side-menu-categories-custom')
}
elements = document.querySelectorAll('.side-menu-setting')
elements = document.querySelectorAll('.side-menu-setting')
document.querySelector('#side-menu-save').addEventListener('click', (event) => {
event.preventDefault()
OC.msg.startSaving(selector)
document.querySelector('#side-menu-save').addEventListener('click', (event) => {
event.preventDefault()
OC.msg.startSaving(selector)
saveSettings(0)
saveSettings(0)
})
const resets = document.querySelectorAll('.btn-reset')
for (let btn of resets) {
btn.addEventListener('click', (event) => {
const target = event.target
const values = JSON.parse(target.getAttribute('data-reset'))
target.classList.toggle('btn-reset--progress', true)
for (let i in values) {
document.querySelector(`#${i}`).value = values[i]
}
window.setTimeout(() => {
target.classList.toggle('btn-reset--progress', false)
}, 800)
})
}
const resets = document.querySelectorAll('.btn-reset')
const displays = document.querySelectorAll('.side-menu-display')
for (let btn of resets) {
btn.addEventListener('click', (event) => {
const target = event.target
const values = JSON.parse(target.getAttribute('data-reset'))
for (let display of displays) {
display.addEventListener('click', (event) => {
const target = event.target
for (let i in values) {
document.querySelector(`#${i}`).value = values[i]
}
})
}
for (let d of displays) {
d.classList.toggle('is-active', d === display)
}
const displays = document.querySelectorAll('.side-menu-display')
for (let display of displays) {
display.addEventListener('click', (event) => {
const target = event.target
for (let d of displays) {
d.classList.toggle('is-active', d === display)
}
document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed')
document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories')
})
}
for (let item of document.querySelectorAll('.apps-categories-custom')) {
item.addEventListener('change', (event) => {
updateAppsCategoriesCustom()
})
}
for (let item of document.querySelectorAll('.side-menu-setting-live')) {
item.addEventListener('change', (event) => {
const target = event.target
const name = target.getAttribute('name')
let value = target.value
let id = null
if (name === 'background-color-opacity') {
id = '#side-menu-background-color, #side-menu-background-color-to'
} else if (name === 'dark-mode-background-color-opacity') {
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
}
if (id) {
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
return
}
if (name === 'opener') {
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
value = `url(${url})`
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
value/=100
}
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
}
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
toggler.addEventListener('click', (event) => {
const target = event.target
const element = document.querySelector(target.getAttribute('data-target'))
elementToggler(element)
})
}
sortable('#categories-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed')
document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories')
})
}
try {
sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
let value = []
for (let item of document.querySelectorAll('.apps-categories-custom')) {
item.addEventListener('change', (event) => {
updateAppsCategoriesCustom()
})
}
for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
console.log(item.getAttribute('data-id'))
value.push(item.getAttribute('data-id'))
}
for (let item of document.querySelectorAll('.side-menu-setting-live')) {
item.addEventListener('change', (event) => {
const target = event.target
const name = target.getAttribute('name')
document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
})
} catch (e) {
}
let value = target.value
let id = null
if (name === 'background-color-opacity') {
id = '#side-menu-background-color, #side-menu-background-color-to'
} else if (name === 'dark-mode-background-color-opacity') {
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
}
if (id) {
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
return
}
if (name === 'opener') {
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
value = `url(${url})`
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
value/=100
}
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
}
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
toggler.addEventListener('click', (event) => {
const target = event.target
const element = document.querySelector(target.getAttribute('data-target'))
elementToggler(element)
})
}
sortable('#categories-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
})
try {
sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
let value = []
for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
value.push(item.getAttribute('data-id'))
}
document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
})
} catch (e) {
}
sortable('#apps-order-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
})
try {
sortable('#apps-order-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
let value = []
for (let item of document.querySelectorAll('#apps-order-list .side-menu-setting-list-item')) {
value.push(item.getAttribute('data-id'))
}
document.querySelector('input[name="apps-order"]').value = JSON.stringify(value)
})
} catch (e) {
}
})

View file

@ -3,7 +3,8 @@
"No": "Ne"
"Yes": "Ano"
"Menu": "Nabídka"
'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.': 'Pro otevření/skrytí postranní nabídky použijte zkratku <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">O</span> („O“ jako otevřít). Pro pohyb po použijte klávesu <span class="keyboard-key">Tab</span>.'
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Pro otevření/skrytí postranní nabídky použijte zkratku <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">O</span> („O“ jako otevřít). Pro pohyb po použijte klávesu <span class="keyboard-key">Tab</span>.'
"Top menu": "Horní nabídka"
"Apps that not must be moved in the side menu": "Aplikace, které nepřesouvat do postranní nabídky"
"If there is no selection then the global configuration is applied.": "Pokud neexistuje žádný výběr, je uplatněno globální nastavení."
@ -38,16 +39,13 @@
"Show only the opener (hidden logo)": "Zobrazovat pouze otevírací tlačítko (logo skryto)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Nezobrazovat postranní nabídku a její otevírací tlačítko pokud nejsou dostupné žádné aplikace (např. na veřejných stránkách)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Otevřít nabídku při najetím ukazatelem na tlačítko nabídky (automaticky vypnuto pro dotykové obrazovky)."
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Otevřít nabídku při najetím ukazatelem na tlačítko nabídky (automaticky vypnuto pro dotykové obrazovky)"
"Display the big menu": "Zobrazit velkou nabídku"
"This menu is not compatible with AppOrder.": "Nabídka není kompatibilní s jinou aplikací (doplňkem) „Pořadí aplikací“."
"Display the logo": "Zobrazit logo"
"This feature is not compatible with the <code>big menu</code> display.": "Tato funkce není kompatibilní se zobrazením <code>velké nabídky</code>."
"Icons and texts": "Ikony a texty"
"Loader enabled": "Načítání zapnuto"
"Tips": "Tipy"
"Always displayed": "Vždy zobrazeno"
"The logo will be hidden when the menu is always displayed.": "Pokud je nabídka zobrazena trvale, logo bude skryto."
"This is the automatic behavior when the menu is always displayed.": "Toto je automatické chování, kdy je nabídka vždy zobrazena."
"Not compatible with touch screens.": "Nekompatibilní s dotykovými obrazovkami."
"Big menu": "Velká nabídka"
@ -65,7 +63,7 @@
"The menu is enabled by default for users": "Nabídka je ve výchozím stavu pro uživatele zapnutá"
"Except when the configuration is forced.": "S výjimkou, kdy je nastavení vynuceno."
"Apps that should not be displayed in the menu": "Aplikace, které by neměly být v nabídce zobrazeny"
"This feature is only compatible with the <code>big menu</code> display.": "Tato funkce je kompatibilní pouze s <code>velkou nabídkou</ code>."
"This feature is only compatible with the <code>big menu</code> display.": "Tato funkce je kompatibilní pouze s <code>velkou nabídkou</code>."
"The logo is a link to the default app": "Logo je odkaz na výchozí aplikaci"
"Others": "Ostatní"
"Categories": "Kategorie"
@ -79,8 +77,6 @@
"With categories": "S kategoriemi"
"Custom categories": "Vlastní kategorie"
"Customize application categories": "Přizpůsobte kategorie aplikací"
"Apps only visible in the top menu": "Aplikace jsou viditelné pouze v horní nabídce "
"Apps visible in the top and side menus": "Aplikace viditelné v horní a boční nabídce"
"Reset to default": "Vrátit zpět na výchozí hodnoty"
"Hidden icon": "Skrytá ikona"
"Small icon": "Malá ikona"
@ -90,3 +86,11 @@
"Small text": "Malý text"
"Normal text": "Normální text"
"Big text": "Velký text"
"Applications": "Aplikace"
"Applications kept in the top menu": "Aplikace ponechané v horní nabídce"
"Applications kept in the top menu but also shown in side menu": "Aplikace ponechané v horní nabídce ale také zobrazené v té boční"
"These applications must be selected in the previous option.": "Tyto aplikace je třeba vybrat v předchozí volbě."
"Hide labels on mouse over": "Skrýt popisky při najetím ukazatele myši"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

View file

@ -3,13 +3,14 @@
"No": "Nein"
"Yes": "Ja"
"Menu": "Menü"
'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.': 'Verwende die Tastenkombination <span class="keyboard-key">Strg</span>+<span class="keyboard-key">o</span>, um das Seitenmenü ein- und auszublenden. Verwende <span class="keyboard-key">tab</span> zum Navigieren.'
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Verwende die Tastenkombination <span class="keyboard-key">Strg</span>+<span class="keyboard-key">o</span>, um das Seitenmenü ein- und auszublenden. Verwende <span class="keyboard-key">tab</span> zum Navigieren.'
"Top menu": "Obere Navigationsleiste"
"Apps that not must be moved in the side menu": "Anwendungen, die nicht ins Seitenmenü verschoben werden sollen"
"Apps that not must be moved in the side menu": "Apps, die nicht ins Seitenmenü verschoben werden sollen"
"If there is no selection then the global configuration is applied.": "Wenn keine Auswahl vorhanden ist, wird die globale Konfiguration angewendet."
"Experimental": "Experimentell"
"Save": "Speichern"
"You like this app and you want to support me?": "Du magst diese Anwendung und möchtest mich unterstützen?"
"You like this app and you want to support me?": "Du magst diese App und möchtest mich unterstützen?"
"Buy me a coffee ☕": "Gib mir einen Kaffee aus ☕"
"Hidden": "Ausblenden"
"Small": "Klein"
@ -17,9 +18,9 @@
"Big": "Groß"
"Colors": "Farben"
"Background color": "Hintergrundfarbe"
"Background color of current app": "Hintergrundfarbe der aktuellen Anwendung"
"Background color of current app": "Hintergrundfarbe der aktuellen App"
"Text color": "Textfarbe"
"Loader": "Ladestandanzeige"
"Loader": "Fortschrittsbalken"
"Icon": "Symbol"
"Same color": "Selbe Farbe"
"Opposite color": "Gegenfarbe"
@ -36,35 +37,32 @@
"After the logo": "Nach dem Logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Nur das Menü-Symbol anzeigen (Logo wird ausgeblendet)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Zeige das Seitenmenü und das Menü-Symbol nicht an, wenn keine Anwendung vorhanden ist (z.B. bei öffentlichen Seiten)."
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Zeige das Seitenmenü und das Menü-Symbol nicht an, wenn keine App vorhanden ist (z.B. bei öffentlichen Seiten)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Öffne das Menü, wenn die Maus über das Menü-Symbol bewegt wird (auf Touchscreens automatisch deaktiviert)."
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Öffne das Menü, wenn die Maus über das Menü-Symbol bewegt wird (auf Touchscreens automatisch deaktiviert)"
"Display the big menu": "Großes Menü anzeigen"
"This menu is not compatible with AppOrder.": "Dieses Menü ist nicht mit <code>AppOrder</code> kompatibel."
"Display the logo": "Logo anzeigen"
"This feature is not compatible with the <code>big menu</code> display.": "Diese Funktion ist nicht mit dem <code>großen Menü</code> kompatibel."
"Icons and texts": "Symbole und Texte"
"Loader enabled": "Ladestandanzeige aktiviert"
"Loader enabled": "Fortschrittsbalken anzeigen"
"Tips": "Tipps"
"Always displayed": "Immer anzeigen"
"The logo will be hidden when the menu is always displayed.": "Das Logo wird ausgeblendet, wenn das Menü immer angezeigt wird."
"This is the automatic behavior when the menu is always displayed.": "Dies ist das automatische Verhalten, wenn das Menü immer angezeigt wird."
"Not compatible with touch screens.": "Nicht kompatibel mit Touchscreens."
"Big menu": "Großes Menü"
"Live preview": "Live-Vorschau"
"Open apps in new tab": "Öffne Anwendungen in einem neuen Tab"
"Open apps in new tab": "Öffne Apps in einem neuen Tab"
"Use the global setting": "Verwende die globale Einstellung"
"Use my selection": "Verwende meine Auswahl"
"Show and hide the list of applications": "Ein- und Ausblenden der Anwendungsliste"
"Show and hide the list of applications": "Ein- und Ausblenden der Appliste"
"Use the avatar instead of the logo": "Avatar anstelle des Logos anzeigen"
"You do not have permission to change the settings.": "Du hast keine Berechtigung, die Einstellungen dieser Anwendung zu ändern."
"You do not have permission to change the settings.": "Du hast keine Berechtigung, die Einstellungen dieser App zu ändern."
"Force this configuration to users": "Konfiguration für alle Benutzer erzwingen"
"Export the configuration": "Konfiguration exportieren"
"Purge the cache": "Cache leeren"
"Show the link to settings": "Link zu den Einstellungen anzeigen"
"The menu is enabled by default for users": "Das Menü ist standardmäßig für alle Benutzer aktiviert"
"Except when the configuration is forced.": "Gilt nicht, wenn die Konfiguration erzwungen wird."
"Apps that should not be displayed in the menu": "Anwendungen, die nicht im Menü angezeigt werden sollen"
"Apps that should not be displayed in the menu": "Apps, die nicht im Menü angezeigt werden sollen"
"This feature is only compatible with the <code>big menu</code> display.": "Kompatibel mit dem <code>großen Menü</code>."
"The logo is a link to the default app": "Das Logo ist ein Link zur Standard-App"
"Others": "Andere"
@ -78,15 +76,21 @@
"Dark mode colors": "Farben für den dunklen Modus"
"With categories": "Mit Kategorien"
"Custom categories": "Benutzerdefinierte Kategorien"
"Customize application categories": "Anwendungskategorien anpassen"
"Apps only visible in the top menu": "Apps nur im oberen Menü sichtbar "
"Apps visible in the top and side menus": "Apps im oberen und seitlichen Menü sichtbar"
"Customize application categories": "App-Kategorien anpassen"
"Reset to default": "Auf Standard zurücksetzen"
"Hidden icon": "Verstecktes Symbol"
"Small icon": "Kleines Symbol"
"Normal icon": "Normales Symbol"
"Big icon": "Große Ikone"
"Big icon": "Großes Icon"
"Hidden text": "Versteckter Text"
"Small text": "Kleiner Text"
"Normal text": "Normaler Text"
"Big text": "Großer Text"
"Applications": "Apps"
"Applications kept in the top menu": "Apps in der oberen Navigationsleiste"
"Applications kept in the top menu but also shown in side menu": "Apps in der oberen Navigationsleiste, die auch im Seitenmenü angezeigt werden sollen"
"These applications must be selected in the previous option.": "Diese Apps müssen auch in der vorherigen Einstellung ausgewählt werden."
"Hide labels on mouse over": "Labels ausblenden, wenn sich die Maus darüber befindet (Hover)"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

96
src/l10n/fixtures/es.yaml Normal file
View file

@ -0,0 +1,96 @@
"Custom menu": "Menú personalizado"
"Enable the custom menu": "Habilitar el menú personalizado"
"No": "No"
"Yes": "Sí"
"Menu": "Menú"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Usa la combinación de teclas <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> para activar y desactivar el menú lateral. Use <span class="keyboard-key">tab</span> para navegar.'
"Top menu": "Menu principal"
"Apps that not must be moved in the side menu": "Aplicaciones que no se deben mover al menú lateral"
"If there is no selection then the global configuration is applied.": "Si no hay selección, se aplica la configuración global."
"Experimental": "En pruebas"
"Save": "Guardar"
"You like this app and you want to support me?": "¿Te gusta esta aplicación y quieres apoyarme?"
"Buy me a coffee ☕": "Cómprame un café ☕"
"Hidden": "Oculto"
"Small": "Pequeño"
"Normal": "Normal"
"Big": "Grande"
"Hidden icon": "Ocultar Icono"
"Small icon": "Icono pequeño"
"Normal icon": "Icono normal"
"Big icon": "Icono grande"
"Hidden text": "Texto oculto"
"Small text": "Texto pequeño"
"Normal text": "Texto normal"
"Big text": "Texto grande"
"Colors": "Colores"
"Background color": "Color de fondo"
"Background color of current app": "Color de fondo de la aplicación actual"
"Text color": "Color del texto"
"Loader": "Cargador"
"Icon": "Icono"
"Same color": "El mismo color"
"Opposite color": "Color opuesto"
"Transparent": "Transparente"
"Opaque": "Opaco"
"Opener": "Abrir"
"Default": "Por defecto"
"Default (dark)": "Por defecto (oscuro)"
"Hamburger": "Hamburguesa"
"Hamburger (dark)": "Hamburger (negro)"
"Hamburger 2": "Hamburguesa 2"
"Hamburger 2 (dark)": "Hamburger 2 (negro)"
"Before the logo": "Antes del logotipo"
"After the logo": "Después del logotipo"
"Position": "Posición"
"Show only the opener (hidden logo)": "Mostrar solo abrir (ocultar logotipo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "No mostrar el menú lateral y el abridor si no hay aplicación (por ejemplo: páginas públicas)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Abra el menú cuando el ratón esté sobre el icono (se desactiva automáticamente en las pantallas táctiles)"
"Display the big menu": "Mostrar el menú grande"
"Display the logo": "Mostrar el logotipo"
"Icons and texts": "Iconos y textos"
"Loader enabled": "Cargador activado"
"Tips": "Consejos"
"Always displayed": "Siempre se muestra"
"This is the automatic behavior when the menu is always displayed.": "Este es el comportamiento automático cuando aún se muestra el menú."
"Not compatible with touch screens.": "No es compatible con las pantallas táctiles."
"Big menu": "Menú grande"
"Live preview": "Previsualización en directo"
"Open apps in new tab": "Abrir las aplicaciones en una nueva pestaña"
"Use the global setting": "Utilizar la configuración global"
"Use my selection": "Utilizar mi selección"
"Show and hide the list of applications": "Mostrar y ocultar la lista de aplicaciones"
"Use the avatar instead of the logo": "Utilizar un avatar en lugar de un logotipo"
"You do not have permission to change the settings.": "No tienes permiso para cambiar la configuración."
"Force this configuration to users": "Forzar esta configuración a todos los usuarios"
"Export the configuration": "Exportar la configuración"
"Purge the cache": "Vaciar la caché"
"Show the link to settings": "Mostrar un enlace a la configuración"
"The menu is enabled by default for users": "El menú está activado por defecto para los usuarios"
"Except when the configuration is forced.": "Excepto cuando la configuración es forzada."
"Apps that should not be displayed in the menu": "Aplicaciones que no deben aparecer en el menú"
"This feature is only compatible with the <code>big menu</code> display.": "Esta función sólo es compatible con la pantalla del <code>menú grande</code>."
"The logo is a link to the default app": "El logotipo es un enlace a la aplicación por defecto"
"Others": "Otros"
"Categories": "Categorías"
"Customize sorting": "Personalizar la clasificación"
"Order by": "Ordenar por"
"Name": "Nombre"
"Customed": "Personalizado"
"Show and hide the list of categories": "Mostrar y ocultar la lista de categorías"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Estos parámetros se utilizan cuando el tema oscuro o el tema oscuro de Breeze están activados."
"Dark mode colors": "Colores del modo oscuro"
"With categories": "Con categorías"
"Custom categories": "Categorías personalizadas"
"Customize application categories": "Personalizar las categorías de las aplicaciones"
"Reset to default": "Restablecer los valores por defecto"
"Applications": "Aplicaciones"
"Applications kept in the top menu": "Aplicaciones guardadas en el menú superior"
"Applications kept in the top menu but also shown in side menu": "Las aplicaciones se mantienen en el menú superior pero también se muestran en el menú lateral"
"These applications must be selected in the previous option.": "Estas aplicaciones deben ser seleccionadas en las opciones anteriores."
"Hide labels on mouse over": "Ocultar las etiquetas al pasar el ratón"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

View file

@ -3,10 +3,11 @@
"No": "Non"
"Yes": "Oui"
"Menu": "Menu"
'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.': 'Utiliser le raccourcis clavier <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> pour ouvrir et fermer le menu latéral. Utiliser <span class="keyboard-key">tab</span> pour naviguer.'
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Utiliser le raccourcis clavier <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> pour ouvrir et fermer le menu latéral. Utiliser <span class="keyboard-key">tab</span> pour naviguer.'
"Top menu": "Menu supérieur"
"Apps that not must be moved in the side menu": "Les applications qui ne doivent pas être affichées dans le menu latéral"
"If there is no selection then the global configuration is applied.": "Si il n'y a aucune sélection alors la configuration globale sera appliquée"
"If there is no selection then the global configuration is applied.": "Si il n'y a aucune sélection alors la configuration globale sera appliquée."
"Experimental": "Expérimental"
"Save": "Sauvegarder"
"You like this app and you want to support me?": "Vous aimer cette application et vous souhaitez m'aider ?"
@ -48,14 +49,11 @@
"Panel": "Panneau"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Ouvrir le menu au passage de la souris (automatiquement désactivé sur les écrans tactiles)"
"Display the big menu": "Afficher le menu large"
"This menu is not compatible with AppOrder.": "Ce menu n'est pas compatible avec l'application AppOrder"
"Display the logo": "Afficher le logo"
"This feature is not compatible with the <code>big menu</code> display.": "Cette fonctionnalité n'est pas compatible avec l'affichage du menu large."
"Icons and texts": "Icônes et textes"
"Loader enabled": "Activation de l'indicateur de chargement"
"Tips": "Astuces"
"Always displayed": "Toujours affiché"
"The logo will be hidden when the menu is always displayed.": "Le logo sera masque si le menu est toujours affiché."
"This is the automatic behavior when the menu is always displayed.": "C'est le comportement automatique lorsque le menu est toujours affiché."
"Not compatible with touch screens.": "Incompatible avec les écrans tactiles."
"Big menu": "Menu large"
@ -87,6 +85,12 @@
"With categories": "Avec les catégories"
"Custom categories": "Catégories personnalisées"
"Customize application categories": "Personnaliser les catégories des applications"
"Apps only visible in the top menu": "Applications visibles uniquement dans le menu supérieur"
"Apps visible in the top and side menus": "Applications visibles dans le menus supérieur et latéral"
"Reset to default": "Restaurer les valeurs par défaut"
"Applications": "Applications"
"Applications kept in the top menu": "Applications conservées dans le menu supérieur"
"Applications kept in the top menu but also shown in side menu": "Applications conservées dans le menu supérieur mais également affichées dans le menu latéral"
"These applications must be selected in the previous option.": "Ces applications doivent également être sélectionnées dans l'option précédente."
"Hide labels on mouse over": "Masquer le libellé des applications au passage de la souris"
"Except the hovered app": "À l'exception de l'application survolée"
"Search": "Rechercher"
"Toggle the menu": "Basculer le menu"

96
src/l10n/fixtures/gl.yaml Normal file
View file

@ -0,0 +1,96 @@
"Custom menu": "Custom menu"
"Enable the custom menu": "Enable the custom menu"
"No": "No"
"Yes": "Yes"
"Menu": "Menu"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
"Top menu": "Top menu"
"Apps that not must be moved in the side menu": "Apps that not must be moved in the side menu"
"If there is no selection then the global configuration is applied.": "If there is no selection then the global configuration is applied."
"Experimental": "Experimental"
"Save": "Save"
"You like this app and you want to support me?": "You like this app and you want to support me?"
"Buy me a coffee ☕": "Buy me a coffee ☕"
"Hidden": "Hidden"
"Small": "Small"
"Normal": "Normal"
"Big": "Big"
"Hidden icon": "Hidden icon"
"Small icon": "Small icon"
"Normal icon": "Normal icon"
"Big icon": "Big icon"
"Hidden text": "Hidden text"
"Small text": "Small text"
"Normal text": "Normal text"
"Big text": "Big text"
"Colors": "Colors"
"Background color": "Background color"
"Background color of current app": "Background color of current app"
"Text color": "Text color"
"Loader": "Loader"
"Icon": "Icon"
"Same color": "Same color"
"Opposite color": "Opposite color"
"Transparent": "Transparent"
"Opaque": "Opaque"
"Opener": "Opener"
"Default": "Default"
"Default (dark)": "Default (dark)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (dark)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (dark)"
"Before the logo": "Before the logo"
"After the logo": "After the logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Show only the opener (hidden logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Do not display the side menu and the opener if there is no application (eg: public pages)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)"
"Display the big menu": "Display the big menu"
"Display the logo": "Display the logo"
"Icons and texts": "Icons and texts"
"Loader enabled": "Loader enabled"
"Tips": "Tips"
"Always displayed": "Always displayed"
"This is the automatic behavior when the menu is always displayed.": "This is the automatic behavior when the menu is always displayed."
"Not compatible with touch screens.": "Not compatible with touch screens."
"Big menu": "Big menu"
"Live preview": "Live preview"
"Open apps in new tab": "Open apps in new tab"
"Use the global setting": "Use the global setting"
"Use my selection": "Use my selection"
"Show and hide the list of applications": "Show and hide the list of applications"
"Use the avatar instead of the logo": "Use the avatar instead of the logo"
"You do not have permission to change the settings.": "You do not have permission to change the settings."
"Force this configuration to users": "Force this configuration to users"
"Export the configuration": "Export the configuration"
"Purge the cache": "Purge the cache"
"Show the link to settings": "Show the link to settings"
"The menu is enabled by default for users": "The menu is enabled by default for users"
"Except when the configuration is forced.": "Except when the configuration is forced."
"Apps that should not be displayed in the menu": "Apps that should not be displayed in the menu"
"This feature is only compatible with the <code>big menu</code> display.": "This feature is only compatible with the <code>big menu</code> display."
"The logo is a link to the default app": "The logo is a link to the default app"
"Others": "Others"
"Categories": "Categories"
"Customize sorting": "Customize sorting"
"Order by": "Order by"
"Name": "Name"
"Customed": "Customed"
"Show and hide the list of categories": "Show and hide the list of categories"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "This parameters are used when Dark theme or Breeze Dark Theme are enabled."
"Dark mode colors": "Dark mode colors"
"With categories": "With categories"
"Custom categories": "Custom categories"
"Customize application categories": "Customize application categories"
"Reset to default": "Reset to default"
"Applications": "Applications"
"Applications kept in the top menu": "Applications kept in the top menu"
"Applications kept in the top menu but also shown in side menu": "Applications kept in the top menu but also shown in side menu"
"These applications must be selected in the previous option.": "These applications must be selected in the previous option."
"Hide labels on mouse over": "Hide labels on mouse over"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

96
src/l10n/fixtures/nl.yaml Normal file
View file

@ -0,0 +1,96 @@
"Custom menu": "Aangepast menu"
"Enable the custom menu": "Het aangepaste menu inschakelen"
"No": "Nee"
"Yes": "Ja"
"Menu": "Menu"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Gebruik de snelkoppeling <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> om het zijmenu te openen en te verbergen. Gebruik <span class="keyboard-key">tab</span> om te navigeren.'
"Top menu": "Bovenste menu"
"Apps that not must be moved in the side menu": "Apps die niet moeten worden verplaatst in het zijmenu"
"If there is no selection then the global configuration is applied.": "Als er geen keuze is, wordt de globale configuratie toegepast."
"Experimental": "Experimenteel"
"Save": "Opslaan"
"You like this app and you want to support me?": "Vind je deze app leuk en wil je me steunen?"
"Buy me a coffee ☕": "Koop een koffie voor me ☕"
"Hidden": "Verborgen"
"Small": "Klein"
"Normal": "Normaal"
"Big": "Groot"
"Hidden icon": "Verborgen icoon"
"Small icon": "Klein icoon"
"Normal icon": "Normaal icoon"
"Big icon": "Groot icoon"
"Hidden text": "Verborgen tekst"
"Small text": "Kleine tekst"
"Normal text": "Normale tekst"
"Big text": "Grote tekst"
"Colors": "Kleuren"
"Background color": "Achtergrond kleur"
"Background color of current app": "Achtergrondkleur van huidige app"
"Text color": "Tekst kleur"
"Loader": "Lader"
"Icon": "Icoon"
"Same color": "Zelfde kleur"
"Opposite color": "Tegenovergestelde kleur"
"Transparent": "Transparant"
"Opaque": "Ondoorzichtig"
"Opener": "Opener"
"Default": "Standaard"
"Default (dark)": "Standaard (donker)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (donker)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (donker)"
"Before the logo": "Voor het logo"
"After the logo": "Na het logo"
"Position": "Positie"
"Show only the opener (hidden logo)": "Toon alleen de opener (verborgen logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Geef het zijmenu en de opener niet weer als er geen toepassing is (bijv. openbare pagina's)."
"Panel": "Paneel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Open het menu wanneer de muis over de opener gaat (automatisch uitgeschakeld op aanraakschermen)"
"Display the big menu": "Toon het grote menu"
"Display the logo": "Toon het logo"
"Icons and texts": "Iconen en teksten"
"Loader enabled": "Lader ingeschakeld"
"Tips": "Tips"
"Always displayed": "Altijd weergegeven"
"This is the automatic behavior when the menu is always displayed.": "Dit is het automatische gedrag wanneer het menu altijd wordt weergegeven."
"Not compatible with touch screens.": "Niet compatibel met aanraakschermen."
"Big menu": "Groot menu"
"Live preview": "Live voorbeeld"
"Open apps in new tab": "Open apps in nieuwe tab"
"Use the global setting": "Gebruik de globale instellingen"
"Use my selection": "Gebruik mijn selectie"
"Show and hide the list of applications": "De lijst met toepassingen tonen en verbergen"
"Use the avatar instead of the logo": "Gebruik avatar in plaats van het logo"
"You do not have permission to change the settings.": "Je hebt geen toestemming om de instellingen te veranderen."
"Force this configuration to users": "Forceer deze configuratie aan gebruikers"
"Export the configuration": "Exporteer de configuratie"
"Purge the cache": "De cache wissen"
"Show the link to settings": "Toon de link naar de instellingen"
"The menu is enabled by default for users": "Het menu is standaard ingeschakeld voor gebruikers"
"Except when the configuration is forced.": "Behalve als de configuratie geforceerd is."
"Apps that should not be displayed in the menu": "Apps die niet in het menu weergegeven mogen worden"
"This feature is only compatible with the <code>big menu</code> display.": "Deze functie is alleen compatibel met het <code>grote menu</code> scherm."
"The logo is a link to the default app": "Het logo is een link naar de standaard app"
"Others": "Overige"
"Categories": "Categorieën"
"Customize sorting": "Sortering aanpassen"
"Order by": "Sorteer op"
"Name": "Naam"
"Customed": "Aangepast"
"Show and hide the list of categories": "De lijst met categorieën tonen en verbergen"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Deze parameters worden gebruikt wanneer Dark theme of Breeze Dark Theme zijn ingeschakeld."
"Dark mode colors": "Donkere modus kleuren"
"With categories": "Met categorieën"
"Custom categories": "Aangepaste categorieën"
"Customize application categories": "Toepassingscategorieën aanpassen"
"Reset to default": "Terugzetten naar standaard"
"Applications": "Applicaties"
"Applications kept in the top menu": "Applicaties bewaard in het bovenste menu"
"Applications kept in the top menu but also shown in side menu": "Applicaties blijven in het topmenu maar worden ook in het zijmenu getoond"
"These applications must be selected in the previous option.": "Deze toepassingen moeten bij de vorige optie zijn geselecteerd."
"Hide labels on mouse over": "Hide labels on mouse over"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

View file

@ -0,0 +1,94 @@
"Custom menu": "Menu personalizado"
"Enable the custom menu": "Habilitar o menu personalizado"
"No": "Não"
"Yes": "Sim"
"Menu": "Menu"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Use o atalho <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> para exibir e para esconder o menu lateral. Use <span class="keyboard-key">tab</span> para navegar.'
"Top menu": "Menu superior"
"Apps that not must be moved in the side menu": "Apps que não devem ser movidos para o menu lateral"
"If there is no selection then the global configuration is applied.": "Se não houver seleção, a configuração global será aplicada."
"Experimental": "Experimental"
"Save": "Salvar"
"You like this app and you want to support me?": "Você gosta deste aplicativo e quer me apoiar?"
"Buy me a coffee ☕": "Me pague um café ☕"
"Hidden": "Oculto"
"Small": "Pequeno"
"Normal": "Normal"
"Big": "Grande"
"Hidden icon": "Ícone oculto"
"Small icon": "Ícone pequeno"
"Normal icon": "Ícone normal"
"Big icon": "Ícone grance"
"Hidden text": "Texto oculto"
"Small text": "Texto pequeno"
"Normal text": "Texto normal"
"Big text": "Texto grande"
"Colors": "Cores"
"Background color": "Cor de fundo"
"Background color of current app": "Cor de fundo do app atual"
"Text color": "Cor do texto"
"Loader": "Progresso"
"Icon": "Ícone"
"Same color": "Mesma cor"
"Opposite color": "Cor oposta"
"Transparent": "Transparente"
"Opaque": "Opaco"
"Opener": "Abrir"
"Default": "Padrão"
"Default (dark)": "Padrão (escuro)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (escuro)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (escuro)"
"Before the logo": "Antes da logo"
"After the logo": "Depois da logo"
"Position": "Posição"
"Show only the opener (hidden logo)": "Mostrar apenas o Abrir (ocultar logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Não mostrar o menu lateral e o Abrir se não houver aplicação (p.ex. páginas públicas)."
"Panel": "Painel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Abrir o menu quando o mouse passar sobre o Abrir (desativado automaticamente em telas de toque)"
"Display the big menu": "Mostrar o menu grande"
"Display the logo": "Mostrar a logo"
"Icons and texts": "Ícones e textos"
"Loader enabled": "Progresso ativado"
"Tips": "Dicas"
"Always displayed": "Sempre visível"
"This is the automatic behavior when the menu is always displayed.": "Este é o comportamento automático quando o menu está sempre visível."
"Not compatible with touch screens.": "Não compatível com telas de toque."
"Big menu": "Menu grande"
"Live preview": "Visualização ao vivo"
"Open apps in new tab": "Abrir apps em nova aba"
"Use the global setting": "Usar configurações globais"
"Use my selection": "Usar minha seleção"
"Show and hide the list of applications": "Mostrar e ocultar a lista de aplicativos"
"Use the avatar instead of the logo": "Use o avatar ao invés da logo"
"You do not have permission to change the settings.": "Você não tem permissão para alterar as configurações."
"Force this configuration to users": "Forçar esta configuração para os usuários"
"Export the configuration": "Exportar a configuração"
"Purge the cache": "Limpar o cache"
"Show the link to settings": "Mostrar o link para configurações"
"The menu is enabled by default for users": "O menu é habilitado por padrão para os usuários"
"Except when the configuration is forced.": "Exceto quando a configuração é forçada."
"Apps that should not be displayed in the menu": "Apps que não devem ser mostrados no menu"
"This feature is only compatible with the <code>big menu</code> display.": "Este recurso só é compatível com a exibição do <code>menu grande</code>."
"The logo is a link to the default app": "A logo é um link para o app padrão"
"Others": "Outros"
"Categories": "Categorias"
"Customize sorting": "Personalizar classificação"
"Order by": "Ordenar por"
"Name": "Nome"
"Customed": "Personalizado"
"Show and hide the list of categories": "Mostrar e esconder a lista de categorias"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Estes parâmetros são usados quando o tema escuro ou o tema Dark Breeze está ativo."
"Dark mode colors": "Cores do modo escuro"
"With categories": "Com categorias"
"Custom categories": "Categorias personalizadas"
"Customize application categories": "Personalizar categorias de apps"
"Reset to default": "Restaurar padrão"
"Applications": "Aplicativos"
"Applications kept in the top menu": "Aplicativos mantidos no menu superior"
"Applications kept in the top menu but also shown in side menu": "Aplicativos mantidos no menu superior, mas também mostrados no menu lateral"
"These applications must be selected in the previous option.": "Estes aplicativos devem ser selecionados na opção anterior."
"Hide labels on mouse over": "Ocultar descrição ao passar o mouse"
"Toggle the menu": "Toggle the menu"

96
src/l10n/fixtures/ru.yaml Normal file
View file

@ -0,0 +1,96 @@
"Custom menu": "Custom menu"
"Enable the custom menu": "Включить пользовательское меню"
"No": "Нет"
"Yes": "Да"
"Menu": "Меню"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Используйте сочетание клавиш <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span>, чтобы открыть или скрыть боковое меню. Используйте <span class="keyboard-key">Tab</span> для навигации.'
"Top menu": "Верхнее меню"
"Apps that not must be moved in the side menu": "Приложения не перемещаемые в боковое меню"
"If there is no selection then the global configuration is applied.": "Если тут ничего не отмечено, применяются глобальные настройки."
"Experimental": "Экспериментальный"
"Save": "Сохранить"
"You like this app and you want to support me?": "Вам нравится приложение или вы хотите поддержать меня?"
"Buy me a coffee ☕": "Купить мне чашку кофе ☕"
"Hidden": "Скрыто"
"Small": "Маленький"
"Normal": "Средний"
"Big": "Большой"
"Hidden icon": "Без иконки"
"Small icon": "Маленькая иконка"
"Normal icon": "Средняя иконка"
"Big icon": "Большая иконка"
"Hidden text": "Без текста"
"Small text": "Маленький текст"
"Normal text": "Средний текст"
"Big text": "Большой текст"
"Colors": "Цвета"
"Background color": "Цвет фона"
"Background color of current app": "Цвет фона выбранного приложения"
"Text color": "Цвет текста"
"Loader": "Загрузчик"
"Icon": "Иконка"
"Same color": "Такой же цвет"
"Opposite color": "Противоположный цвет"
"Transparent": "Прозрачный"
"Opaque": "Непрозрачный"
"Opener": "Открывалка"
"Default": "По умолчанию"
"Default (dark)": "По умолчанию (тёмный)"
"Hamburger": "Гамбургер"
"Hamburger (dark)": "Гамбургер (тёмный)"
"Hamburger 2": "Гамбургер 2"
"Hamburger 2 (dark)": "Гамбургер 2 (тёмный)"
"Before the logo": "Перед логотипом"
"After the logo": "После логотипа"
"Position": "Положение"
"Show only the opener (hidden logo)": "Показать только открывающую часть (скрытый логотип)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Не отображать боковое меню и открывалку, если нет приложения (например, публичные страницы)."
"Panel": "Панель"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Открывать меню при наведении мыши на экран (автоматически отключается на сенсорных экранах)"
"Display the big menu": "Отобразить большое меню"
"Display the logo": "Показать логотип"
"Icons and texts": "Иконки и текст"
"Loader enabled": "Загрузчик включен"
"Tips": "Советы"
"Always displayed": "Всегда отображается"
"This is the automatic behavior when the menu is always displayed.": "This is the automatic behavior when the menu is always displayed."
"Not compatible with touch screens.": "Не совместимо с сенсорными экранами."
"Big menu": "Большое меню"
"Live preview": "Live preview"
"Open apps in new tab": "Открывать приложения в новой вкладке"
"Use the global setting": "Использовать глобальные настройки"
"Use my selection": "Использовать мои настройки"
"Show and hide the list of applications": "Показать или скрыть список приложений"
"Use the avatar instead of the logo": "Использовать аватар вместо логотипа"
"You do not have permission to change the settings.": "У вас нет разрешения изменять настройки."
"Force this configuration to users": "Force this configuration to users"
"Export the configuration": "Экспортировать конфигурацию"
"Purge the cache": "Очистить кэш"
"Show the link to settings": "Показать ссылку на настройки"
"The menu is enabled by default for users": "Это меню включено по умолчанию для пользователей"
"Except when the configuration is forced.": "Except when the configuration is forced."
"Apps that should not be displayed in the menu": "Ппрограммы, скрытые из меню"
"This feature is only compatible with the <code>big menu</code> display.": "This feature is only compatible with the <code>big menu</code> display."
"The logo is a link to the default app": "Логотип открывает приложение по умолчанию"
"Others": "Прочие"
"Categories": "Категории"
"Customize sorting": "Настроить сортировку"
"Order by": "В порядке"
"Name": "Название"
"Customed": "Customed"
"Show and hide the list of categories": "Показать или скрыть список категорий"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Эти настройки используются темами Тёмная и Тёмная Breeze."
"Dark mode colors": "Цвета тёмной темы"
"With categories": "С категориями"
"Custom categories": "Пользовательские категории"
"Customize application categories": "Изменить категории приложений"
"Reset to default": "Сбросить к значениям по умолчанию"
"Applications": "Приложения"
"Applications kept in the top menu": "Applications kept in the top menu"
"Applications kept in the top menu but also shown in side menu": "Applications kept in the top menu but also shown in side menu"
"These applications must be selected in the previous option.": "These applications must be selected in the previous option."
"Hide labels on mouse over": "Скрыть название при наведении мыши"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

94
src/l10n/fixtures/sk.yaml Normal file
View file

@ -0,0 +1,94 @@
"Custom menu": "Custom menu"
"Enable the custom menu": "Enable the custom menu"
"No": "No"
"Yes": "Yes"
"Menu": "Menu"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
: 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'
"Top menu": "Top menu"
"Apps that not must be moved in the side menu": "Apps that not must be moved in the side menu"
"If there is no selection then the global configuration is applied.": "If there is no selection then the global configuration is applied."
"Experimental": "Experimental"
"Save": "Save"
"You like this app and you want to support me?": "You like this app and you want to support me?"
"Buy me a coffee ☕": "Buy me a coffee ☕"
"Hidden": "Hidden"
"Small": "Small"
"Normal": "Normal"
"Big": "Big"
"Hidden icon": "Hidden icon"
"Small icon": "Small icon"
"Normal icon": "Normal icon"
"Big icon": "Big icon"
"Hidden text": "Hidden text"
"Small text": "Small text"
"Normal text": "Normal text"
"Big text": "Big text"
"Colors": "Colors"
"Background color": "Background color"
"Background color of current app": "Background color of current app"
"Text color": "Text color"
"Loader": "Loader"
"Icon": "Icon"
"Same color": "Same color"
"Opposite color": "Opposite color"
"Transparent": "Transparent"
"Opaque": "Opaque"
"Opener": "Opener"
"Default": "Default"
"Default (dark)": "Default (dark)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (dark)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (dark)"
"Before the logo": "Before the logo"
"After the logo": "After the logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Show only the opener (hidden logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Do not display the side menu and the opener if there is no application (eg: public pages)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)"
"Display the big menu": "Display the big menu"
"Display the logo": "Display the logo"
"Icons and texts": "Icons and texts"
"Loader enabled": "Loader enabled"
"Tips": "Tips"
"Always displayed": "Always displayed"
"This is the automatic behavior when the menu is always displayed.": "This is the automatic behavior when the menu is always displayed."
"Not compatible with touch screens.": "Not compatible with touch screens."
"Big menu": "Big menu"
"Live preview": "Live preview"
"Open apps in new tab": "Open apps in new tab"
"Use the global setting": "Use the global setting"
"Use my selection": "Use my selection"
"Show and hide the list of applications": "Show and hide the list of applications"
"Use the avatar instead of the logo": "Use the avatar instead of the logo"
"You do not have permission to change the settings.": "You do not have permission to change the settings."
"Force this configuration to users": "Force this configuration to users"
"Export the configuration": "Export the configuration"
"Purge the cache": "Purge the cache"
"Show the link to settings": "Show the link to settings"
"The menu is enabled by default for users": "The menu is enabled by default for users"
"Except when the configuration is forced.": "Except when the configuration is forced."
"Apps that should not be displayed in the menu": "Apps that should not be displayed in the menu"
"This feature is only compatible with the <code>big menu</code> display.": "This feature is only compatible with the <code>big menu</code> display."
"The logo is a link to the default app": "The logo is a link to the default app"
"Others": "Others"
"Categories": "Categories"
"Customize sorting": "Customize sorting"
"Order by": "Order by"
"Name": "Name"
"Customed": "Customed"
"Show and hide the list of categories": "Show and hide the list of categories"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "This parameters are used when Dark theme or Breeze Dark Theme are enabled."
"Dark mode colors": "Dark mode colors"
"With categories": "With categories"
"Custom categories": "Custom categories"
"Customize application categories": "Customize application categories"
"Reset to default": "Reset to default"
"Applications": "Applications"
"Applications kept in the top menu": "Applications kept in the top menu"
"Applications kept in the top menu but also shown in side menu": "Applications kept in the top menu but also shown in side menu"
"These applications must be selected in the previous option.": "These applications must be selected in the previous option."
"Hide labels on mouse over": "Hide labels on mouse over"
"Toggle the menu": "Toggle the menu"

View file

@ -0,0 +1,100 @@
"Custom menu": "Custom menu"
"Enable the custom menu": "Enable the custom menu"
"No": "No"
"Yes": "Yes"
"Menu": "Menu"
? 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span>
to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to
navigate.'
: 'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span>
to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to
navigate.'
"Top menu": "Top menu"
"Apps that not must be moved in the side menu": "Apps that not must be moved in the side menu"
"If there is no selection then the global configuration is applied.": "If there is no selection then the global configuration is applied."
"Experimental": "Experimental"
"Save": "Save"
"You like this app and you want to support me?": "You like this app and you want to support me?"
"Buy me a coffee ☕": "Buy me a coffee ☕"
"Hidden": "Hidden"
"Small": "Small"
"Normal": "Normal"
"Big": "Big"
"Hidden icon": "Hidden icon"
"Small icon": "Small icon"
"Normal icon": "Normal icon"
"Big icon": "Big icon"
"Hidden text": "Hidden text"
"Small text": "Small text"
"Normal text": "Normal text"
"Big text": "Big text"
"Colors": "Colors"
"Background color": "Background color"
"Background color of current app": "Background color of current app"
"Text color": "Text color"
"Loader": "Loader"
"Icon": "Icon"
"Same color": "Same color"
"Opposite color": "Opposite color"
"Transparent": "Transparent"
"Opaque": "Opaque"
"Opener": "Opener"
"Default": "Default"
"Default (dark)": "Default (dark)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (dark)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (dark)"
"Before the logo": "Before the logo"
"After the logo": "After the logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Show only the opener (hidden logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Do not display the side menu and the opener if there is no application (eg: public pages)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)"
"Display the big menu": "Display the big menu"
"Display the logo": "Display the logo"
"Icons and texts": "Icons and texts"
"Loader enabled": "Loader enabled"
"Tips": "Tips"
"Always displayed": "Always displayed"
"This is the automatic behavior when the menu is always displayed.": "This is the automatic behavior when the menu is always displayed."
"Not compatible with touch screens.": "Not compatible with touch screens."
"Big menu": "Big menu"
"Live preview": "Live preview"
"Open apps in new tab": "Open apps in new tab"
"Use the global setting": "Use the global setting"
"Use my selection": "Use my selection"
"Show and hide the list of applications": "Show and hide the list of applications"
"Use the avatar instead of the logo": "Use the avatar instead of the logo"
"You do not have permission to change the settings.": "You do not have permission to change the settings."
"Force this configuration to users": "Force this configuration to users"
"Export the configuration": "Export the configuration"
"Purge the cache": "Purge the cache"
"Show the link to settings": "Show the link to settings"
"The menu is enabled by default for users": "The menu is enabled by default for users"
"Except when the configuration is forced.": "Except when the configuration is forced."
"Apps that should not be displayed in the menu": "Apps that should not be displayed in the menu"
"This feature is only compatible with the <code>big menu</code> display.": "This feature is only compatible with the <code>big menu</code> display."
"The logo is a link to the default app": "The logo is a link to the default app"
"Others": "Others"
"Categories": "Categories"
"Customize sorting": "Customize sorting"
"Order by": "Order by"
"Name": "Name"
"Customed": "Customed"
"Show and hide the list of categories": "Show and hide the list of categories"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "This parameters are used when Dark theme or Breeze Dark Theme are enabled."
"Dark mode colors": "Dark mode colors"
"With categories": "With categories"
"Custom categories": "Custom categories"
"Customize application categories": "Customize application categories"
"Reset to default": "Reset to default"
"Applications": "Applications"
"Applications kept in the top menu": "Applications kept in the top menu"
"Applications kept in the top menu but also shown in side menu": "Applications kept in the top menu but also shown in side menu"
"These applications must be selected in the previous option.": "These applications must be selected in the previous option."
"Hide labels on mouse over": "Hide labels on mouse over"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle the menu": "Toggle the menu"

View file

@ -3,7 +3,8 @@
"No": "取消"
"Yes": "确定"
"Menu": "菜单"
"Use the shortcut <span class=\"keyboard-key\">Ctrl<\/span>+<span class=\"keyboard-key\">o<\/span> to open and to hide the side menu. Use <span class=\"keyboard-key\">tab<\/span> to navigate.": "使用快捷键 <span class=\"keyboard-key\">Ctrl<\/span>+<span class=\"keyboard-key\">o<\/span> 打开或隐藏侧边栏菜单。使用<span class=\"keyboard-key\">tab<\/span> 来导航。"
? "Use the shortcut <span class=\"keyboard-key\">Ctrl</span>+<span class=\"keyboard-key\">o</span> to open and to hide the side menu. Use <span class=\"keyboard-key\">tab</span> to navigate."
: "使用快捷键 <span class=\"keyboard-key\">Ctrl</span>+<span class=\"keyboard-key\">o</span> 打开或隐藏侧边栏菜单。使用<span class=\"keyboard-key\">tab</span> 来导航。"
"Top menu": "顶部菜单"
"Apps that not must be moved in the side menu": "禁止在侧边栏菜单移动的应用"
"If there is no selection then the global configuration is applied.": "如不选择,将应用全局设定。"
@ -40,14 +41,11 @@
"Panel": "面板"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "鼠标悬停时打开菜单 (触摸屏时将自动禁用)"
"Display the big menu": "显示大型菜单"
"This menu is not compatible with AppOrder.": "型菜单与应用顺序不兼容"
"Display the logo": "显示logo"
"This feature is not compatible with the <code>big menu<\/code> display.": "此功能与显示<code>大型菜单<\/code>不兼容。"
"Icons and texts": "图标与文字"
"Loader enabled": "菜单指示器已激活"
"Tips": "技巧"
"Always displayed": "一直显示"
"The logo will be hidden when the menu is always displayed.": "一直显示菜单时logo将被隐藏。"
"This is the automatic behavior when the menu is always displayed.": "一直显示菜单时的自动动作。"
"Not compatible with touch screens.": "与触屏不兼容。"
"Big menu": "大型菜单"
@ -65,7 +63,7 @@
"The menu is enabled by default for users": "用户的默认菜单已激活"
"Except when the configuration is forced.": "除非设置被强制使用。"
"Apps that should not be displayed in the menu": "禁止在菜单中显示的应用"
"This feature is only compatible with the <code>big menu<\/code> display.": "此功能只和<code>大型菜单<\/code>兼容。"
"This feature is only compatible with the <code>big menu</code> display.": "此功能只和<code>大型菜单</code>兼容。"
"The logo is a link to the default app": "logo链接到默认应用"
"Others": "其他"
"Categories": "类别"
@ -79,8 +77,6 @@
"With categories": "有类别"
"Custom categories": "自定义类别"
"Customize application categories": "自定义应用程序类别"
"Apps only visible in the top menu": "应用程序仅在顶部菜单中可见"
"Apps visible in the top and side menus": "顶部和侧边菜单中可见的应用程序"
"Reset to default": "重置为默认设置"
"Hidden icon": "隐藏图标"
"Small icon": "小图标"
@ -90,3 +86,11 @@
"Small text": "小文本"
"Normal text": "普通文本"
"Big text": "大文本"
"Applications": "Applications"
"Applications kept in the top menu": "Applications kept in the top menu"
"Applications kept in the top menu but also shown in side menu": "Applications kept in the top menu but also shown in side menu"
"These applications must be selected in the previous option.": "These applications must be selected in the previous option."
"Hide labels on mouse over": "Hide labels on mouse over"
"Except the hovered app": "Except the hovered app"
"Search": "Search"
"Toggle menu": "Toggle menu"

17
src/lib/createElement.js Normal file
View file

@ -0,0 +1,17 @@
module.exports = (tagName, attributes) => {
const element = document.createElement(tagName)
if (typeof attributes === 'object') {
for (let i in attributes) {
if (i === 'text') {
element.textContent = attributes[i]
} else if (i === 'html') {
element.innerHTML = attributes[i]
} else {
element.setAttribute(i, attributes[i])
}
}
}
return element
}

View file

@ -1,116 +1,132 @@
:root {
<?php foreach ($_['vars'] as $key => $value): ?>
<?php if ($key === 'opener'): ?>
--side-menu-<?php echo $key ?>: url('<?php print_unescaped(image_path('side_menu', $value.'.svg')); ?>');
<?php else: ?>
--side-menu-<?php echo $key ?>: <?php echo $value ?>;
<?php endif; ?>
<?php endforeach; ?>
<?php foreach ($_['vars'] as $key => $value): ?>
<?php if ($key === 'opener'): ?>
--side-menu-<?php echo $key ?>: url('<?php print_unescaped(image_path('side_menu', $value.'.svg')); ?>');
<?php else: ?>
--side-menu-<?php echo $key ?>: <?php echo $value ?>;
<?php endif; ?>
<?php endforeach; ?>
}
<?php if (empty($_['top-menu-apps']) && empty($_['top-side-menu-apps'])): ?>
#appmenu {
display: none;
}
#appmenu {
display: none;
}
#appmenu + nav {
display: none;
}
#appmenu + nav {
display: none;
}
<?php else: ?>
.app-hidden {
opacity: 0;
}
.app-hidden {
opacity: 0;
}
<?php endif; ?>
<?php if ($_['opener-only']): ?>
#nextcloud {
display: none;
}
#nextcloud {
display: none;
}
<?php endif; ?>
<?php if (!$_['display-logo']): ?>
.side-menu-logo {
display: none;
.side-menu-logo {
display: none;
}
.side-menu-header {
height: 50px;
}
.side-menu-apps-list {
height: calc(100vh - 49px);
top: 49px;
}
#side-menu.hide-opener .side-menu-header .side-menu-opener.side-menu-closer {
visibility: hidden;
}
#side-menu.hide-opener.side-menu-with-categories .side-menu-search {
float: none;
}
<?php if ($_['size-text'] === 'hidden'): ?>
#side-menu, .side-menu-apps-list {
<?php if ($_['size-icon'] === 'big'): ?>
width: 55px;
<?php else: ?>
width: 52px;
<?php endif; ?>
}
.side-menu-header {
height: 50px;
#side-menu .side-menu-opener {
<?php if ($_['size-icon'] === 'big'): ?>
margin-left: 1px;
<?php else: ?>
margin-left: 0px;
<?php endif; ?>
}
.side-menu-apps-list {
height: calc(100vh - 49px);
top: 49px;
}
#side-menu.hide-opener .side-menu-header {
visibility: hidden;
}
<?php if ($_['size-text'] === 'hidden'): ?>
#side-menu, .side-menu-apps-list {
<?php if ($_['size-icon'] === 'big'): ?>
width: 55px;
<?php else: ?>
width: 52px;
<?php endif; ?>
}
#side-menu .side-menu-opener {
<?php if ($_['size-icon'] === 'big'): ?>
margin-left: 1px;
<?php else: ?>
margin-left: 0px;
<?php endif; ?>
}
<?php endif; ?>
<?php endif; ?>
<?php endif; ?>
<?php if ($_['size-icon'] === 'hidden'): ?>
.side-menu-app-icon {
display: none;
}
.side-menu-app-icon {
display: none;
}
<?php elseif ($_['size-icon'] === 'small'): ?>
.side-menu-app-icon svg {
width: 15px;
height: 15px;
}
.side-menu-app-icon svg {
width: 15px;
height: 15px;
}
img.side-menu-app-icon {
width: 15px;
height: 15px;
}
img.side-menu-app-icon {
width: 15px;
height: 15px;
}
<?php elseif ($_['size-icon'] === 'normal'): ?>
.side-menu-app-icon svg {
width: 20px;
height: 20px;
}
.side-menu-app-icon svg {
width: 20px;
height: 20px;
}
img.side-menu-app-icon {
width: 20px;
height: 20px;
}
img.side-menu-app-icon {
width: 20px;
height: 20px;
}
<?php elseif ($_['size-icon'] === 'big'): ?>
.side-menu-app-icon svg {
width: 23px;
height: 23px;
}
.side-menu-app-icon svg {
width: 23px;
height: 23px;
}
img.side-menu-app-icon {
width: 23px;
height: 23px;
}
img.side-menu-app-icon {
width: 23px;
height: 23px;
}
<?php endif; ?>
<?php if ($_['size-text'] === 'hidden'): ?>
.side-menu-app-text {
display: none;
}
.side-menu-app-text {
display: none;
}
<?php elseif ($_['size-text'] === 'small'): ?>
.side-menu-app-text {
font-size: 12px;
}
.side-menu-app-text {
font-size: 12px;
}
<?php elseif ($_['size-text'] === 'big'): ?>
.side-menu-app-text {
font-size: 16px;
}
.side-menu-app-text {
font-size: 16px;
}
<?php endif; ?>
<?php if ($_['always-displayed']): ?>
#content {
left: 53px;
width: calc(100% - (var(--body-container-margin) * 2) - 62px);
}
#content-vue {
width: calc(100% - (var(--body-container-margin) * 2) - 60px);
margin-left: 11px;
}
<?php endif; ?>

View file

@ -1,86 +0,0 @@
const alwaysDisplayed = function() {
const elements = querySelectorAll('*')
const fixedElements = []
for (let element of elements) {
if (typeof element !== 'object') {
continue
}
const position = window.getComputedStyle(element, null).getPropertyValue('position')
if (position !== 'fixed') {
continue
}
const id = element.getAttribute('id')
if (id === 'header' || id === 'side-menu' || id === 'side-menu-loader') {
continue
}
if (element.classList.contains('oc-dialog')) {
continue
}
let elementIsInSideMenu = false
let parent = element.parentNode
while (parent && !elementIsInSideMenu) {
try {
if (parent.getAttribute('id') === 'side-menu') {
elementIsInSideMenu = true
}
} catch (e) {
}
parent = parent.parentNode
}
if (elementIsInSideMenu) {
continue
}
fixedElements.push(element)
}
for (let i in fixedElements) {
const element = fixedElements[i]
const computedStyle = window.getComputedStyle(element, null)
const left = computedStyle.getPropertyValue('left')
const right = computedStyle.getPropertyValue('right')
if (right !== '0px') {
const intValue = parseInt(left.replace('px', '')) + 50
element.style.setProperty('transform', 'translateX(' + intValue.toString() + 'px)')
}
}
}
const content = querySelector('#content')
if (content && content.classList.contains('app-settings')) {
let loaded = false
const config = {
attributes: false,
childList: true,
subtree: true
}
const observer = new MutationObserver(() => {
if (loaded) {
return
}
const element = content.querySelector('#app-category-your-apps') || content.querySelector('#app-navigation ul')
if (element) {
loaded = true
alwaysDisplayed()
}
})
observer.observe(content, config)
} else {
window.setTimeout(alwaysDisplayed, 200)
}

View file

@ -1,14 +0,0 @@
let pageLoader = createElement('div', {id: 'side-menu-loader'})
let pageLoaderBar = createElement('div', {id: 'side-menu-loader-bar'})
pageLoader.appendChild(pageLoaderBar)
querySelector('body').appendChild(pageLoader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25)
})

View file

@ -1,216 +0,0 @@
let menuCache = null
const breakpointMobileWidth = 1024
const usePercentualAppMenuLimit = 0.8
const minAppsDesktop = 8
const handleMenuClick = (e, icon) => {
let element = e.target
while (element.tagName !== 'LI') {
element = element.parentNode
}
const a = querySelector('a', element)
if (a.getAttribute('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey) {
for (let tag of ['svg', 'div']) {
let el = querySelector(tag, element)
if (el) {
el.remove()
}
}
const loader = createElement('div', {'class': icon})
a.insertBefore(loader, querySelector('span', a))
}
}
const updateTopMenu = function() {
const isMobile = window.innerWidth < breakpointMobileWidth
const menu = querySelector('#appmenu')
const moreApps = querySelector('#more-apps')
const navigation = querySelector('#navigation')
const navigationApps = querySelector('#apps ul')
let apps = querySelectorAll('li', menu)
let lastShownApp = null
let appShown = []
if ((menu.innerHTML + menu.nextSibling.innerHTML) === menuCache) {
return
}
let navigationAppsHtml = ''
for (let app of apps) {
const dataId = app.getAttribute('data-id')
if (dataId === null) {
continue
}
if (topMenuApps.indexOf(dataId) === -1 && topSideMenuApps.indexOf(dataId) === -1) {
app.classList.add('hidden')
app.classList.add('app-hidden')
} else {
app.classList.remove('hidden')
app.classList.add('app-external-site')
if (topSideMenuApps.indexOf(dataId) !== -1) {
app.classList.add('app-top-side-menu')
}
appShown.push(app)
navigationAppsHtml = navigationAppsHtml + app.outerHTML
}
if (targetBlankApps.indexOf(dataId) !== -1) {
querySelector('a', app).setAttribute('target', '_blank')
}
}
navigationApps.innerHTML = navigationAppsHtml
const rightHeaderWidth = querySelector('.header-right').offsetWidth
const headerWidth = querySelector('header').offsetWidth
let availableWidth = headerWidth
availableWidth -= nextcloud.offsetWidth
availableWidth -= querySelector('#header .side-menu-opener').offsetWidth
availableWidth -= rightHeaderWidth > 230 ? rightHeaderWidth : 230
availableWidth *= isMobile ? usePercentualAppMenuLimit : 1
let appCount = Math.floor(availableWidth / querySelector('#appmenu li:not(.hidden)').offsetWidth)
if (isMobile && appCount > minAppsDesktop) {
appCount = minAppsDesktop
} else if (!isMobile && appCount < minAppsDesktop) {
appCount = minAppsDesktop
}
menu.style.opacity = 1
if (appShown.length - 1 - appCount >= 1) {
appCount--
}
for (let item of querySelectorAll('a', moreApps)) {
item.classList.remove('active')
}
let k = 0
let notInHeader = 0
for (let app of appShown) {
const name = app.getAttribute('data-id')
const li = querySelector('#apps li[data-id=' + name + '].app-external-site')
if (k < appCount && appCount > 0) {
app.classList.remove('hidden')
li.classList.add('in-header')
lastShownApp = app
} else {
app.classList.add('hidden')
li.classList.remove('in-header')
notInHeader++
const a = querySelector('a', app)
if (appCount > 0 && a.classList.contains('active')) {
lastShownApp.classList.add('hidden')
app.classList.remove('hidden')
notInHeader++
li.classList.add('in-header')
}
}
k++
}
// Hack for:
// - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
// - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
jQuery(menu).undelegate('li:not(#more-apps) > a', 'click')
jQuery(navigation).undelegate('a', 'click')
const confs = [
{
items: querySelectorAll('#navigation li'),
icon: 'icon-loading-small'
},
{
items: querySelectorAll('li:not(#more-apps)', menu),
icon: OCA.Theming && OCA.Theming.inverted ? 'icon-loading-small' : 'icon-loading-small-dark'
},
]
for (let conf of confs) {
for (let item of conf.items) {
item.addEventListener('click', (e) => {
handleMenuClick(e, conf.icon)
})
}
}
for (let app of querySelectorAll('#apps li.app-external-site')) {
const appId = app.getAttribute('data-id')
if (app.classList.contains('in-header')) {
for (let defs of querySelectorAll('svg defs', app)) {
defs.remove()
}
} else {
const svg = querySelector('svg', app)
if (querySelectorAll('svg defs', app).length > 0) {
continue
}
const defs = `
<defs>
<filter id="invertMenuMore-${appId}">
<feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix>
</filter>
</defs>`
svg.innerHTML = defs + svg.innerHTML
for (let image of querySelectorAll('image', svg)) {
image.setAttribute('filter', `url(#invertMenuMore-${appId})`)
}
svg.innerHTML = svg.innerHTML.replace(/fecolormatrix/g, 'feColorMatrix')
}
}
if (notInHeader === 0) {
moreApps.style.display = 'none'
navigation.style.display = 'none'
} else {
moreApps.style.display = 'flex'
}
menuCache = menu.innerHTML + menu.nextSibling.innerHTML
}
for (let i = 0; i < 4000; i+= 100) {
setTimeout(updateTopMenu, i)
}
let resizeTimeout = null;
window.addEventListener('resize', () => {
if (resizeTimeout !== null) {
clearTimeout(resizeTimeout)
}
resizeTimeout = setTimeout(updateTopMenu, 100)
})

View file

@ -1,5 +1,7 @@
<?php
header('Content-type: text/javascript');
$display = 'default';
if ($_['always-displayed']) {
@ -12,205 +14,191 @@ if ($_['always-displayed']) {
?>
const SMcreateElement = (tagName, attributes) => {
const element = document.createElement(tagName)
if (typeof attributes === 'object') {
for (let i in attributes) {
if (i === 'text') {
element.textContent = attributes[i]
} else if (i === 'html') {
element.innerHTML = attributes[i]
} else {
element.setAttribute(i, attributes[i])
}
}
}
return element
}
(function() {
const querySelector = function(selector, element) {
if (element) {
return element.querySelector(selector)
}
const sideMenuContainer = SMcreateElement('div', {id: 'side-menu-container'})
const sideMenuOpener = SMcreateElement('button', {
'class': 'side-menu-opener',
'arial-label': t('side_menu', 'Toggle the menu'),
'html': `<span>${t('side_menu', 'Toggle the menu')}</span>`
})
const sideMenu = SMcreateElement('div', {id: 'side-menu'})
return document.querySelector(selector)
const body = document.querySelector('body')
const html = document.querySelector('html')
const nextcloud = document.querySelector('#nextcloud')
const logo = document.querySelector('.header-left .logo')
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
window.targetBlankApps = <?php echo json_encode($_['target-blank-apps']), "\n" ?>
window.topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
window.topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']), "\n"; ?>
window.menuAppsOrder = <?php echo json_encode($_['apps-order']), "\n"; ?>
window.topMenuAppsMouseOverHiddenLabel = <?php echo json_encode($_['top-menu-mouse-over-hidden-label']), "\n"; ?>
<?php if ($display === 'big-menu'): ?>
sideMenu.setAttribute('data-bigmenu', '1')
<?php elseif ($display === 'side-with-categories'): ?>
sideMenu.setAttribute('data-sidewithcategories', '1')
<?php endif; ?>
const sideMenuFocus = () => {
let a = document.querySelector('#side-menu .side-menu-app.active a')
|| document.querySelector('#side-menu .side-menu-app a')
if (a) {
a.focus()
}
}
const querySelectorAll = function(selector, element) {
if (element) {
return element.querySelectorAll(selector)
document.querySelector('body').addEventListener('side-menu.apps', (e) => {
const apps = e.detail.apps;
<?php if ($_['hide-when-no-apps']): ?>
const sideMenu = document.querySelector('#side-menu')
if (apps.length === 0) {
sideMenu.classList.remove('open')
sideMenu.classList.add('hide')
sideMenuOpener.classList.add('hide')
} else {
sideMenu.classList.remove('hide')
sideMenuOpener.classList.remove('hide')
}
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
return document.querySelectorAll(selector)
}
const createElement = function(tagName, attributes) {
const element = document.createElement(tagName)
if (typeof attributes === 'object') {
for (let i in attributes) {
element.setAttribute(i, attributes[i])
}
<?php endif; ?>
<?php else: ?>
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
return element
}
const sideMenuContainer = createElement('div', {id: 'side-menu-container'})
const sideMenuOpener = createElement('button', {'class': 'side-menu-opener'})
const sideMenu = createElement('div', {id: 'side-menu'})
const body = querySelector('body')
const html = querySelector('html')
const nextcloud = querySelector('#nextcloud')
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
const targetBlankApps = <?php echo json_encode($_['target-blank-apps']) ?>
<?php if ($display === 'big-menu'): ?>
sideMenu.setAttribute('data-bigmenu', '1')
<?php elseif ($display === 'side-with-categories'): ?>
sideMenu.setAttribute('data-sidewithcategories', '1')
<?php endif; ?>
<?php endif; ?>
})
querySelector('body').addEventListener('side-menu.apps', (e) => {
const apps = e.detail.apps;
body.addEventListener('side-menu.ready', () => {
const sideMenu = document.querySelector('#side-menu')
const headerMenuOpener = document.querySelector('#header .side-menu-opener')
const sideMenuOpener = document.querySelectorAll('#side-menu .side-menu-opener')
<?php if ($_['hide-when-no-apps']): ?>
const sideMenu = querySelector('#side-menu')
if (!headerMenuOpener) {
return
}
if (apps.length === 0) {
sideMenu.classList.remove('open')
sideMenu.classList.add('hide')
sideMenuOpener.classList.add('hide')
} else {
sideMenu.classList.remove('hide')
sideMenuOpener.classList.remove('hide')
}
<?php if ($_['opener-hover']): ?>
const sideMenuMouseLeave = () => {
sideMenu.classList.remove('open')
sideMenu.removeEventListener('mouseleave', sideMenuMouseLeave)
}
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
<?php endif; ?>
<?php else: ?>
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
<?php endif; ?>
<?php endif; ?>
})
const sideMenuMouseEnter = () => {
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
}
body.addEventListener('side-menu.ready', () => {
const sideMenu = querySelector('#side-menu')
const headerMenuOpener = querySelector('#header .side-menu-opener')
const sideMenuOpener = querySelectorAll('#side-menu .side-menu-opener')
const sideMenuOpenerMouseEnter = () => {
sideMenu.classList.add('open')
sideMenu.addEventListener('mouseenter', sideMenuMouseEnter)
sideMenuFocus = () => {
let a = querySelector('.side-menu-app.active a', sideMenu)
if (!a) {
return
}
if (a.length === 0) {
a = querySelector('.side-menu-app:first-child a', sideMenu)
}
if (a.length > 0) {
a.focus()
}
}
sideMenuFocus()
}
if (!isTouchDevice) {
<?php if ($_['opener-hover']): ?>
const sideMenuMouseLeave = () => {
sideMenu.classList.remove('open')
sideMenu.removeEventListener('mouseleave', sideMenuMouseLeave)
}
headerMenuOpener.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
const sideMenuMouseEnter = () => {
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
}
sideMenu.classList.add('hide-opener')
<?php endif ?>
const sideMenuOpenerMouseEnter = () => {
sideMenu.classList.add('open')
sideMenu.addEventListener('mouseenter', sideMenuMouseEnter)
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
sideMenu.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
}
<?php endif; ?>
sideMenuFocus()
}
if (!isTouchDevice) {
<?php if ($_['opener-hover']): ?>
headerMenuOpener.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
sideMenu.classList.add('hide-opener')
<?php endif ?>
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
sideMenu.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
}
<?php endif; ?>
headerMenuOpener.addEventListener('click', () => {
sideMenu.classList.add('open')
const a = querySelector('.side-menu-app.active a', sideMenu)
if (a !== null) {
a.focus()
}
headerMenuOpener.blur()
})
for (let opener of sideMenuOpener) {
opener.addEventListener('click', () => {
<?php if ($display === 'always-displayed'): ?>
sideMenu.classList.toggle('open')
<?php else: ?>
sideMenu.classList.remove('open')
<?php endif; ?>
})
}
document.addEventListener('keydown', (e) => {
var key = e.key || e.keyCode
if ((key === 'o' || key === 79) && e.ctrlKey === true) {
e.preventDefault()
sideMenu.classList.toggle('open')
sideMenuFocus()
}
})
const sideMenuObserver = new MutationObserver((e) => {
if (body.getAttribute('id') !== 'body-settings') {
return
}
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
})
sideMenuObserver.observe(sideMenu, {
attributes: true,
attributeFilter: ['class'],
childList: false,
characterData: false
})
headerMenuOpener.addEventListener('click', () => {
sideMenu.classList.add('open')
headerMenuOpener.blur()
sideMenuFocus()
})
body.appendChild(sideMenuContainer)
sideMenuContainer.appendChild(sideMenu)
for (let opener of sideMenuOpener) {
opener.addEventListener('click', () => {
<?php if ($display === 'always-displayed'): ?>
sideMenu.classList.toggle('open')
<?php else: ?>
sideMenu.classList.remove('open')
<?php endif; ?>
})
}
<?php if ($_['loader-enabled'] === true): ?>
<?php require_once __DIR__.'/_loaderEnabled.js'; ?>
<?php endif; ?>
document.addEventListener('keydown', (e) => {
var key = e.key || e.keyCode
if ((key === 'o' || key === 79) && e.ctrlKey === true) {
e.preventDefault()
sideMenu.classList.toggle('open')
sideMenuFocus()
}
})
const sideMenuObserver = new MutationObserver((e) => {
if (body.getAttribute('id') !== 'body-settings') {
return
}
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
})
sideMenuObserver.observe(sideMenu, {
attributes: true,
attributeFilter: ['class'],
childList: false,
characterData: false
})
})
body.appendChild(sideMenuContainer)
sideMenuContainer.appendChild(sideMenu)
<?php if ($_['loader-enabled'] === true): ?>
PageLoader()
<?php endif; ?>
if (nextcloud) {
if (logo && logo.parentNode !== nextcloud) {
nextcloud.appendChild(logo)
}
<?php if ($_['opener-position'] === 'before'): ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud)
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud)
<?php else: ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
<?php endif; ?>
<?php if (!empty($_['top-menu-apps']) || !empty($_['top-side-menu-apps'])): ?>
const topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
const topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
<?php require_once __DIR__.'/_topMenuApps.js'; ?>
<?php endif; ?>
<?php if ($display === 'always-displayed'): ?>
<?php require_once __DIR__.'/_alwaysDisplayed.js'; ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
<?php endif; ?>
}
})();

View file

@ -41,8 +41,108 @@ $choicesSizes = [
$labelShowHideApps = 'Show and hide the list of applications';
$labelReset = 'Reset to default';
$labelDefault = 'Default';
$labelWithCategories = 'With categories';
$labelBigMenu = 'Big menu';
$labelAlwaysDisplayed = 'Always displayed';
?>
<div id="side-menu-section">
<div class="section">
<h2>
<?php p($l->t('Panel')); ?>
</h2>
<?php
$displays = [
'default' => !$_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'always-displayed' => $_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'side-with-categories' => $_['side-with-categories'] && !$_['always-displayed'] && !$_['big-menu'],
'big-menu' => $_['big-menu'] && !$_['always-displayed'] && !$_['side-with-categories'],
];
?>
<div>
<label>
<?php p($l->t($labelDefault)); ?>
</label>
</div>
<p>
<img
class="side-menu-display <?php echo $displays['default'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-default.svg')); ?>" alt="<?php p($l->t($labelDefault)); ?>">
</p>
<div>
<label>
<?php p($l->t($labelWithCategories)); ?>
</label>
</div>
<p>
<img
class="side-menu-display <?php echo $displays['side-with-categories'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="0"
data-sidewithcategories="1"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-side-with-categories.svg')); ?>" alt="<?php p($l->t($labelWithCategories)); ?>">
</p>
<div>
<label for="side-menu-opener">
<?php p($l->t($labelBigMenu)); ?>
</label>
</div>
<p>
<img
class="side-menu-display <?php echo $displays['big-menu'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="1"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-big-menu.svg')); ?>" alt="<?php p($l->t($labelBigMenu)); ?>">
</p>
<div>
<label for="side-menu-opener">
<?php p($l->t($labelAlwaysDisplayed)); ?>
</label>
</div>
<p><em><?php p($l->t('Not compatible with touch screens.')); ?></em></p>
<p>
<img
class="side-menu-display <?php echo $displays['always-displayed'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="1"
data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-always-displayed.svg')); ?>" alt="<?php p($l->t($labelAlwaysDisplayed)); ?>">
</p>
<input
type="hidden"
class="side-menu-setting"
name="always-displayed"
id="side-menu-always-displayed"
value="<?php echo (int) $_['always-displayed'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="big-menu"
id="side-menu-big-menu"
value="<?php echo (int) $_['big-menu'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="side-with-categories"
id="side-menu-side-with-categories"
value="<?php echo (int) $_['side-with-categories'] ?>"
>
</div>
<div class="section">
<h2>
<?php p($l->t('Colors')); ?>
@ -50,6 +150,13 @@ $labelReset = 'Reset to default';
<small><span class="info"><?php p($l->t('Live preview')); ?></span></small>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label side-menu-setting-label-short">
@ -206,7 +313,7 @@ $labelReset = 'Reset to default';
<div class="side-menu-setting-table">
<?php
$choices = [
'Default' => 'side-menu-opener',
$labelDefault => 'side-menu-opener',
'Default (dark)' => 'side-menu-opener-dark',
'Hamburger' => 'side-menu-opener-hamburger',
'Hamburger (dark)' => 'side-menu-opener-hamburger-dark',
@ -234,6 +341,13 @@ $labelReset = 'Reset to default';
<?php p($l->t('Dark mode colors')); ?>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<p>
<?php echo $l->t('This parameters are used when Dark theme or Breeze Dark Theme are enabled.'); ?>
</p>
@ -392,7 +506,7 @@ $labelReset = 'Reset to default';
<div class="side-menu-setting-table">
<?php
$choices = [
'Default' => 'side-menu-opener',
$labelDefault => 'side-menu-opener',
'Default (dark)' => 'side-menu-opener-dark',
'Hamburger' => 'side-menu-opener-hamburger',
'Hamburger (dark)' => 'side-menu-opener-hamburger-dark',
@ -422,6 +536,13 @@ $labelReset = 'Reset to default';
<?php p($l->t('Opener')); ?>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<?php
@ -473,107 +594,7 @@ $labelReset = 'Reset to default';
</select>
</div>
</div>
</div>
</div>
<div class="section">
<h2>
<?php p($l->t('Panel')); ?>
</h2>
<?php
$displays = [
'default' => !$_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'always-displayed' => $_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'side-with-categories' => $_['side-with-categories'] && !$_['always-displayed'] && !$_['big-menu'],
'big-menu' => $_['big-menu'] && !$_['always-displayed'] && !$_['side-with-categories'],
];
?>
<div>
<label>
<?php p($l->t('Default')); ?>
</label>
</div>
<p>
<img
class="side-menu-display <?php echo $displays['default'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-default.svg')); ?>" alt="<?php p($l->t('Default')); ?>">
</p>
<div>
<label>
<?php p($l->t('With categories')); ?>
</label>
</div>
<p><em><?php echo $l->t('This menu is not compatible with AppOrder.'); ?></em></p>
<p>
<img
class="side-menu-display <?php echo $displays['side-with-categories'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="0"
data-sidewithcategories="1"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-side-with-categories.svg')); ?>" alt="<?php p($l->t('With categories')); ?>">
</p>
<div>
<label for="side-menu-opener">
<?php p($l->t('Big menu')); ?>
</label>
</div>
<p><em><?php echo $l->t('This menu is not compatible with AppOrder.'); ?></em></p>
<p>
<img
class="side-menu-display <?php echo $displays['big-menu'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="1"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-big-menu.svg')); ?>" alt="<?php p($l->t('Big menu')); ?>">
</p>
<div>
<label for="side-menu-opener">
<?php p($l->t('Always displayed')); ?>
</label>
</div>
<p><em><?php p($l->t('Not compatible with touch screens.')); ?></em></p>
<p>
<img
class="side-menu-display <?php echo $displays['always-displayed'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="1"
data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-always-displayed.svg')); ?>" alt="<?php p($l->t('Always displayed')); ?>">
</p>
<input
type="hidden"
class="side-menu-setting"
name="always-displayed"
id="side-menu-always-displayed"
value="<?php echo (int) $_['always-displayed'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="big-menu"
id="side-menu-big-menu"
value="<?php echo (int) $_['big-menu'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="side-with-categories"
id="side-menu-side-with-categories"
value="<?php echo (int) $_['side-with-categories'] ?>"
>
<br>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Open the menu when the mouse is hover the opener (automatically disabled on touch screens)')); ?>
@ -591,16 +612,18 @@ $labelReset = 'Reset to default';
</select>
</div>
</div>
</div>
</div>
<div class="section">
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Display the logo')); ?>
<br>
<em>
<?php echo $l->t('This feature is not compatible with the <code>big menu</code> display.'); ?>
<br>
<?php p($l->t('The logo will be hidden when the menu is always displayed.')); ?>
</em>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
</div>
</div>
<div class="side-menu-setting-form">
<select id="side-menu-display-logo" name="display-logo" class="side-menu-setting">
@ -616,6 +639,10 @@ $labelReset = 'Reset to default';
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Use the avatar instead of the logo')); ?>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
</div>
</div>
<div class="side-menu-setting-form">
<select id="side-menu-use-avatar" name="use-avatar" class="side-menu-setting">
@ -631,6 +658,10 @@ $labelReset = 'Reset to default';
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('The logo is a link to the default app')); ?>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
</div>
</div>
<div class="side-menu-setting-form">
<select id="side-menu-add-logo-link" name="add-logo-link" class="side-menu-setting">
@ -646,8 +677,13 @@ $labelReset = 'Reset to default';
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps that should not be displayed in the menu')); ?>
<br>
<em><?php echo $l->t('This feature is only compatible with the <code>big menu</code> display.'); ?></em>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#big-menu-hidden-apps" href="#_">
@ -767,10 +803,17 @@ $labelReset = 'Reset to default';
<?php p($l->t('Top menu')); ?>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps only visible in the top menu')); ?>
<?php p($l->t('Applications kept in the top menu')); ?>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#top-menu-apps" href="#_">
@ -798,12 +841,12 @@ $labelReset = 'Reset to default';
</div>
</div>
</div>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps visible in the top and side menus')); ?>
<?php p($l->t('Applications kept in the top menu but also shown in side menu')); ?>
<br>
<em><?php p($l->t('These applications must be selected in the previous option.')); ?></em>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#top-side-menu-apps" href="#_">
@ -831,6 +874,72 @@ $labelReset = 'Reset to default';
</div>
</div>
</div>
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Hide labels on mouse over')); ?>
</div>
<?php
$choices = array_merge(
$choicesYesNo,
['Except the hovered app' => '2']
);
?>
<div class="side-menu-setting-form">
<select id="side-menu-top-menu-mouse-over-hidden-label" name="top-menu-mouse-over-hidden-label" class="side-menu-setting">
<?php foreach ($choices as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['top-menu-mouse-over-hidden-label']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
</div>
<div class="section">
<h2>
<?php p($l->t('Applications')); ?>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Customize sorting')); ?>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#apps-order-list" href="#_">
🖱️ <?php p($l->t($labelShowHideApps)); ?>
</a>
<div class="theme-undo icon icon-history btn-reset btn-reset--down" data-toggle="tooltip" data-original-title="<?php echo p($l->t($labelReset)); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-apps-order' => [],
])) ?>"></div>
<div id="apps-order-list" style="display: none">
<ul class="side-menu-setting-list">
<?php foreach ($_['ordered-apps'] as $key => $app): ?>
<li data-id="<?php echo $app['id']; ?>" class="side-menu-setting-list-item">
<span class="arrow">
</span>
<?php echo p($l->t($app['name'])); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<input type="hidden" value='<?php echo json_encode($_['apps-order']) ?>' name="apps-order" class="side-menu-setting" id="side-menu-apps-order">
</div>
</div>
</div>
</div>
@ -840,6 +949,11 @@ $labelReset = 'Reset to default';
<?php p($l->t('Categories')); ?>
</h2>
<div class="badges">
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<?php
@ -863,7 +977,7 @@ $labelReset = 'Reset to default';
</div>
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<div class="side-menu-setting-label side-menu-setting-label--top">
<?php p($l->t('Custom categories')); ?>
</div>
<div class="side-menu-setting-form">
@ -950,6 +1064,13 @@ $labelReset = 'Reset to default';
<?php p($l->t('Tips')); ?>
</h2>
<div class="badges">
<span class="badge badge-1"><?php p($l->t($labelDefault)); ?></span>
<span class="badge badge-2"><?php p($l->t($labelWithCategories)); ?></span>
<span class="badge badge-3"><?php p($l->t($labelBigMenu)); ?></span>
<span class="badge badge-4"><?php p($l->t($labelAlwaysDisplayed)); ?></span>
</div>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
@ -994,19 +1115,19 @@ $labelReset = 'Reset to default';
</div>
<div class="section" id="more">
<button id="side-menu-save" class="btn btn-info">
<button id="side-menu-save" class="btn btn-info" arial-label="<?php p($l->t('Save')); ?>">
<?php p($l->t('Save')); ?>
<progress max="100" value="0" id="side-menu-save-progress"></progress>
</button>
<a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.exportConfiguration') ?>" target="_blank" rel="noopener">
<button class="btn btn-primary" >
<button class="btn btn-primary" arial-label="<?php p($l->t('Export the configuration')); ?>">
<?php p($l->t('Export the configuration')); ?>
</button>
</a>
<a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.removeCache') ?>">
<button class="btn btn-primary" >
<button class="btn btn-primary" arial-label="<?php p($l->t('Purge the cache')); ?>">
<?php p($l->t('Purge the cache')); ?> (<?php echo $cacheSize ?> Kb)
</button>
</a>
@ -1018,7 +1139,7 @@ $labelReset = 'Reset to default';
<?php p($l->t('You like this app and you want to support me?')); ?>
<a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan" rel="noopener">
<button>
<button arial-label="<?php p($l->t('Buy me a coffee ☕')); ?>">
<?php p($l->t('Buy me a coffee ☕')); ?>
</button>
</a>

View file

@ -27,6 +27,7 @@ $choicesYesNo = [
$labelShowHideApps = 'Show and hide the list of applications';
$labelReset = 'Reset to default';
?>
<div id="side-menu-section">
<?php if ($_['force']): ?>
@ -41,10 +42,6 @@ $labelShowHideApps = 'Show and hide the list of applications';
</div>
<?php else: ?>
<div class="section">
<h2>
<?php p($l->t('Menu')); ?>
</h2>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
@ -123,7 +120,7 @@ $labelShowHideApps = 'Show and hide the list of applications';
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps only visible in the top menu')); ?>
<?php p($l->t('Applications kept in the top menu')); ?>
<p>
<em>
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
@ -163,9 +160,10 @@ $labelShowHideApps = 'Show and hide the list of applications';
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps visible in the top and side menus')); ?>
<?php p($l->t('Applications kept in the top menu but also shown in side menu')); ?>
<p>
<em>
<?php p($l->t('These applications must be selected in the previous option.')); ?><br>
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
</em>
</p>
@ -200,11 +198,49 @@ $labelShowHideApps = 'Show and hide the list of applications';
</div>
</div>
</div>
<div class="section">
<h2>
<?php p($l->t('Applications')); ?>
</h2>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Customize sorting')); ?>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#apps-order-list" href="#_">
🖱️ <?php p($l->t($labelShowHideApps)); ?>
</a>
<div class="theme-undo icon icon-history btn-reset btn-reset--down" data-toggle="tooltip" data-original-title="<?php echo p($l->t($labelReset)); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-apps-order' => '[]',
])) ?>"></div>
<div id="apps-order-list" style="display: none">
<ul class="side-menu-setting-list">
<?php foreach ($_['ordered-apps'] as $key => $app): ?>
<li data-id="<?php echo $app['id']; ?>" class="side-menu-setting-list-item">
<span class="arrow">
</span>
<?php echo p($l->t($app['name'])); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<input type="hidden" value='<?php echo json_encode($_['apps-order']) ?>' name="apps-order" class="side-menu-setting" id="side-menu-apps-order" data-personal>
</div>
</div>
</div>
</div>
<?php endif ?>
<div class="section">
<?php if (!$_['force']): ?>
<button id="side-menu-save" class="btn btn-info">
<button id="side-menu-save" class="btn btn-info" arial-label="<?php p($l->t('Save')); ?>">
<?php p($l->t('Save')); ?>
<progress max="100" value="0" id="side-menu-save-progress"></progress>
</button>
@ -219,7 +255,7 @@ $labelShowHideApps = 'Show and hide the list of applications';
<?php p($l->t('You like this app and you want to support me?')); ?>
<a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan" rel="noopener">
<button>
<button arial-label="<?php p($l->t('Buy me a coffee ☕')); ?>">
<?php p($l->t('Buy me a coffee ☕')); ?>
</button>
</a>

33
tsconfig.json Normal file
View file

@ -0,0 +1,33 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"include": ["./src/**/*.js"],
"compilerOptions": {
"types": ["node", "vue", "vue-router"],
"outDir": "./js/",
"target": "ESNext",
"module": "ESNext",
// Set module resolution to bundler and `noEmit` to be able to set `allowImportingTsExtensions`, so we can import Typescript with .ts extension
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
// Allow ts to import js files
"allowJs": true,
"allowSyntheticDefaultImports": true,
"declaration": false,
"noImplicitAny": false,
"resolveJsonModule": true,
"strict": true,
},
"vueCompilerOptions": {
"target": 2.7
},
"ts-node": {
// these options are overrides used only by ts-node
// same as our --compilerOptions flag and our TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs",
"verbatimModuleSyntax": false
}
}
}

View file

@ -1,53 +1,70 @@
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const StyleLintPlugin = require('stylelint-webpack-plugin')
const BabelLoaderExcludeNodeModulesExcept = require('babel-loader-exclude-node-modules-except')
const {
VueLoaderPlugin
} = require('vue-loader')
// const StyleLintPlugin = require('stylelint-webpack-plugin')
module.exports = {
entry: {
'admin': path.join(__dirname, 'src', 'admin.js'),
'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'),
},
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/js',
filename: '[name].js?v=[hash]',
chunkFilename: 'chunks/[name]-[hash].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: 'url-loader',
options: {
name: '[name].[ext]?[hash]',
limit: 8192,
},
devtool: "source-map",
entry: {
'admin': path.join(__dirname, 'src', 'admin.js'),
'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'),
},
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/js',
filename: '[name].js?v=[chunkhash]',
chunkFilename: 'chunks/[name]-[chunkhash].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: /\.tsx?$/,
use: [
'babel-loader',
{
// Fix TypeScript syntax errors in Vue
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
plugins: [
new VueLoaderPlugin(),
new StyleLintPlugin(),
exclude: BabelLoaderExcludeNodeModulesExcept([]),
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]?[hash]',
limit: 8192,
},
},
],
resolve: {
extensions: ['*', '.js', '.vue'],
symlinks: false,
},
},
plugins: [
new VueLoaderPlugin(),
// new StyleLintPlugin(),
],
resolve: {
extensions: ['.*', '.js', '.vue'],
symlinks: false,
},
}