Compare commits

...

67 Commits

Author SHA1 Message Date
Henrique Dias
1077d5cd6b Version 1.3.7
Former-commit-id: c76a13cb8d0bf0a360c63c18cbfac378f8888425 [formerly e58788aa3de9e8fb54894a72bdd567c3846bf943] [formerly 77cd4f2feb8ef317cfeeff84d85ffb7eff4d899f [formerly 3cddb26572]]
Former-commit-id: af62db2d663287f662990539be428ae84f1974cf [formerly eaae8679ea0b9daeeba5cd29dd981d60d66e6c1e]
Former-commit-id: 886b61f0af26b85625cf7092ce5e7d4c5e44ed59
2017-10-29 13:33:31 +00:00
Henrique Dias
57cc174b3d [ci skip] auto: setting untracked version
Former-commit-id: ed15673452af4cd3ccf1a1f316efd6a733e6bd43 [formerly 0a9637c464bbae4c5c351f7e6f65dd24197a9968] [formerly e7388628831533d4476ba69c8d0cfac3ae18be14 [formerly 08bfeea103]]
Former-commit-id: 412e1b3bfcdef0037292b7ab3f5a5eeb4e9523f3 [formerly 2ca352a5af95eedadc91ea207833bdb670f1481a]
Former-commit-id: b359f15e105d55a9761aa9bf43c331bad113d561
2017-10-29 13:32:10 +00:00
Henrique Dias
cf61baa273 Version 1.3.4
Former-commit-id: 6d70d81a1967f171a41cecbdad8664fc65d1111a [formerly e273e78de0030dcb2eec730a00f248f1e8dbd9f7] [formerly 37b0ede2385e153153a854112ffab1065bcdd44d [formerly f89b86939e]]
Former-commit-id: 84fc2f9742c2e0011254ee762faf6f087986c6fa [formerly f22c175e96173bf25aa08067533937041152fdb3]
Former-commit-id: 32981c2a1211608d2d5c059264aa5eb434099400
2017-10-29 13:32:02 +00:00
Henrique Dias
2d5cd2d1d3 Fix Travis
Former-commit-id: 435a3bebf2ac1bbae2f1ed94d596956227141358 [formerly 46e69c9783cb07a9ca6b7bd428a1ab54cff2005b] [formerly 36ed5ea293a4d39d161bffed5b18a1d7f1a0d5c1 [formerly 3434090a7c]]
Former-commit-id: d5bc8f96b631af8d440438c1a089e710ae09a47a [formerly f447c0c7031af05129bf7c45b6c87168a365738d]
Former-commit-id: 627ac93b54b236e20d552486d125cef813eaf9e5
2017-10-29 13:12:54 +00:00
Henrique Dias
9472aad877 Fix #262 #266
Former-commit-id: 0acca700ed0a2ced8bf6b5b019aa6ff4774b8ea8 [formerly 0ff934dd107e56f4b7349f1363dfb1b6cbbc7916] [formerly f8b60e60087d754b46ca4dad49a151bec299febd [formerly ae1702c4e2]]
Former-commit-id: 824abe0c6cb5d32c061e8efd4a7f1d3354b3dbfe [formerly 655809d5c670c9de352c473de1fe08303a4c5506]
Former-commit-id: 6ca6f383602241b8a50c13cce5b066cd5f2cf990
2017-10-29 13:07:48 +00:00
Henrique Dias
dd0f3ef144 [ci skip] auto: setting untracked version
Former-commit-id: 7abc56816c4aee81b91bccce14251ed71b02ac0e [formerly 5d1acb37bf487778cb740299a0f25846f49f0ad4] [formerly b97420d631839e9a7a74961403113986d493638f [formerly 26e1e44d60]]
Former-commit-id: c196dfa47127e87efcec64c4cda2e7013e5cdc4d [formerly 5647ffd57b1e049a3e11f6ec37fd2344abfc591a]
Former-commit-id: 3cc55fc8d38f553f0405972dd39da2b0bbb52740
2017-10-15 08:28:41 +01:00
Henrique Dias
44e492160b Version 1.3.6
Former-commit-id: 28c72f8ee9f7cf548dd5f2c2cc564b31ba13ada8 [formerly cd087b92826cb418d4d17ac26a8cfeb408a54919] [formerly bf241b663bdcfc00737f5bb429ef4a669c1a6c2b [formerly 3e9c3ed912]]
Former-commit-id: 2b74d089de06c4fa5d4933e0c11ff8b1afcd2f46 [formerly 9ac185e1ea76c1beec895f6c51109fd137547e60]
Former-commit-id: a27b4e08dc726769b4731abb5865d2e908387774
2017-10-15 08:28:28 +01:00
Henrique Dias
0137b03887 Fix Portuguese language reference and DRY
Former-commit-id: 3954a92011b3c1699236b32373688ce80f720179 [formerly 67bca685bac4e893f836b21f85f4369444f3e708] [formerly 9817c40128b6660be30ea48b5317d07d21b84ca0 [formerly 77df2034f5]]
Former-commit-id: 2681c1f068e8094f0f9593a4c21fc6f84d8a6353 [formerly 26986c2af1193a31e3abec64b829cb29aa97011c]
Former-commit-id: c2813afc8de2e22ff9f3de0f55fbb26b3b954619
2017-10-15 08:26:22 +01:00
Equim
92c9b134c3 i18n: auto detect locale from browser (#253)
* i18n: detect locale from browser

* fix regex for locale matching

* remove debug code


Former-commit-id: 17e550af54ff213d5e2b60f83b374cd962052b5b [formerly 62fb089a7a45092b3818135dd68fac218067ef67] [formerly 733c463d2332307dafd40da5e77c6c9558239283 [formerly 4b84492a11]]
Former-commit-id: 2e117c9e060ac5cd9f80a0de2a4582eef74df6b8 [formerly 5fb6fc086bb2ebeb49a578042240a26a7879a4b8]
Former-commit-id: 7692a4fbb2889acbd7c6ee09ccd01a234998616a
2017-10-15 08:12:40 +01:00
Geno
1737702c7c typo in logging (#255)
Former-commit-id: fd0b7d6887e14b77ab97b323f4e8d1bb51ecda23 [formerly fb4fab794ca27e8f77e0dec71483cec51e4a8fce] [formerly df9da803a4301a8b1e5ed93f1f3fd121b8a3bf49 [formerly b93aed98b1]]
Former-commit-id: d17f1d4be63659781b4254b9ec36737d2f794008 [formerly 27cc8277db82e0ae123d2b1ffe95bed297c6095d]
Former-commit-id: 8137dd86f6f3caef4636416f6370bd686773df3a
2017-10-15 08:06:53 +01:00
Equim
ece52ecf7c Don't expose error (#254)
* share: remove share link when it has been moved

* http: don't expose error


Former-commit-id: c7f1d28117c770006132c75e5950d73aa9d87a12 [formerly f29a0260622644a79ff401263ba4efb143dea23a] [formerly fdd741679f09c72a121076e0a62a0d2a6eafefe4 [formerly 538b99ee77]]
Former-commit-id: 5ec9b62254b0cbc233673e7e196a5a4ece53a3f4 [formerly d26d45418267d11a1a211ba90a8b68b5b9fad714]
Former-commit-id: 57333d74cbac7088c6c527a4fe757af427dedea8
2017-10-14 10:07:01 +01:00
Henrique Dias
a7d6a72718 Build Assets
Former-commit-id: aa40a922e2dbd104bed161fd6564343c64f2fb34 [formerly 16a73ea595678b00ab9690338015746a8f5e5f02] [formerly cf4ef0a6fe12d211a1e905047a3a2cdba12e9c84 [formerly 7f7d536c5e]]
Former-commit-id: cb509a9c9961b4c8d2c6f8f67b37f30b2732eef8 [formerly f7465805945f0c08312c31bb067e3d9546d883bd]
Former-commit-id: cab59e68c27cd7c180a8964889c759efbbff7616
2017-10-09 08:28:49 +01:00
Henrique Dias
3fa9286238 ViewMode constants; default view modes
Former-commit-id: d51ad4b2671c76c3a493daf93fd4fb94a76826ed [formerly 576cfa8ebe9c66c9dbc1c8f79ab8ba7fecbe8845] [formerly 0969e87e3c070763d6e58f1d09f815b772814e4d [formerly 8633e9677b]]
Former-commit-id: 4dad4cf6bb3858122cd4d4c6a574e691788ef9ae [formerly 0b356f24a005c8886bd2165884e363f277456d8f]
Former-commit-id: cadb7589dad74e9bdb47bf3664f98ac672940fe5
2017-10-09 08:26:05 +01:00
Henrique Dias
ad5ff4cfe0 Close #248
Former-commit-id: af79c3515a4a6a6d5b72da1193b888b7cd03b286 [formerly bb0e1f8e966e238feacf3013eccf9b5e711f7061] [formerly 0f38f0df6f964f88310fde0e59f6b3a79b9c103c [formerly 90c2de44c3]]
Former-commit-id: ac03a781bce6530b0512b014c38e8d8c77d8ec01 [formerly 5469e2322432fd1991229f909e69d70966fe8f9e]
Former-commit-id: d17517418e68671237ad845df93b62840038d8d7
2017-10-09 08:14:17 +01:00
Henrique Dias
9aee1ebd2a [ci skip] auto: setting untracked version
Former-commit-id: 89e071320621710d9f24bf4dde8827f159c64d31 [formerly 6d44fe3f3e56b82762bacce64c1682262d4d1a90] [formerly 8e0f92b3205067d4644bf2febdc160d84696a37a [formerly 769bdad12a]]
Former-commit-id: 9e00c24fdca0e1ebe71aceb84624ea0b9fb85afa [formerly a04f2e37805000e19c7dcdb7daafe2396cb8269e]
Former-commit-id: 827a695fce5d7731e58052dbbf4f5bd0e3d6f5c7
2017-10-08 23:48:24 +01:00
Henrique Dias
f8ed1b41d6 Version 1.3.5
Former-commit-id: e064282645a50d6eeeb293577da260c586d4bb45 [formerly 2a9ac206ab79a8f115ed25a3afb595663f18fcb4] [formerly 2ea943c96653266a9533d9e95776955821706ea4 [formerly dd59e3c62b]]
Former-commit-id: 4ec9b9738ee48943830b9045ae40e7c305010bf3 [formerly 7748b70d5109e788ad146b466b3430a822037570]
Former-commit-id: 0981b89cb1d7bc0da43d47ff8900eb1c67301982
2017-10-08 23:47:56 +01:00
Equim
bb9b0dfd2b i18n: Fix typo (#250)
Former-commit-id: c27f105374233923e7691327f7e6f6b9a52077ce [formerly c34be33394c74229a1029edf21d9c83d5c9dd2a9] [formerly 3f5bd5a8457310d034b7fe761beb915caf35ae65 [formerly f0609757f7]]
Former-commit-id: 82d69494e5c421a245ceff0df033529ef488e419 [formerly 910438160faa1e853c91252845443514a066b3f9]
Former-commit-id: 01874c63eb13dc39471c3c2cd06a9b2e99394f46
2017-10-08 23:42:51 +01:00
Henrique Dias
cc2ce884fc [ci skip] auto: setting untracked version
Former-commit-id: d68d28c1e0000f0308b1e84a912f00b8a91f7fbf [formerly 15e0131f25aa82b8bb340e2d58d09c35a4a91754] [formerly 282b0c8d07c82de35fb87927c9da29182efaceff [formerly 9d27c38097]]
Former-commit-id: 9cc06bb0c230f2acac72fc6b4d00e4d8446a5f88 [formerly 5634a96f7b01d6f08a6a46f24dab7a3f03668fd6]
Former-commit-id: c9629add111d645e386461016771ebd1b14b98b5
2017-09-11 09:49:54 +01:00
Henrique Dias
e7e7679002 Version 1.3.4
Former-commit-id: f7727c37ed227e062a34de8b67a5bdeb95caab27 [formerly 9202314bd4a0a0288e3b95c179aaeaaf38997f7f] [formerly c57ba427c5e85f4aff34ccc5476d2c374c93a6d4 [formerly d41715c0c2]]
Former-commit-id: 7aa4c2ee81fa7dd451fef64198335f7a6ee78173 [formerly 4d1553375b1a3c86e9f8de45ef658ff8fe64aba4]
Former-commit-id: 85340361dc98b2ce775d1c41291ff3af165f99ee
2017-09-11 09:49:42 +01:00
Henrique Dias
9a829fd594 fix #241
Former-commit-id: 8578bf0b790ea4b8b5c5da4876fbccd2ead42d3c [formerly b67d35502fb0c9a3c57226b812dd2b869c5fcae1] [formerly 506eb279c974a86b232be57f87a11ec283b3b742 [formerly f658394dd7]]
Former-commit-id: e9565ce6a4ef229943f132fb6e05c5ec853447ed [formerly d0b2c24f6df2a74c403ded829cfd0746659e7d5f]
Former-commit-id: 2f4afe92081915821dd5b2fe745faf1492bdcabf
2017-09-11 09:46:17 +01:00
Henrique Dias
624d61930c Add ReCaptcha to main
Former-commit-id: 06bc7079f6d939e5531a3d9600052f979adac86d [formerly e6d8fa4418ccfa8f0163530647099432a936d4ee] [formerly dbd0cfc3770972afdf7aae8121d3af50681d55eb [formerly 879ad7b518]]
Former-commit-id: e6706456ba300c501ae66664596b5709e45d87df [formerly 98238eb61781a545ee4bac512a4f02257f4cf165]
Former-commit-id: 30bfb2b201ecbbd1ac2cf58cbadbe82daea793cb
2017-09-11 09:23:59 +01:00
Henrique Dias
ee30e7711f implement recaptcha on login
Former-commit-id: d7495b6fff4a99a8d155a3be87b15535a74a1305 [formerly 5b3a544447cca0d1cdcb6c87ca94f450a5493506] [formerly b4de1a4f5d4dd295c98366ede2b87bf2cb7918f9 [formerly 002f8066c7]]
Former-commit-id: c0e5d38111a99f8e3e71fb5db86e19b7ba44ec48 [formerly 1b5e454263ba64ced95c6d4b51f5f32e66f74758]
Former-commit-id: cfb17a53fc86d0071fba91503502444f5f10a0c7
2017-09-11 09:00:59 +01:00
Henrique Dias
6e5116aa27 [ci skip] auto: setting untracked version
Former-commit-id: 4f0cecd21f2f1fec680773c6242d6ab9687384f3 [formerly 1dec18820a98ad1ffc39eddff12167b4d1564254] [formerly ffdde8ede3a4d934d492224a046b654518cffbe9 [formerly a61329843c]]
Former-commit-id: 865c60a764807ea8d6781ac4bc95a4c8ddd9ef56 [formerly c9799373cf5e9637c78faffb94117c355a3a4f8c]
Former-commit-id: 070a498ca4c9d6da545c9b10d60e0f84e7e56889
2017-09-10 10:14:35 +01:00
Henrique Dias
34acffbb7b Version 1.3.3
Former-commit-id: d3126ad3137ddb4270199227c7ac4a0aa248b117 [formerly d8de0edac44991fb25fc3d58bc5fbf34ca51e14c] [formerly 65f8bd6a9c9a15f2182f7d9d0d0317df8a7a636c [formerly 53dedb7f5e]]
Former-commit-id: 473f94e0b181bfe06eaf73648775af5e89b03032 [formerly 9efded6125dc4ef0384f441933139847513fde51]
Former-commit-id: 8ada7c0571decba2abb0e721650401f040a6fb86
2017-09-10 10:14:22 +01:00
Henrique Dias
eb6f26c191 Add view mode on users created on previous versions
Former-commit-id: 6c3e320b2004beb8172e966e1a7f1885e33ed20b [formerly 829930444312dcfa1e144b3631c56632f6094d01] [formerly 68ae2ee9df5b3908fb30a07094f7d464818a0d52 [formerly b651d03ea9]]
Former-commit-id: 3af8ad9075641ca34504e33a2eb363eff7c6c63d [formerly e2873038181f9cc6f8d1c465aa5544cf98eb735d]
Former-commit-id: 584b5c3452545d2a6361ade02e093fe5596a52c3
2017-09-10 10:13:10 +01:00
Henrique Dias
06f3e9744a [ci skip] auto: setting untracked version
Former-commit-id: c7af338241ab6cac5e5e6f5683a8394e30a1ff6b [formerly a28f7d51e97810acd69616c22eb578f2d65e8528] [formerly 6b372592bf387d73b10e42a29ab75a5b99647d65 [formerly 750862b17d]]
Former-commit-id: a6a9cedfc2f7507080b39c665d97e8b586a746de [formerly ceeb7a7fc060245d21d169eb22b6b099fc4c384d]
Former-commit-id: 5dcf4b4c5a1caf3b482baf7f0f8aecb1b69a9263
2017-09-07 18:28:22 +01:00
Henrique Dias
cac5413424 Version 1.3.2
Former-commit-id: 90d5ad658b45bedcba302eb5ba31bd419742b6da [formerly 7fbf1df2fdf4319d6079a844d03fc68d0e6272bf] [formerly 8c9bc269c306ba49250a959200b86f1035ff5565 [formerly 6fce037280]]
Former-commit-id: 6da196d1aa360654f7fb61f02d448e6d278d52ff [formerly 272a9fd5dc6092af922aac72040fa0fbe4ecfc52]
Former-commit-id: 07fe419998104c357feb2376e61a9d3c3a65273b
2017-09-07 18:28:10 +01:00
Henrique Dias
037efc2eb8 bugfixes
Former-commit-id: 38f7bdc4de8a29be11376e2321aaedb027b84a57 [formerly 72daa72c1b48182002bf81ac374b54dad2bae465] [formerly 8498a21191a043432e0a857cbc4aae49761bfdbf [formerly 2ad805d793]]
Former-commit-id: 068d502148b08cfd8dc627739847888f018f4417 [formerly 882e3aff3891329a62374c448e839ba41edebb45]
Former-commit-id: 7b71062e2c6e86c5e1ce36daaeb265972c941ca5
2017-09-07 18:27:30 +01:00
Henrique Dias
7de22b53b8 Settings styles; close #228
Former-commit-id: b564ba4e357b2d4b18c7f9407395894eb5e18159 [formerly 84ef220a2b10b8ed501c4499ef03d99acc148546] [formerly 00056633e5c2e947201a2dd1ad3bb937821edf61 [formerly e3212cd076]]
Former-commit-id: 866b84d788a5f8a9767affdef17806dbac984db6 [formerly a22d95ba5726dd5ea757410249bef37654213a68]
Former-commit-id: 4600591829c0a41c5de9defc3e30fbac28815e25
2017-09-07 18:19:29 +01:00
Henrique Dias
ae19731015 Merge branch 'master' of https://github.com/hacdias/filemanager
Former-commit-id: cda66995085b2fe15ba1327f5f21d6cb1e7fbad9 [formerly 1e91b652c2c9ac12f7148a5fcee6826823f861ae] [formerly 6e9c1d23074a4ef51a8342014600a64421e37411 [formerly 35742fe91f]]
Former-commit-id: aaf02c9e8fae32fe0507d4375bce87a97637ee0e [formerly 4b2b1e3a10edbe38434ba663d6888cc462afe749]
Former-commit-id: 02ad43301dbc1d5dcfa4e9dd6aca07689d7df378
2017-09-07 16:39:03 +01:00
Henrique Dias
8aa0797019 build assets
Former-commit-id: 8ac0fd77c313bc4527531ba8fcfbab0723108d4e [formerly 83f9d8ce20bacf6259216c0d902957e4c2b9d798] [formerly 04e9fd8ecdf88a44f562da449ff08bb16c108f5f [formerly fcfc28d09e]]
Former-commit-id: 36785e0d78ac8f7af84f4a1071d4bf226eac2366 [formerly 874b8426ee0aa485fe29b962c7ad6e01d90758ad]
Former-commit-id: ed6bd42a0bf059265816edb0085abbc9fbfda4dc
2017-09-07 16:38:58 +01:00
Maxime Daniel
17b3a403a5 Add new triggers and improve environment variables (#238)
* Add copy/rename trigger and more environment variables

* Add after_upload and after_delete trigger

* Fix scope since merge with master

* Add new before triggers


Former-commit-id: 65f7d47840980e5e9f330ae29053b37f2b98bcdb [formerly 9ec3d017d7f072d1bce2492c3a3e9839aab679a0] [formerly 2359047e3b51594ec705f2baf30b73f3bfc4c6cd [formerly 51295f999d]]
Former-commit-id: f128ee9a69ee280f0488aeaa6c8d86337427bc80 [formerly 311dd0b0207a58427d51a07244c3b0ff310dfc6d]
Former-commit-id: 734321b412e9ec7a5d848f9579e134ebce58145f
2017-09-07 16:38:32 +01:00
Henrique Dias
819d511690 materialdesignish
Former-commit-id: 5624723eeb939734902eeaa6c2f132c4beffa911 [formerly a83456d3eda5175e2941b2deeb58b5da323ff678] [formerly a6b3ac7942dbf48d7d9b3f8db5e5041a93143f19 [formerly 4d6b54c63e]]
Former-commit-id: d03621c16b2c892701d678361b6c0a7d5dbec620 [formerly d818b6751e035f283e1c8390d7993a33a459a7dd]
Former-commit-id: 3539ee68532135467daa2cc175482769b1efb592
2017-09-07 16:37:11 +01:00
Henrique Dias
30cfd06e3d change location of database docker
Former-commit-id: f4b3c8ffe4d6772abed3064173ba0ea8efaa806a [formerly 4792bef0a22c9a132ef1a8d239e92bfcb319c38e] [formerly b0de425e325346a805635a3907f3441556ece17b [formerly 6d2fab77e8]]
Former-commit-id: be1ed94475486cf7d44b339f11b232e284efe8b9 [formerly 167c46931b5751dc7455551cadcc075f646742a5]
Former-commit-id: e8ccb7b598697255949abdcfd6ea760fd2e401f5
2017-09-07 14:19:05 +01:00
Henrique Dias
c5558e6e41 Merge branch 'master' of https://github.com/hacdias/filemanager
Former-commit-id: 068b3d896b21bf786e9a2b7758eece8fa5071058 [formerly 7d80cfb73a7481dee4c95c0f3c39a95d13fd2f7c] [formerly 2b4adf5432b9fd7bb49b244955676db4ba6c5fdd [formerly e7bdba8948]]
Former-commit-id: 4f37bb0dc50a5acbf8956f0ef19d4ecebc64a534 [formerly 4eb5ba6be4bc857fe05862485d76e060bcf37fe6]
Former-commit-id: 98af155a74f74287f36fa30d29b8036c73fa1302
2017-09-07 14:18:10 +01:00
Henrique Dias
c3b3099ebb 1st phase - Global CSS
Former-commit-id: 508c4ab746f994bb3f4f5e86ff1ca5e6dc873f3a [formerly 8a29c22f817f54abefe21116e46f21b24306b6b8] [formerly e23c1a85571f61877b67656ef361f1c15acfcb3d [formerly 67fb6f8a78]]
Former-commit-id: 71e9024cf9107ef4e40f95bd388068fe052ea4f0 [formerly 7861ffe8d20f0777ae48c4f159efd7e32b2204d9]
Former-commit-id: 7db04ac5016d182f13ac56911d0a10871fb261f3
2017-09-07 14:17:56 +01:00
Ricardo Gonçalves
b1d47daa69 Docker image built from scratch (#237)
* Docker image built from scratch

* ldflags aren't need anymore


Former-commit-id: c527464833492254cdd14ced0f182fd78fbf4dda [formerly 5d99678c525e565d5b39f3bd645906b4e997752c] [formerly 80463ad3ae8e60456d5725277125c800bb2d6f50 [formerly 55789107e0]]
Former-commit-id: cea3a7799a9bd767008cb1b854314f6414892aff [formerly e682f6a01994537199f8c178de7526b961591d2b]
Former-commit-id: e76349c3eef778dc5913fb7128ff2b69907e9433
2017-09-07 14:15:25 +01:00
Henrique Dias
f51e2d5ba1 fix #234
Former-commit-id: 7e0b0a321a0bf352eb3530fc8d1250ba04499c87 [formerly a431ecc1f8f4d79b6ef98529ca38b7a00ba332d2] [formerly cc09c86c0b996c5c01bd4b7e65671e545e3c1828 [formerly dd7cd110db]]
Former-commit-id: 7bd178d355de3228aa03ec3cbb814e52588ea7df [formerly 35095c2b6728944bea8173c3ea1904c632ab30b9]
Former-commit-id: f2e8e8539a5ffce98ea186f42fd183710bc2f9a8
2017-09-07 11:42:17 +01:00
Henrique Dias
386974657e Build assets
Former-commit-id: 3ac594e7a4f3e806f56ed23790a1a195c87d941e [formerly 6e6b55d1430bddce55b701c04da53c1d746b4168] [formerly e3b0d8c63cf8bacb88c90d2f4e1c5163a448e473 [formerly 59b99ee298]]
Former-commit-id: 5d32e7939ed0941e60ec56f5a7f21fd78216706c [formerly 9c8c8a52a0633de356322fbdc0c54a9f5395daaa]
Former-commit-id: dbd51a43c6e8bc3df50ae57aa98e60bb611ab066
2017-09-07 10:43:32 +01:00
Henrique Dias
f7858cd719 Default view mode
Former-commit-id: 9212e217514046246a2bde10319763b05998b223 [formerly 8643e0ba3b4246ac0320b5d96dee4ba95e74a346] [formerly 77adb69159dce9cc89b631107340812916a2e279 [formerly 5daacf0298]]
Former-commit-id: 1a264e8a297e2a5146db53cd6cc40b8baba778de [formerly 607685afb5b6c89703c0b6d82b3dcec07eb22bef]
Former-commit-id: 1732f02d1641236402976ae182ee03f7b936d0c1
2017-09-07 10:41:01 +01:00
Henrique Dias
b355a5c058 Close #232
Former-commit-id: ec2f7562e0830ebb98bc7b4d997d74f2e6d685a6 [formerly 281a652559131d195b76feefef5cf0303d312b1e] [formerly 11a192c2d6f5d9667c55fdcf3f028391f70f6793 [formerly d3d3cb3d4f]]
Former-commit-id: a55ef038e404c2e9b97a802bfbf562fac02a98cc [formerly 033ded413b4f7bd21b7ff60feced6b590203cc16]
Former-commit-id: 6fc7fedc5fc087790102a07c3db3060e82400411
2017-09-07 10:29:19 +01:00
Henrique Dias
50758b53f4 Autoplay music and videos #233
Former-commit-id: e6305724cc9b53b764ffc199cb0b114e4fa63984 [formerly 36e7978a12f653f156c01ee11c2f91d24f7a7ca8] [formerly 9927ed89f8c30a24141865a3a7a74ffa79180853 [formerly d555cc44a0]]
Former-commit-id: 42efd044dc45d99a8fcd16c29a1b903b54f99a9b [formerly 92f6b43d0420b782defa61c34d2eb5ae9e9244bf]
Former-commit-id: 8fa9ecdbd6cc57a0513c9a72b5ab0b029839af41
2017-09-07 10:07:24 +01:00
Kyle Bai
f9902d2bdb Update zh-tw translation (#236)
Former-commit-id: e2e8b607eaacbcea36a8d3389dd4502158d2ac8a [formerly d47475c6aab73ad3c0ddb08e90664a0d3d2d9547] [formerly d347499f05216a64cd8af1075525ca6b3b7c3c41 [formerly 4d1b2cbb14]]
Former-commit-id: 229b92d7ce1143d6f9768ef8f52b18f5ead937b8 [formerly 673c0452c0d7a677a7c6fbbef941e47701bafe47]
Former-commit-id: 7e8d93b3114d545139cb2f436cf16bdd481713c7
2017-09-07 09:41:03 +01:00
Henrique Dias
b1512d2b66 remove served with from translations
Former-commit-id: 8fd3db2a2303fd77e51230e4ca9336013021d55d [formerly 3a6d99cad26160eda920508962ffa4f23fe2acd4] [formerly 969fef4feac9234ac7d165e4b8c2d572cf41e493 [formerly cbba245f84]]
Former-commit-id: 85af99ac3e6c48967e1c5d11e0fc6403492dfb0d [formerly ef7e5d4f0b655ca32783c21a6678f5e2c6ff8a1d]
Former-commit-id: 88024e6c39480df5799d892f1a7517b5345b7e22
2017-09-03 10:54:48 +01:00
Henrique Dias
1cfd31756d untracked version Sun, Sep 3, 2017 10:51:45 AM
Former-commit-id: 3f9a066d3456fd0435b5df19a6071f403761c363 [formerly 7ed9f0a686c30d97af32dfb7fa35b1f4dbbcc9ca] [formerly 8f473d98da1799211e7e7fd4e26dc9481297ea7d [formerly c60e7b9c0a]]
Former-commit-id: 732ae3300efa4444b06406743f9b8cce813e6b99 [formerly 465966ff48d99ca33496ff9dcf2d51e375985d0f]
Former-commit-id: 8c61fc2054cbecd2f0b7354884634f4b73f6e77f
2017-09-03 10:51:45 +01:00
Henrique Dias
9152c77543 Version 1.3.1
Former-commit-id: f16b67a8bc80baf5ed8e6e3e4764c0219429c681 [formerly 1d264f27c55881673c8cf7597c49859975e05312] [formerly 4246582d7a7292c037733c0c0f946312241e4bfb [formerly 17cb3dc1f1]]
Former-commit-id: 61269ea3228995bcd72b721529b25ad0cc94bd36 [formerly 3cce1019691228714433106d6e6c83490f36217e]
Former-commit-id: 6d51d1908e6db75ed0e614e273ac108aa9774d79
2017-09-03 10:51:33 +01:00
Henrique Dias
c236db329f Add FM version to interface; close #221
Former-commit-id: 0d8cdc850ea970ecb73bb6ce5af85ca517dabba2 [formerly 9adaffca71250d6e350914e90965ea07198be2f0] [formerly 4c982e090fe2809829c35d574b75023ec384ba26 [formerly 59bd6e97bf]]
Former-commit-id: 959919b2c265757a9dde9f34ae06373468ebae0a [formerly 120c5d413404a70885ea597d93469cc3b93bf26a]
Former-commit-id: ebf8abfcf0003babcad11185da96dede783d25a9
2017-09-03 10:50:49 +01:00
Henrique Dias
bf6e0abd96 untracked version Sun, Sep 3, 2017 10:45:00 AM
Former-commit-id: b27d1ac3dd03bc3c6e4bafc9947d57ec8ff447b8 [formerly cc8278a69ea0ff56f370fda951fbb504b13d9938] [formerly 2553bd5a8c7f8c621080d7d50a89befb1aeff4b3 [formerly 011d5aee46]]
Former-commit-id: 5e67474dbcc85cf6fccefbbc8d566a3aaa4477b9 [formerly 63293531e894499121f1a845311877f62c5addfd]
Former-commit-id: c315ae84528e06a285e01d0cca740cec071d4a87
2017-09-03 10:45:00 +01:00
Henrique Dias
8d8c756233 Version 1.3.0
Former-commit-id: 77f5b5b6d73cf979c074d4390a55b14dd9fb8c39 [formerly 4e77a8d13ec4233f1b38ec9b26e7a42bc6f4a9c5] [formerly 5d9600a5fd5b8044ea0a624570be5881740aa3f0 [formerly a4584e7a55]]
Former-commit-id: b8832f925ca7d7daa162a46b87c03b2fe32f67cf [formerly e109aaf234b60cb046c9c6dea2d96089581066a0]
Former-commit-id: 8916c596e729dc1d887da2135fe7cb7e997b03a5
2017-09-03 10:44:49 +01:00
Henrique Dias
d5f0471ab7 untracked version Sun, Sep 3, 2017 10:42:09 AM
Former-commit-id: 682aeae0dcaeac554de9fe24e50a42e53011cd76 [formerly f6147413fe2dc33d356fedd50644d2471e139871] [formerly 1d90a78470bca90eba6c0221fb06af2a10b7cfad [formerly a3f06b526a]]
Former-commit-id: ba046d6e696866a4c32fb33fa4de545aae29d91e [formerly b2e130a7078e4ac95dbdb37911ab6514f36e565c]
Former-commit-id: e13622c99901155e1d2c4fc67b75b320bd3ab1db
2017-09-03 10:42:09 +01:00
Henrique Dias
f7aaae3f63 Version 1.3.0
Former-commit-id: 474be62198116810d28ffda109669b686b66d2c3 [formerly a2c2f34340b407679035f03a41cf75f6b75f80ec] [formerly bd04b4c765993602859a3eb5e72f9761ec16901f [formerly 6b14dc336e]]
Former-commit-id: efcb629aeecb7d61f4db93be32ae9b574d9385bf [formerly 951ea5271bfad7526aec3da1ef4afdaa7004fe58]
Former-commit-id: 1e0c3c13dc7ea0247c0a2909b79f9417e1cbe59a
2017-09-03 10:41:56 +01:00
Henrique Dias
2515819026 Add version stuff on root
Former-commit-id: 4dcf3117e274fe2b656af6fd2ee98db386143620 [formerly df3bd216ef1020e9ca6a0ba57ef1c4bad5a6e04d] [formerly e5eded7248098655cd60112a6ec5fec06fd99ff2 [formerly 044575d2af]]
Former-commit-id: 8fe1a523af24c490eef9d7cd2b1bc3de656002de [formerly 73aea3062fa487a5c6c89f8f04bd86c177c783f3]
Former-commit-id: 6fd9e5260feacfe6b9d96515a7c3ab28c31f17db
2017-09-03 10:41:14 +01:00
Henrique Dias
7ad727d27d Fix #227
Former-commit-id: f1bc07c44f53df9cd1d67b870c5bb477e341abbb [formerly 4d7dba923461b33e4fab40c912458901d2295f08] [formerly 574b5b5b364719ea1939ee232e598f12697fea2f [formerly 96a5226076]]
Former-commit-id: a2262d2112a5d1ccd094d9077233d043660a9100 [formerly 73d2cf6e74cc635bf40bc4b89204c754d8c21da2]
Former-commit-id: 219e96d7c4b1da662643e48dbfa6e97468d9c0a9
2017-09-03 10:06:25 +01:00
Henrique Dias
adbff03274 Add tabs on settings. Close #220
Former-commit-id: ceb274e0e526378d5ab449470c122f11789200dd [formerly 63e043d75c173243116fb35ba389141e323e7606] [formerly da9fc0747c65a3fbdeb032a5e7e3b9e146f409e4 [formerly 9a9b41dca8]]
Former-commit-id: 2a287ab5251869f0e2e2fb836268fb2b1682b81d [formerly d7201a4b07230d32422940250bf0e01b430dde9c]
Former-commit-id: f8f4013fe9eb03ce233738e51ec2c967753b29d7
2017-09-03 09:54:51 +01:00
Henrique Dias
6b8a65382f fix bug
Former-commit-id: acb5afb206fb81d285be03b7fa93dc4e8b32fafb [formerly ff9425ef412822c13f78ddb04bae862ef0f4254d] [formerly 78a8707fa8eef88ddfca44ea61d1409326dc2183 [formerly 359e311da1]]
Former-commit-id: fa4a813c9bddc8b4ef88c3cd24b9f3fa84cb5f8f [formerly d18bd2c49ad25547fb2bb66519221b6d7fc72849]
Former-commit-id: 0a6e241fd1ef05bdbed545aeef76b442468710a7
2017-09-01 18:18:00 +01:00
Henrique Dias
4f7e6cbb52 prefix url as a flag
Former-commit-id: 837bddec0e006860889cebfa74e476aab7f1b04f [formerly 681933b995c0d65da501270f677c6dec358da62d] [formerly 7db51254915e0eeaf7e7098fb5ee79425aae9514 [formerly 3fde3c716b]]
Former-commit-id: 45535dfac3fbb2cf605d107bcb1887bfc3809cf2 [formerly ee5d2e0d7df8606566988854b0de22303a32748d]
Former-commit-id: 51378e5b5a6325938d00416c200053f772ca0b61
2017-09-01 17:48:15 +01:00
Henrique Dias
40fe081962 fix error
Former-commit-id: 6f9d7c0b4ee819f737395ec1b721a88be1e6c5e6 [formerly 0489f9f724d1165bf63c45df84398f9631ed9833] [formerly 5a3ffb16e3a801dd811b3d05ce853413d1da2ae6 [formerly 5c2166bc15]]
Former-commit-id: 4672f5e091c847482af0ae4741bcb809134a1559 [formerly 96ecde75a3d61b9cc1e77eae6254166ec53b902c]
Former-commit-id: 683218b398c699cd0f6ce15bebb6385d20eee390
2017-09-01 13:26:04 +01:00
Henrique Dias
ebf1325126 build assets
Former-commit-id: 5fd962f1a14d288995f97fdfd22480c0af54cf80 [formerly 1642bbafb9bdb91a58a5fb1532b1168894b5574f] [formerly ad423b6250b27f4eed0d9cbdbe3f09155e343ef0 [formerly 0ffb1fbe39]]
Former-commit-id: c3c687df2340c41f44356cd844a0b5ca0d779f71 [formerly 7238fdf7c28d58a734d4d6d8d71bd47456064dbc]
Former-commit-id: 5be42b6cb65f638508133c56fc6775d71e1dacca
2017-09-01 13:22:35 +01:00
Henrique Dias
ea1b9febb7 Merge branch 'master' of https://github.com/hacdias/filemanager
Former-commit-id: 79aec32eb9ac9b7e75ad00689990a865d5fed5c7 [formerly 96b947bf637adadc04409075b715ee85e4c7376b] [formerly 384abd27c0d7d22e67fd70b57c764b2dcbe69611 [formerly 349b3d02e6]]
Former-commit-id: b58b2e6bc0a7e90a3a77054c686c98f2cfb96978 [formerly 8a6ddadf8af01a29380dad9fa6ea4ca035aa8043]
Former-commit-id: 31856ee921c1fb7d77ba7704ea0af5e2d58ff952
2017-09-01 13:20:11 +01:00
Henrique Dias
f3edf63fb2 fix #226
Former-commit-id: 7a2d0e67b2320af58b04da5e5834c0ed6a0ec137 [formerly 468e34a7bbadffdfc268c072b4460c99beb066d9] [formerly 7a962e989a213218868b02cfdd7c289ca0b6831e [formerly 2a5aa21b8d]]
Former-commit-id: 2f443bbdf9518e66136ab97cf8c2f7e434a65f7b [formerly abcf5d2dc1aaa8b034bbe7b958cdec14feaef1b4]
Former-commit-id: 22cf31bc4973b51fbf9849f537e0191c65bf327a
2017-09-01 13:17:10 +01:00
Equim
f13e7ba940 Update locale (#219)
* i18n: update locale

* i18n: update locale


Former-commit-id: f2e21d0882f071f86bc3c225067313e54bcab1f4 [formerly 8cf56e48e3c40cb7bc1f9df58daa14ebc1d4d459] [formerly 48628588159737a35937da4511c1680600c97f51 [formerly e910f8580a]]
Former-commit-id: 2b0669041ae6f9c48d8163f3e20f69636a9ff291 [formerly 0a23672f8298176a8a03564a20d978225ef1bb6b]
Former-commit-id: 6d9a6057a6c67d645edf3fd617663afa1f42f977
2017-08-28 19:12:35 +01:00
Henrique Dias
3ebe219e96 Option to lock user's password #215
Former-commit-id: e4f0afef51c437bd55c58f12f2f45ce8e8c84bb0 [formerly 549235edf9d6c43d1454a8a00d7b6f832bb8a3ca] [formerly 12d099aa44bff7d995b05680e405d8040f1e1850 [formerly fc9ca4f6a4]]
Former-commit-id: e9666db20e2b473095f21c03d59f2a8fbf07929e [formerly 87ee9eb83daed5180c6a3714c0ddc861668d747b]
Former-commit-id: 5a15b05320c1eb28324e50cad7ca980d3eebcb02
2017-08-24 14:44:53 +01:00
Henrique Dias
d838856711 Popup on copy link.
Former-commit-id: e483df4402733b102d11b10436ff74aad11dfa7c [formerly 6d761c2ee838a9766f755b6c54cdc2ca388b5934] [formerly 1365e9e067af021ad0c680bae3af963dc4a90b28 [formerly 889871ec0a]]
Former-commit-id: ba443a90fded4501c0a6872eb293c14b2923c627 [formerly d21c6b9ab41869d2b10aa99853bc5b6931b63d96]
Former-commit-id: 7c19b231861797c62dc35c1e8a28f4ceeb8761c7
2017-08-24 14:13:29 +01:00
Henrique Dias
610d55c26f Fix override.
Former-commit-id: 515874171ed00bf70db40b2fc302d479d18fe93a [formerly ea9e47252daae3fb211150e2c3ab67ac97067a32] [formerly 2b0378600c957da5e2610f3cf8e82dd079d11f65 [formerly e285195fe1]]
Former-commit-id: 05d03bb250d4bc6bc4b3dbaf388f79a682f09841 [formerly 4ef3ceb2dd3862011a09ae39fbf4a78b889100e4]
Former-commit-id: 448224f282d6b14678f8d5d6629cbbe44ed19644
2017-08-24 12:33:54 +01:00
Henrique Dias
ac044016ee Implement SHIFT+Click to select range of files. #206
Former-commit-id: 15da15e0588d5977b42f71b8c96980248db73889 [formerly 95e464410bb619f4ff70b21d846e937bf9355e8a] [formerly cae63a6f6de24229ff04392fe16260c0d9045e8c [formerly 297a52e606]]
Former-commit-id: de9d291dba6c2e2d750c519f1fcbd59c34538ab1 [formerly 4fb3fcf8ba5726f6dde56fcbd8c1457799539a8e]
Former-commit-id: ad1568f33fa927dc750515856950b95b588d9f68
2017-08-24 09:14:26 +01:00
Henrique Dias
58af3461a8 Fix #216
Former-commit-id: 6d4b0674327746ed46fa4397f2d3ab01b8579bc6 [formerly eafa0d3effab4921e1a57de23f5a189bb75dced9] [formerly db40d686eabe4b78d9a5e8a6b1c20b623880d986 [formerly 9301966eb7]]
Former-commit-id: a148dc60ea64d054567f6d9b5d0c383893851da0 [formerly 02af0aeb73dd88cc157e5e21a9e9dd3658316d2a]
Former-commit-id: 1d4e9c50e20d3689f39224308bfadbe6097f5664
2017-08-23 11:05:54 +01:00
Henrique Dias
4191a6f9e0 Fix login issues regarding basicauth+noauth #214 #204
Former-commit-id: 5c150c622724e7d2c9a9630f60325a5a246b7526 [formerly 6ff5856dc0ab40dbb9a9759c0c23de74a23d2531] [formerly b41c082f0631a33648bc50e81f38d7dbba612d8c [formerly c95b1aaace]]
Former-commit-id: ead207a0e5405ccc0641b3705875b8846846060d [formerly 7c1221485698d524ef679d09166cff7549f7b00a]
Former-commit-id: b01bdc787983c2c48394ef9e3eabed9137c1f31c
2017-08-22 15:25:54 +01:00
74 changed files with 1872 additions and 1238 deletions

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode

View File

@@ -15,7 +15,7 @@ install:
- go get github.com/tsenart/deadcode
script:
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./...
- gometalinter --disable-all -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./...
- go test ./... -timeout 30s
after_success:

View File

@@ -1,7 +1,7 @@
{
"port": 80,
"address": "",
"database": "/etc/database.db",
"database": "/database.db",
"scope": "/srv",
"allowCommands": true,
"allowEdit": true,

View File

@@ -7,16 +7,16 @@ RUN apk add --no-cache git
RUN go get ./...
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
RUN go build -ldflags "-X main.version=$(git tag -l --points-at HEAD)"
RUN CGO_ENABLED=0 go build -a
RUN mv filemanager /go/bin/filemanager
FROM alpine:latest
COPY --from=0 /go/bin/filemanager /usr/local/bin/filemanager
FROM scratch
COPY --from=0 /go/bin/filemanager /filemanager
VOLUME /srv
EXPOSE 80
COPY Docker.json /etc/config.json
COPY Docker.json /config.json
ENTRYPOINT ["/usr/local/bin/filemanager"]
CMD ["--config", "/etc/config.json"]
ENTRYPOINT ["/filemanager"]
CMD ["--config", "/config.json"]

View File

@@ -6,6 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="base" content="{{ .BaseURL }}">
<meta name="staticgen" content="{{ .StaticGen }}">
<meta name="noauth" content="{{ .NoAuth }}">
<meta name="version" content="{{ .Version }}">
<meta name="recaptcha" content="{{ .ReCaptchaKey }}">
<title>File Manager</title>
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
@@ -22,6 +25,13 @@
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff">
<script>CSS = "{{ .CSS }}"</script>
{{ if .ReCaptcha -}}
<script src='https://www.google.com/recaptcha/api.js?render=explicit'></script>
{{ end }}
<% for (var chunk of webpack.chunks) {
for (var file of chunk.files) {
if (file.match(/\.(js|css)$/)) { %>

View File

@@ -1,18 +1,74 @@
<template>
<router-view></router-view>
<router-view :dependencies="loaded" @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'app',
mounted: function () {
// Remove loading animation.
let loading = document.getElementById('loading')
loading.classList.add('done')
computed: mapState(['recaptcha']),
data () {
return {
loaded: false
}
},
mounted () {
if (this.recaptcha.length === 0) {
this.unload()
return
}
setTimeout(function () {
loading.parentNode.removeChild(loading)
}, 200)
let check = () => {
if (typeof window.grecaptcha === 'undefined') {
setTimeout(check, 100)
return
}
this.unload()
}
check()
},
methods: {
unload () {
this.loaded = true
// Remove loading animation.
let loading = document.getElementById('loading')
loading.classList.add('done')
setTimeout(function () {
loading.parentNode.removeChild(loading)
}, 200)
this.updateCSS()
},
updateCSS (global = false) {
let css = this.$store.state.css
if (typeof this.$store.state.user.css === 'string' && !global) {
css += '\n' + this.$store.state.user.css
}
this.removeCSS()
let style = document.createElement('style')
style.title = 'custom-css'
style.type = 'text/css'
style.appendChild(document.createTextNode(css))
document.head.appendChild(style)
},
removeCSS () {
let style = document.querySelector('style[title="custom-css"]')
if (style === undefined || style === null) {
return
}
style.parentElement.removeChild(style)
},
cleanCSS () {
this.updateCSS(true)
}
}
}
</script>

View File

@@ -46,7 +46,7 @@
</button>
</div>
<div v-if="!$store.state.user.noAuth">
<div v-if="!$store.state.noAuth">
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
<i class="material-icons">settings_applications</i>
<span>{{ $t('sidebar.settings') }}</span>
@@ -59,7 +59,7 @@
</div>
<p class="credits">
<span>{{ $t('sidebar.servedWith') }} <a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a>.</span>
<span><a rel="noopener noreferrer" href="https://github.com/hacdias/filemanager">File Manager</a> v{{ version }}</span>
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
</p>
</nav>
@@ -72,7 +72,7 @@ import auth from '@/utils/auth'
export default {
name: 'sidebar',
computed: {
...mapState(['user', 'staticGen']),
...mapState(['user', 'staticGen', 'version']),
active () {
return this.$store.state.show === 'sidebar'
}

View File

@@ -1,32 +1,35 @@
<template>
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
<i class="material-icons">{{ icon() }}</i>
<i class="material-icons">{{ icon }}</i>
<span>{{ $t('buttons.switchView') }}</span>
</button>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { updateUser } from '@/utils/api'
export default {
name: 'switch-button',
computed: {
...mapState(['user']),
icon: function () {
if (this.user.viewMode === 'mosaic') return 'view_list'
return 'view_module'
}
},
methods: {
...mapMutations(['updateUser']),
change: function (event) {
// If we are on mobile we should close the dropdown.
this.$store.commit('closeHovers')
let display = 'mosaic'
let user = {...this.user}
user.viewMode = (this.icon === 'view_list') ? 'list' : 'mosaic'
if (this.$store.state.req.display === 'mosaic') {
display = 'list'
}
this.$store.commit('listingDisplay', display)
let path = this.$store.state.baseURL
if (path === '') path = '/'
document.cookie = `display=${display}; max-age=31536000; path=${path}`
},
icon: function () {
if (this.$store.state.req.display === 'mosaic') return 'view_list'
return 'view_module'
updateUser(user, 'partial').then(() => {
this.updateUser({ viewMode: user.viewMode })
}).catch(this.$showError)
}
}
}

View File

@@ -134,7 +134,7 @@ export default {
})
.catch(error => {
buttons.done(button)
this.$store.commit('showError', error)
this.$showError(error)
this.$store.commit('setSchedule', '')
})
}

View File

@@ -7,7 +7,7 @@
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
</div>
<div v-else id="listing"
:class="req.display"
:class="user.viewMode"
@dragenter="dragEnter"
@dragend="dragEnd">
<div>
@@ -98,7 +98,7 @@ export default {
name: 'listing',
components: { Item },
computed: {
...mapState(['req', 'selected']),
...mapState(['req', 'selected', 'user']),
nameSorted () {
return (this.req.sort === 'name')
},
@@ -196,6 +196,10 @@ export default {
})
},
paste (event) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
event.preventDefault()
let items = []
@@ -210,17 +214,13 @@ export default {
if (this.$store.state.clipboard.key === 'x') {
api.move(items).then(() => {
this.$store.commit('setReload', true)
}).catch(error => {
this.$store.commit('showError', error)
})
}).catch(this.$showError)
return
}
api.copy(items).then(() => {
this.$store.commit('setReload', true)
}).catch(error => {
this.$store.commit('showError', error)
})
}).catch(this.$showError)
},
resizeEvent () {
// Update the columns size based on the window width.
@@ -267,7 +267,7 @@ export default {
.then(req => {
this.checkConflict(files, req.items, base)
})
.catch(error => { console.log(error) })
.catch(this.$showError)
return
}
@@ -348,7 +348,7 @@ export default {
})
.catch(error => {
finish()
this.$store.commit('showError', error)
this.$showError(error)
})
return false

View File

@@ -109,21 +109,24 @@ export default {
.then(() => {
this.$store.commit('setReload', true)
})
.catch(error => {
this.$store.commit('showError', error)
})
.catch(this.$showError)
},
click: function (event) {
if (this.selectedCount !== 0) event.preventDefault()
if (this.$store.state.selected.indexOf(this.index) === -1) {
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
this.addSelected(this.index)
} else {
if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index)
return
}
return false
if (event.shiftKey && this.selected.length === 1) {
let fi = (this.index > this.selected[0]) ? this.selected[0] : this.index
let la = (this.index > this.selected[0]) ? this.index : this.selected[0]
for (; fi <= la; fi++) this.addSelected(fi)
return
}
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
this.addSelected(this.index)
},
touchstart (event) {
setTimeout(() => {

View File

@@ -20,8 +20,8 @@
<div class="preview">
<img v-if="req.type == 'image'" :src="raw()">
<audio v-else-if="req.type == 'audio'" :src="raw()" controls></audio>
<video v-else-if="req.type == 'video'" :src="raw()" controls>
<audio v-else-if="req.type == 'audio'" :src="raw()" autoplay controls></audio>
<video v-else-if="req.type == 'video'" :src="raw()" autoplay controls>
Sorry, your browser doesn't support embedded videos,
but don't worry, you can <a :href="download()">download it</a>
and watch it with your favorite video player!
@@ -75,7 +75,7 @@ export default {
this.listing = req
this.updateLinks()
})
.catch(error => { console.log(error) })
.catch(this.$showError)
},
beforeDestroy () {
window.removeEventListener('keyup', this.key)

View File

@@ -1,19 +1,24 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.copy') }}</h3>
<p>{{ $t('prompts.copyMessage') }}</p>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.copy') }}</h2>
</div>
<file-list @update:selected="val => dest = val"></file-list>
<div class="card-content">
<p>{{ $t('prompts.copyMessage') }}</p>
<file-list @update:selected="val => dest = val"></file-list>
</div>
<div>
<button class="ok"
@click="copy"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
<button class="cancel"
<div class="card-action">
<button class="cancel flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
@click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div>
</div>
</template>
@@ -56,7 +61,7 @@ export default {
})
.catch(error => {
buttons.done('copy')
this.$store.commit('showError', error)
this.$showError(error)
})
}
}

View File

@@ -1,16 +1,18 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.deleteTitle') }}</h3>
<p v-show="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
<p v-show="req.kind === 'listing'">{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
<div>
<button @click="submit"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
<button class="cancel"
@click="$store.commit('closeHovers')"
<div class="card floating">
<div class="card-content">
<p v-if="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
</div>
<div class="card-action">
<button @click="$store.commit('closeHovers')"
class="flat cancel"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button @click="submit"
class="flat"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
</div>
</div>
</template>
@@ -43,7 +45,7 @@ export default {
})
.catch(error => {
buttons.done('delete')
this.$store.commit('showError', error)
this.$showError(error)
})
return
@@ -70,7 +72,7 @@ export default {
.catch(error => {
buttons.done('delete')
this.$store.commit('setReload', true)
this.$store.commit('showError', error)
this.$showError(error)
})
}
}

View File

@@ -1,13 +1,18 @@
<template>
<div class="prompt" id="download">
<h3>{{ $t('prompts.download') }}</h3>
<p>{{ $t('prompts.downloadMessage') }}</p>
<div class="card floating" id="download">
<div class="card-title">
<h2>{{ $t('prompts.download') }}</h2>
</div>
<button @click="download('zip')" autofocus>zip</button>
<button @click="download('tar')" autofocus>tar</button>
<button @click="download('targz')" autofocus>tar.gz</button>
<button @click="download('tarbz2')" autofocus>tar.bz2</button>
<button @click="download('tarxz')" autofocus>tar.xz</button>
<div class="card-content">
<p>{{ $t('prompts.downloadMessage') }}</p>
<button class="block cancel" @click="download('zip')" autofocus>zip</button>
<button class="block cancel" @click="download('tar')" autofocus>tar</button>
<button class="block cancel" @click="download('targz')" autofocus>tar.gz</button>
<button class="block cancel" @click="download('tarbz2')" autofocus>tar.bz2</button>
<button class="block cancel" @click="download('tarxz')" autofocus>tar.xz</button>
</div>
</div>
</template>

View File

@@ -1,31 +0,0 @@
<template>
<div class="prompt error">
<i class="material-icons">error_outline</i>
<h3>{{ $t('prompts.error') }}</h3>
<pre>{{ $store.state.showMessage }}</pre>
<div>
<button @click="close"
autofocus
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
<button @click="reportIssue"
class="cancel"
:aria-label="$t('buttons.reportIssue')"
:title="$t('buttons.reportIssue')">{{ $t('buttons.reportIssue') }}</button>
</div>
</div>
</template>
<script>
export default {
name: 'error',
methods: {
reportIssue () {
window.open('https://github.com/hacdias/filemanager/issues/new')
},
close () {
this.$store.commit('closeHovers')
}
}
}
</script>

View File

@@ -53,7 +53,7 @@ export default {
// so we fetch the data from the previous directory.
api.fetch(url.removeLastDir(this.$route.path))
.then(this.fillOptions)
.catch(this.showError)
.catch(this.$showError)
},
methods: {
fillOptions (req) {
@@ -96,7 +96,7 @@ export default {
api.fetch(uri)
.then(this.fillOptions)
.catch(this.showError)
.catch(this.$showError)
},
touchstart (event) {
let url = event.currentTarget.dataset.url

View File

@@ -1,23 +1,27 @@
<template>
<div class="prompt help">
<h3>{{ $t('help.help') }}</h3>
<div class="card floating help">
<div class="card-title">
<h2>{{ $t('help.help') }}</h2>
</div>
<ul>
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
</ul>
<div class="card-content">
<ul>
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
</ul>
</div>
<div>
<div class="card-action">
<button type="submit"
@click="$store.commit('closeHovers')"
class="ok"
class="flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
</div>

View File

@@ -1,29 +1,33 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.fileInfo') }}</h3>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.fileInfo') }}</h2>
</div>
<p v-show="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
<div class="card-content">
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
<p v-show="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
<p><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
<p v-show="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime() }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name() }}</p>
<p><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span>{{ humanSize() }}</p>
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime() }}</p>
<section v-show="dir() && selected.length === 0">
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
</section>
<template v-if="dir() && selected.length === 0">
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
</template>
<section v-show="!dir()">
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
</section>
<template v-if="!dir()">
<p><strong>MD5:</strong> <code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA1:</strong> <code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA256:</strong> <code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA512:</strong> <code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
</template>
</div>
<div>
<div class="card-action">
<button type="submit"
@click="$store.commit('closeHovers')"
class="ok"
class="flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
</div>
@@ -111,7 +115,7 @@ export default {
api.checksum(link, hash)
.then((hash) => { event.target.innerHTML = hash })
.catch(error => { this.$store.commit('showError', error) })
.catch(this.$showError)
}
}
}

View File

@@ -1,19 +1,23 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.move') }}</h3>
<p>{{ $t('prompts.moveMessage') }}</p>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.move') }}</h2>
</div>
<file-list @update:selected="val => dest = val"></file-list>
<div class="card-content">
<file-list @update:selected="val => dest = val"></file-list>
</div>
<div>
<button class="ok"
@click="move"
:aria-label="$t('buttons.move')"
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
<button class="cancel"
<div class="card-action">
<button class="flat cancel"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
@click="move"
:disabled="$route.path === dest"
:aria-label="$t('buttons.move')"
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
</div>
</div>
</template>
@@ -56,7 +60,7 @@ export default {
})
.catch(error => {
buttons.done('move')
this.$store.commit('showError', error)
this.$showError(error)
})
event.preventDefault()

View File

@@ -1,18 +1,24 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.newFile') }}</h3>
<p>{{ $t('prompts.newArchetype') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
<input type="text" @keyup.enter="submit" v-model.trim="archetype">
<div>
<button class="ok"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
<button class="cancel"
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.newFile') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.newArchetype') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
<input type="text" @keyup.enter="submit" v-model.trim="archetype">
</div>
<div class="card-action">
<button class="flat cancel"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
</div>
</div>
</template>
@@ -37,9 +43,7 @@ export default {
.then((url) => {
this.$router.push({ path: url })
})
.catch(error => {
this.$store.commit('showError', error)
})
.catch(this.$showError)
},
new (url, type) {
url = removePrefix(url)
@@ -47,7 +51,7 @@ export default {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${this.$store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${this.$store.state.jwt}`)
if (!this.$store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${this.$store.state.jwt}`)
request.setRequestHeader('Archetype', encodeURIComponent(type))
request.onload = () => {

View File

@@ -1,17 +1,23 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.newDir') }}</h3>
<p>{{ $t('prompts.newDirMessage') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
<div>
<button class="ok"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"
@click="submit">{{ $t('buttons.create') }}</button>
<button class="cancel"
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.newDir') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.newDirMessage') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
</div>
<div class="card-action">
<button class="cancel flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"
@click="submit">{{ $t('buttons.create') }}</button>
</div>
</div>
</template>
@@ -43,7 +49,7 @@ export default {
api.post(uri)
.then(() => { this.$router.push({ path: uri }) })
.catch(error => { this.$store.commit('showError', error) })
.catch(this.$showError)
// Close the prompt
this.$store.commit('closeHovers')

View File

@@ -1,17 +1,23 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.newFile') }}</h3>
<p>{{ $t('prompts.newFileMessage') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
<div>
<button class="ok"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
<button class="cancel"
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.newFile') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.newFileMessage') }}</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
</div>
<div class="card-action">
<button class="cancel flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')">{{ $t('buttons.create') }}</button>
</div>
</div>
</template>
@@ -44,7 +50,7 @@ export default {
// Create the new file.
api.post(uri)
.then(() => { this.$router.push({ path: uri }) })
.catch(error => { this.$store.commit('showError', error) })
.catch(this.$showError)
// Close the prompt.
this.$store.commit('closeHovers')

View File

@@ -9,8 +9,6 @@
<info v-else-if="showInfo"></info>
<move v-else-if="showMove"></move>
<copy v-else-if="showCopy"></copy>
<error v-else-if="showError"></error>
<success v-else-if="showSuccess"></success>
<replace v-else-if="showReplace"></replace>
<schedule v-else-if="show === 'schedule'"></schedule>
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
@@ -27,8 +25,6 @@ import Rename from './Rename'
import Download from './Download'
import Move from './Move'
import Copy from './Copy'
import Error from './Error'
import Success from './Success'
import NewFile from './NewFile'
import NewDir from './NewDir'
import NewArchetype from './NewArchetype'
@@ -47,9 +43,7 @@ export default {
NewArchetype,
Schedule,
Rename,
Error,
Download,
Success,
Move,
Copy,
Share,
@@ -70,8 +64,6 @@ export default {
},
computed: {
...mapState(['show', 'plugins']),
showError: function () { return this.show === 'error' },
showSuccess: function () { return this.show === 'success' },
showInfo: function () { return this.show === 'info' },
showHelp: function () { return this.show === 'help' },
showDelete: function () { return this.show === 'delete' },

View File

@@ -1,18 +1,24 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.rename') }}</h3>
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.rename') }}</h2>
</div>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
<div>
<button @click="submit"
type="submit"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
<button class="cancel"
<div class="card-content">
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
</div>
<div class="card-action">
<button class="cancel flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button @click="submit"
class="flat"
type="submit"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
</div>
</div>
</template>
@@ -68,7 +74,7 @@ export default {
}
this.$store.commit('setReload', true)
}).catch(error => {
this.$store.commit('showError', error)
this.$showError(error)
})
this.$store.commit('closeHovers')

View File

@@ -1,17 +1,22 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.replace') }}</h3>
<p>{{ $t('prompts.replaceMessage') }}</p>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.replace') }}</h2>
</div>
<div>
<button class="ok"
@click="showConfirm"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
<button class="cancel"
<div class="card-content">
<p>{{ $t('prompts.replaceMessage') }}</p>
</div>
<div class="card-action">
<button class="flat cancel"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="flat"
@click="showConfirm"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
</div>
</div>
</template>

View File

@@ -1,17 +1,23 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.schedule') }}</h3>
<p>{{ $t('prompts.scheduleMessage') }}</p>
<input autofocus type="datetime-local" v-model="date">
<div>
<button class="ok"
@click="submit"
:aria-label="$t('buttons.schedule')"
:title="$t('buttons.schedule')">{{ $t('buttons.schedule') }}</button>
<button class="cancel"
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.schedule') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.scheduleMessage') }}</p>
<input autofocus type="datetime-local" v-model="date">
</div>
<div class="card-action">
<button class="cancel flat"
@click="close"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="falt"
@click="submit"
:aria-label="$t('buttons.schedule')"
:title="$t('buttons.schedule')">{{ $t('buttons.schedule') }}</button>
</div>
</div>
</template>

View File

@@ -1,51 +1,55 @@
<template>
<div class="prompt" id="share">
<h3>{{ $t('buttons.share') }}</h3>
<p></p>
<ul>
<li v-if="!hasPermanent">
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
</li>
<div class="card floating" id="share">
<div class="card-title">
<h2>{{ $t('buttons.share') }}</h2>
</div>
<li v-for="link in links" :key="link.hash">
<a :href="buildLink(link.hash)" target="_blank">
<template v-if="link.expires">{{ humanTime(link.expireDate) }}</template>
<template v-else>{{ $t('permanent') }}</template>
</a>
<div class="card-content">
<ul>
<li v-if="!hasPermanent">
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
</li>
<button class="action"
@click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
<li v-for="link in links" :key="link.hash">
<a :href="buildLink(link.hash)" target="_blank">
<template v-if="link.expires">{{ humanTime(link.expireDate) }}</template>
<template v-else>{{ $t('permanent') }}</template>
</a>
<button class="action copy"
:data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
</li>
<button class="action"
@click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
<li>
<input autofocus
type="number"
max="2147483647"
min="0"
@keyup.enter="submit"
v-model.trim="time">
<select v-model="unit" :aria-label="$t('time.unit')">
<option value="seconds">{{ $t('time.seconds') }}</option>
<option value="minutes">{{ $t('time.minutes') }}</option>
<option value="hours">{{ $t('time.hours') }}</option>
<option value="days">{{ $t('time.days') }}</option>
</select>
<button class="action"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
</li>
</ul>
<button class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
</li>
<div>
<button class="cancel"
<li>
<input autofocus
type="number"
max="2147483647"
min="0"
@keyup.enter="submit"
v-model.trim="time">
<select v-model="unit" :aria-label="$t('time.unit')">
<option value="seconds">{{ $t('time.seconds') }}</option>
<option value="minutes">{{ $t('time.minutes') }}</option>
<option value="hours">{{ $t('time.hours') }}</option>
<option value="days">{{ $t('time.days') }}</option>
</select>
<button class="action"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
</li>
</ul>
</div>
<div class="card-action">
<button class="flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
@@ -54,7 +58,7 @@
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { mapState } from 'vuex'
import { getShare, deleteShare, share } from '@/utils/api'
import moment from 'moment'
import Clipboard from 'clipboard'
@@ -101,20 +105,25 @@ export default {
})
.catch(error => {
if (error === 404) return
this.showError(error)
this.$showError(error)
})
},
mounted () {
this.clip = new Clipboard('.copy')
this.clip = new Clipboard('.copy-clipboard')
this.clip.on('success', (e) => {
this.$showSuccess(this.$t('success.linkCopied'))
})
},
beforeDestroy () {
this.clip.destroy()
},
methods: {
...mapMutations([ 'showError' ]),
submit: function (event) {
if (!this.time) return
share(this.url, this.time, this.unit)
.then(result => { this.links.push(result); this.sort() })
.catch(error => { this.showError(error) })
.catch(this.$showError)
},
getPermalink (event) {
share(this.url)
@@ -123,7 +132,7 @@ export default {
this.sort()
this.hasPermanent = true
})
.catch(error => { this.showError(error) })
.catch(this.$showError)
},
deleteLink (event, link) {
event.preventDefault()
@@ -132,7 +141,7 @@ export default {
if (!link.expires) this.hasPermanent = false
this.links = this.links.filter(item => item.hash !== link.hash)
})
.catch(error => { this.showError(error) })
.catch(this.$showError)
},
humanTime (time) {
return moment(time).fromNow()

View File

@@ -1,23 +0,0 @@
<template>
<div class="prompt success">
<i class="material-icons">done</i>
<h3>{{ $store.state.showMessage }}</h3>
<div>
<button @click="close"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')"
autofocus>{{ $t('buttons.ok') }}</button>
</div>
</div>
</template>
<script>
export default {
name: 'success',
methods: {
close () {
this.$store.commit('closeHovers')
}
}
}
</script>

View File

@@ -1,9 +1,8 @@
body {
font-family: 'Roboto', sans-serif;
padding-top: 4em;
background-color: #f8f8f8;
user-select: none;
color: #212121;
background-color: #fafafa;
color: #333333;
}
* {
@@ -66,6 +65,58 @@ button:hover {
background-color: #1E88E5;
}
input[type="submit"].block,
button.block {
display: block;
width: 100%;
margin: 0 0 1em;
}
button.delete {
background: #F44336;
}
button.delete:hover {
background: #D32F2F;
}
button.cancel {
background-color: #ECEFF1;
color: #37474F;
}
button.cancel:hover {
background-color: #e9eaeb;
}
button.flat,
input[type="submit"].flat {
color: #1E88E5;
background: transparent;
box-shadow: 0 0 0;
border: 0;
margin-left: 0;
text-transform: uppercase;
}
button.flat:hover,
input[type="submit"].flat:hover {
background: rgba(0,0,0,0.05)
}
button.flat.delete {
color: #F44336;
}
button.flat.cancel {
color: #ccc;
}
button.flat[disabled] {
color: #ccc;
cursor: not-allowed;
}
.mobile-only {
display: none !important;
}

View File

@@ -1,64 +1,34 @@
.dashboard {
max-width: 600px;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
border-radius: .5em;
background: #fff;
padding: 1em;
margin: 1em 0;
}
.dashboard a {
a {
color: inherit
}
.dashboard h1 button {
font-size: 0.5em;
float: right;
}
.dashboard table {
width: 100%;
}
.dashboard table th {
font-weight: 500;
color: #757575;
text-align: left;
}
.dashboard table th,
.dashboard table td {
padding: .5em 0;
}
.dashboard table td:last-child {
width: 1em
}
.dashboard > h1:first-of-type {
margin-top: 0;
}
.dashboard form > p:last-child,
form.dashboard > p:last-child {
text-align: right
}
.dashboard > *:last-child {
margin-bottom: 0;
}
.dashboard select,
.dashboard textarea,
.dashboard input[type="text"],
.dashboard input[type="password"] {
padding: 0;
line-height: 1.7;
select,
textarea,
input[type="text"],
input[type="password"] {
padding: 0.5em 0;
line-height: 1;
display: block;
border: 0;
border-bottom: 1px solid #dddddd;
transition: .2s ease border;
width: 100%;
background: transparent;
}
textarea {
line-height: 1.15;
padding: .5em;
border: 1px solid #ddd;
font-family: monospace;
min-height: 10em;
resize: none;
border-radius: 2px;
}
.dashboard #locale,
@@ -69,49 +39,32 @@ form.dashboard > p:last-child {
}
.dashboard #locale {
border: 1px solid #dddddd;
margin-top: .5em;
}
.dashboard textarea:focus,
.dashboard textarea:hover,
.dashboard input[type="text"]:focus,
.dashboard input[type="password"]:focus,
.dashboard input[type="text"]:hover,
.dashboard input[type="password"]:hover {
textarea:focus,
textarea:hover,
input[type="text"]:focus,
input[type="password"]:focus,
input[type="text"]:hover,
input[type="password"]:hover {
border-color: #2979ff;
}
.dashboard input.red {
input.red {
border-color: red;
}
.dashboard input.green {
input.green {
border-color: green;
}
.dashboard button.delete {
background: #F44336;
}
.dashboard button.delete:hover {
background: #D32F2F;
}
.dashboard textarea {
line-height: 1.15;
padding: .5em;
border: 1px solid #ddd;
font-family: monospace;
min-height: 10em;
resize: vertical;
}
.dashboard p label {
margin-bottom: .2em;
display: block;
font-size: .8em;
font-weight: bold;
font-weight: 500;
color: rgba(0, 0, 0, 0.57);
}
li code,
@@ -131,21 +84,337 @@ p code {
display: flex;
color: rgb(84, 110, 122);
font-weight: 500;
padding: 0 0 1em;
margin: 0 0 1em;
font-size: .8em;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
text-align: center;
justify-content: space-between;
padding: 0;
}
.dashboard #nav li {
width: 100%;
padding: 0 0 1em;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
}
.dashboard #nav li:last-child {
text-align: right
.dashboard #nav li.active {
border-color: #2196f3
}
.dashboard #nav i {
font-size: 1em;
vertical-align: middle;
}
table {
border-collapse: collapse;
width: 100%;
}
table tr {
border-bottom: 1px solid #ccc;
}
table tr:last-child {
border: 0;
}
table th {
font-weight: 500;
color: #757575;
text-align: left;
}
table th,
table td {
padding: .5em 0;
}
table td.small {
width: 1em;
}
table tr>*:first-child {
padding-left: 1em;
}
table tr>*:last-child {
padding-right: 1em;
}
.card {
position: relative;
margin: .5rem 0 1rem 0;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.card.floating {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99999;
max-width: 25em;
width: 90%;
max-height: 95%;
z-index: 99999;
animation: .1s show forwards;
}
.card>*>*:first-child {
margin-top: 0;
}
.card>*>*:last-child {
margin-bottom: 0;
}
.card .card-title {
padding: 1.5em 1em 1em;
display: flex;
}
.card .card-title>*:first-child {
margin-right: auto;
}
.card>div {
padding: 1em 1em;
}
.card>div:first-child {
padding-top: 1.5em;
}
.card>div:last-child {
padding-bottom: 1.5em;
}
.card .card-title * {
margin: 0;
}
.card .card-action {
text-align: right;
}
.card .card-content.full {
padding-bottom: 0;
}
.card h2 {
font-weight: 500;
}
.card h3 {
color: rgba(0, 0, 0, 0.53);
font-size: 1em;
font-weight: 500;
margin: 2em 0 1em;
}
.card-content table {
margin: 0 -1em;
width: calc(100% + 2em);
}
.card code {
word-wrap: break-word;
}
.card#download {
max-width: 15em;
}
.card#share ul {
list-style: none;
padding: 0;
margin: 0;
}
.card#share ul li {
display: flex;
justify-content: space-between;
align-items: center;
}
.card#share ul li a {
color: #2196F3;
cursor: pointer;
margin-right: auto;
}
.card#share ul li .action i {
font-size: 1em;
}
.card#share ul li input,
.card#share ul li select {
padding: .2em;
margin-right: .5em;
border: 1px solid #dadada;
}
.card#share .action.copy-clipboard::after {
content: 'Copied!';
position: absolute;
left: -25%;
width: 150%;
font-size: .6em;
text-align: center;
background: #44a6f5;
color: #fff;
padding: .5em .2em;
border-radius: .4em;
top: -2em;
transition: .1s ease opacity;
opacity: 0;
}
.card#share .action.copy-clipboard.active::after {
opacity: 1;
}
.overlay {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 9999;
animation: .1s show forwards;
}
/* * * * * * * * * * * * * * * *
* PROMPT - MOVE *
* * * * * * * * * * * * * * * */
.file-list {
max-height: 50vh;
overflow: auto;
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
.file-list li {
width: 100%;
user-select: none;
border-radius: .2em;
padding: .3em;
}
.file-list li[aria-selected=true] {
background: #2196f3 !important;
color: #fff !important;
transition: .1s ease all;
}
.file-list li:hover {
background-color: #e9eaeb;
cursor: pointer;
}
.file-list li:before {
content: "folder";
color: #6f6f6f;
vertical-align: middle;
line-height: 1.4;
font-family: 'Material Icons';
font-size: 1.75em;
margin-right: .25em;
}
.file-list li[aria-selected=true]:before {
color: white;
}
.help {
max-width: 24em;
}
.help ul {
padding: 0;
margin: 1em 0;
list-style: none;
}
@keyframes show {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
.collapsible {
border-top: 1px solid rgba(0,0,0,0.1);
}
.collapsible:last-of-type {
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.collapsible > input {
display: none;
}
.collapsible > label {
padding: 1em 0;
cursor: pointer;
border-right: 0;
border-left: 0;
display: flex;
justify-content: space-between;
}
.collapsible > label * {
margin: 0;
color: rgba(0,0,0,0.57);
}
.collapsible > label i {
transition: .2s ease transform;
user-select: none;
}
.collapsible .collapse {
max-height: 0;
overflow: hidden;
transition: .2s ease all;
}
.collapsible > input:checked ~ .collapse {
padding-top: 1em;
padding-bottom: 1em;
max-height: 20em;
}
.collapsible > input:checked ~ label i {
transform: rotate(180deg)
}
.card .collapsible {
width: calc(100% + 2em);
margin: 0 -1em;
}
.card .collapsible > label {
padding: 1em;
}
.card .collapsible .collapse {
padding: 0 1em;
}

View File

@@ -122,8 +122,9 @@ header .search-button {
#search input {
width: 100%;
border: 0;
outline: 0;
background-color: transparent;
line-height: 0;
padding: 0;
}
#search #result {

View File

@@ -159,7 +159,7 @@
#listing.list .item.header {
display: flex !important;
background: #f8f8f8;
background: #fafafa;
position: fixed;
width: calc(100% - 19em);
top: 7em;

View File

@@ -29,6 +29,14 @@
width: 90%;
}
#login.recaptcha form {
min-width: 304px;
}
#login #recaptcha {
margin: .5em 0 0;
}
#login input {
width: 100%;
width: 100%;

View File

@@ -1,208 +0,0 @@
.prompt {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
padding: 2em;
max-width: 25em;
width: 90%;
max-height: 95%;
z-index: 99999;
animation: .1s show forwards;
}
.overlay {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 9999;
animation: .1s show forwards;
}
.prompt h3 {
margin: 0;
font-weight: 500;
font-size: 1.5em;
}
.prompt p {
font-size: .9em;
color: rgba(0, 0, 0, 0.8);
margin: .5em 0 1em;
}
.prompt input:not([type="submit"]) {
width: 100%;
border: 1px solid #dadada;
line-height: 1;
padding: .3em;
margin: .3em 0;
}
.prompt code {
word-wrap: break-word;
}
.prompt div:last-of-type {
margin-top: 1em;
display: flex;
justify-content: flex-start;
flex-direction: row-reverse;
}
.prompt .cancel {
background-color: #ECEFF1;
color: #37474F;
}
.prompt .cancel:hover {
background-color: #e9eaeb;
}
.prompt.success i,
.prompt.error i {
color: #F44336;
display: block;
margin: 0 auto .15em;
text-align: center;
font-size: 5em;
}
.prompt.success h3,
.prompt.error h3 {
text-align: center;
}
.prompt.error button:not(.cancel) {
background-color: #F44336
}
.prompt.success i {
color: #8BC34A;
}
.prompt.success button {
background-color: #8BC34A;
}
/* * * * * * * * * * * * * * * *
* PROMPT - MOVE *
* * * * * * * * * * * * * * * */
.file-list {
max-height: 50vh;
overflow: auto;
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
.file-list li {
width: 100%;
user-select: none;
border-radius: .2em;
padding: .3em;
}
.file-list li[aria-selected=true] {
background: #2196f3 !important;
color: #fff !important;
transition: .1s ease all;
}
.file-list li:hover {
background-color: #e9eaeb;
cursor: pointer;
}
.file-list li:before {
content: "folder";
color: #6f6f6f;
vertical-align: middle;
line-height: 1.4;
font-family: 'Material Icons';
font-size: 1.75em;
margin-right: .25em;
}
.file-list li[aria-selected=true]:before {
color: white;
}
.prompt#download {
max-width: 15em;
}
.prompt#download button {
width: 100%;
display: block;
margin: 0 0 1em;
background-color: #ECEFF1;
color: #37474F;
}
.prompt#download button:last-of-type {
margin-bottom: 0;
}
.help {
max-width: 24em;
}
.help ul {
padding: 0;
margin: 1em 0;
list-style: none;
}
@keyframes show {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
.prompt#share ul {
list-style: none;
padding: 0;
margin: 0;
}
.prompt#share ul li {
display: flex;
justify-content: space-between;
align-items: center;
}
.prompt#share ul li a {
color: #2196F3;
cursor: pointer;
margin-right: auto;
}
.prompt#share ul li .action i {
font-size: 1em;
}
.prompt#share ul li input,
.prompt#share ul li select {
padding: .2em;
margin-right: .5em;
border: 1px solid #dadada;
}

View File

@@ -1,8 +1,8 @@
@import "~normalize.css/normalize.css";
@import "~noty/lib/noty.css";
@import "./fonts.css";
@import "./base.css";
@import "./header.css";
@import "./prompts.css";
@import "./listing.css";
@import "./editor.css";
@import "./dashboard.css";
@@ -180,6 +180,17 @@
* PROMPT *
* * * * * * * * * * * * * * * */
.noty_buttons {
text-align: right;
padding: 0 10px 10px !important;
}
.noty_buttons button {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0,0,0,0.1);
box-shadow: 0 0 0 0;
font-size: 14px;
}
/* * * * * * * * * * * * * * * *
* FOOTER *

View File

@@ -26,11 +26,13 @@ buttons:
publish: Publish
selectMultiple: Select multiple
schedule: Schedule
switchView: Swicth view
switchView: Switch view
toggleSidebar: Toggle sidebar
update: Update
upload: Upload
permalink: Get Permanent Link
success:
linkCopied: Link copied!
errors:
forbidden: You're not welcome here.
internal: Something really went wrong.
@@ -115,13 +117,14 @@ settings:
commandsHelp: >
Here you can set commands that are executed in the named events. You
write one command per line. If the event is related to files, such as before and
after saving, the environment variable "file" will be available with the path
after saving, the environment variable "FILE" will be available with the path
of the file.
commandsUpdated: Commands updated!
customStylesheet: Custom Stylesheet
examples: Examples
globalSettings: Global Settings
language: Language
lockPassword: Prevent the user from changing the password
newPassword: Your new password
newPasswordConfirm: Confirm your new password
newUser: New User
@@ -165,7 +168,6 @@ sidebar:
myFiles: My files
newFile: New file
newFolder: New folder
servedWith: Served with
settings: Settings
siteSettings: Site Settings
hugoNew: Hugo New

View File

@@ -115,7 +115,7 @@ settings:
commandsHelp: >
Ici vous pouvez définir des commandes qui seront exécutées lors de l'évènement correspondant.
Vous devez indiquer une commande par ligne. Si l'évènement est en rapport avec des fichiers,
par exemple avant et après enregistrement, la variable d'environement "file" sera disponible
par exemple avant et après enregistrement, la variable d'environement "FILE" sera disponible
et contiendra le chemin d'accès vers le fichier.
commandsUpdated: Commandes mises à jour !
customStylesheet: Feuille de style personnalisée
@@ -161,7 +161,6 @@ sidebar:
myFiles: Mes fichiers
newFile: Nouveau fichier
newFolder: Nouveau dossier
servedWith: Géré avec
settings: Paramètres
siteSettings: Paramètres du site
hugoNew: Nouveau Hugo

View File

@@ -9,8 +9,39 @@ import zhTW from './zh-tw.yaml'
Vue.use(VueI18n)
export function detectLocale () {
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
switch (true) {
case /^en.*/i.test(locale):
locale = 'en'
break
case /^fr.*/i.test(locale):
locale = 'fr'
break
case /^pt.*/i.test(locale):
locale = 'pt'
break
case /^ja.*/i.test(locale):
locale = 'ja'
break
case /^zh-CN/i.test(locale):
locale = 'zh-cn'
break
case /^zh-TW/i.test(locale):
locale = 'zh-tw'
break
case /^zh.*/i.test(locale):
locale = 'zh-cn'
break
default:
locale = 'en'
}
return locale
}
const i18n = new VueI18n({
locale: 'en',
locale: detectLocale(),
fallbackLocale: 'en',
messages: {
'en': en,

View File

@@ -31,6 +31,8 @@ buttons:
update: 更新
upload: アップロード
permalink: 固定リンク
success:
linkCopied: リンクがコピーされました!
errors:
forbidden: アクセスが拒否されました。
internal: 内部エラーが発生しました。
@@ -116,12 +118,13 @@ settings:
ここで、名前付きイベントに実行するコマンドを設定することができます。\
一行にコマンド一つを入力してください。\
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
環境変数 file はファイルのパスに割り当てられます。"
環境変数 FILE はファイルのパスに割り当てられます。"
commandsUpdated: コマンドは更新されました!
customStylesheet: カスタムスタイルシ ート
examples:
globalSettings: グローバル設定
language: 言語
lockPassword: 新しいパスワードを変更に禁止
newPassword: 新しいパスワード
newPasswordConfirm: 新しいパスワードを確認します
newUser: 新しいユーザー
@@ -165,7 +168,6 @@ sidebar:
myFiles: 私のファイル
newFile: 新しいファイルを作成
newFolder: 新しいフォルダを作成
servedWith: サービス提供者
settings: 設定
siteSettings: サイト設定
hugoNew: Hugo New

View File

@@ -31,6 +31,8 @@ buttons:
update: Atualizar
upload: Enviar
permalink: Obter link permanente
success:
linkCopied: Link copiado!
errors:
forbidden: Tu não és bem-vindo aqui.
internal: Algo correu bastante mal.
@@ -137,12 +139,13 @@ settings:
Pode definir um conjunto de comandos a executar em determiandos eventos.
Deve escrever um comando por linha. Se o evento estiver relacionado com ficheiros,
como antes e depois de guardar, irá existir uma variável de ambiente denominada
"file" com o caminho do ficheiro.
"FILE" com o caminho do ficheiro.
commandsUpdated: Comandos atualizados!
customStylesheet: Estilos Personalizados
examples: Exemplos
globalSettings: Configurações Globais
language: Linguagem
lockPassword: Não permitir que o utilizador altere a palavra-passe
newPassword: Nova palavra-passe
newPasswordConfirm: Confirme a nova palavra-passe
newUser: Novo Utilizador
@@ -190,7 +193,6 @@ sidebar:
newFile: Novo ficheiro
newFolder: Nova pasta
preview: Pré-visualizar
servedWith: Servido com
settings: Configurações
siteSettings: Configurações do Site
time:

View File

@@ -31,6 +31,8 @@ buttons:
update: 更新
upload: 上传
permalink: 获取永久链接
success:
linkCopied: 链接已复制!
errors:
forbidden: 你被禁止访问。
internal: 内部出现麻烦了。
@@ -115,12 +117,13 @@ settings:
commandsHelp: "\
在这里,您可以设置在指定事件下执行的命令,一行一条。\
若事件与文件相关,如“在保存文件前”,\
则文件的路径会被赋值给环境变量 \"file\"。"
则文件的路径会被赋值给环境变量 \"FILE\"。"
commandsUpdated: 命令已更新!
customStylesheet: 自定义样式表
examples: 例子
globalSettings: 全局设置
language: 语言
lockPassword: 禁止用户修改密码
newPassword: 您的新密码
newPasswordConfirm: 重输一遍新密码
newUser: 新建用户
@@ -163,7 +166,6 @@ sidebar:
myFiles: 我的文件
newFile: 新建文件
newFolder: 新建文件夹
servedWith: '服务提供者:'
settings: 设置
siteSettings: 网站设置
hugoNew: Hugo New

View File

@@ -4,7 +4,7 @@ buttons:
close: 關閉
copy: 複製
copyFile: 複製檔案
copyToClipboard: 複製到剪貼
copyToClipboard: 複製到剪貼簿
create: 建立
delete: 刪除
download: 下載
@@ -31,8 +31,10 @@ buttons:
update: 更新
upload: 上傳
permalink: 獲取永久連結
success:
linkCopied: 連結已複製!
errors:
forbidden: 你被禁止訪問
forbidden: 你被禁止存取
internal: 內部出現麻煩了。
notFound: 找不到檔案。
files:
@@ -45,7 +47,7 @@ files:
lastModified: 最後修改
loading: 讀取中...
lonely: 這裡沒有任何檔案...
metadata: 中繼資料
metadata: 詮釋資料
multipleSelectionEnabled: 多選模式已開啟
name: 名稱
size: 大小
@@ -100,70 +102,70 @@ prompts:
show: 顯示
size: 大小
schedule: 計畫
scheduleMessage: 請選擇發佈這篇帖子的日期。
newArchetype: 建立一個基於原型的新帖子。您的檔案將會建立在內容資料夾中。
scheduleMessage: 請選擇發佈這篇貼文的日期。
newArchetype: 建立一個基於原型的新貼文。您的檔案將會建立在內容資料夾中。
settings:
admin: 管理員
administrator: 管理員
allowCommands: 執行命令
allowEdit: 編輯、重命名或刪除檔案/目錄
allowNew: 創建新檔案和目錄
allowPublish: 發佈新的帖子與頁面
allowPublish: 發佈新的貼文與頁面
avoidChanges: '(留空以避免更改)'
changePassword: 更改密碼
commands: 命令
commandsHelp: "\
在這裡,您可以設定在指定事件下執行的命令,一行一條。\
若事件與檔案相關,如“在保存檔案前”,\
則檔案的路徑會被賦值給環境變數 \"file\"。"
則檔案的路徑會被賦值給環境變數 \"FILE\"。"
commandsUpdated: 命令已更新!
customStylesheet: 自定義樣式表
examples:
examples:
globalSettings: 全域設定
language: 語言
lockPassword: 禁止使用者修改密碼
newPassword: 您的新密碼
newPasswordConfirm: 重輸一遍新密碼
newUser: 建立用戶
newUser: 建立使用者
password: 密碼
passwordUpdated: 密碼已更新!
permissions: 權限
permissionsHelp: "\
您可以將該用戶設置為管理員,也可以單獨選擇各項權限。\
您可以將該使用者設置為管理員,也可以單獨選擇各項權限。\
如果選擇了“管理員”,則其他的選項會被自動勾上,\
同時該用戶可以管理其他用戶。"
同時該使用者可以管理其他使用者。"
profileSettings: 設定檔設定
ruleExample1: "\
封鎖用戶訪問所有資料夾下任何以 . 開頭的檔案\
封鎖使用者存取所有資料夾下任何以 . 開頭的檔案\
(隱藏文件, 例如: .git, .gitignore)。"
ruleExample2: 封鎖用戶訪問其目錄範圍的根目錄下名為 Caddyfile 的檔案。
ruleExample2: 封鎖使用者存取其目錄範圍的根目錄下名為 Caddyfile 的檔案。
rules: 規則
rulesHelp1: "\
您可以為該用戶製定一組黑名單或白名單式的規則,\
被屏蔽的檔案將不會顯示在清單中,用戶也無權限訪問,\
您可以為該使用者製定一組黑名單或白名單式的規則,\
被屏蔽的檔案將不會顯示在清單中,使用者也無權限存取,\
支持相對於目錄範圍的路徑。"
rulesHelp2: "\
每行一條規則,且必須以關鍵字 {0} 或 {1} 開頭。\
如要使用規則運算式,請在加上 {2} 之後再附上運算式或路徑。"
scope: 目錄範圍
settingsUpdated: 設定已更新!
user: 用戶
userCommands: 用戶命令
user: 使用者
userCommands: 使用者命令
userCommandsHelp: "\
指定該用戶可以執行的命令,用空格分隔。\
指定該使用者可以執行的命令,用空格分隔。\
例如:"
userCreated: 用戶已建立!
userDeleted: 用戶已刪除!
userManagement: 用戶管理
username: 用戶名
users: 用戶
userUpdated: 用戶已更新!
userCreated: 使用者已建立!
userDeleted: 使用者已刪除!
userManagement: 使用者管理
username: 使用者名稱
users: 使用者
userUpdated: 使用者已更新!
sidebar:
help: 幫助
logout: 登出
myFiles: 我的檔案
newFile: 建立檔案
newFolder: 建立資料夾
servedWith: '服務提供者:'
settings: 設定
siteSettings: 網站設定
hugoNew: Hugo New
@@ -187,9 +189,10 @@ languages:
pt: Português
ja: 日本語
zhCN: 中文 (简体)
zhTW: 中文 (繁體)
time:
unit: 時間單位
seconds:
minutes: 分鐘
hours: 小時
days:
days:

View File

@@ -3,9 +3,46 @@ import App from './App'
import store from './store'
import router from './router'
import i18n from './i18n'
import Noty from 'noty'
Vue.config.productionTip = true
const notyDefault = {
type: 'info',
layout: 'bottomRight',
timeout: 1000,
progressBar: true
}
Vue.prototype.$noty = function (opts) {
new Noty(Object.assign({}, notyDefault, opts)).show()
}
Vue.prototype.$showSuccess = function (message) {
new Noty(Object.assign({}, notyDefault, {
text: message,
type: 'success'
})).show()
}
Vue.prototype.$showError = function (error) {
let n = new Noty(Object.assign({}, notyDefault, {
text: error,
type: 'error',
timeout: null,
buttons: [
Noty.button(i18n.t('buttons.reportIssue'), '', function () {
window.open('https://github.com/hacdias/filemanager/issues/new')
}),
Noty.button(i18n.t('buttons.close'), '', function () {
n.close()
})
]
}))
n.show()
}
/* eslint-disable no-new */
new Vue({
el: '#app',

View File

@@ -3,14 +3,15 @@ import Router from 'vue-router'
import Login from '@/views/Login'
import Layout from '@/views/Layout'
import Files from '@/views/Files'
import Users from '@/views/Users'
import User from '@/views/User'
import GlobalSettings from '@/views/GlobalSettings'
import ProfileSettings from '@/views/ProfileSettings'
import Users from '@/views/Settings/Users'
import User from '@/views/Settings/User'
import Settings from '@/views/Settings'
import GlobalSettings from '@/views/settings/Global'
import ProfileSettings from '@/views/settings/Profile'
import Error403 from '@/views/errors/403'
import Error404 from '@/views/errors/404'
import Error500 from '@/views/errors/500'
import auth from '@/utils/auth.js'
import auth from '@/utils/auth'
import store from '@/store'
Vue.use(Router)
@@ -49,22 +50,44 @@ const router = new Router({
{
path: '/settings',
name: 'Settings',
component: Settings,
redirect: {
path: '/settings/profile'
}
},
{
path: '/settings/profile',
name: 'Profile Settings',
component: ProfileSettings
},
{
path: '/settings/global',
name: 'Global Settings',
component: GlobalSettings,
},
meta: {
requiresAdmin: true
}
disableOnNoAuth: true
},
children: [
{
path: '/settings/profile',
name: 'Profile Settings',
component: ProfileSettings
},
{
path: '/settings/global',
name: 'Global Settings',
component: GlobalSettings,
meta: {
requiresAdmin: true
}
},
{
path: '/settings/users',
name: 'Users',
component: Users,
meta: {
requiresAdmin: true
}
},
{
path: '/settings/users/*',
name: 'User',
component: User,
meta: {
requiresAdmin: true
}
}
]
},
{
path: '/403',
@@ -81,22 +104,6 @@ const router = new Router({
name: 'Internal Server Error',
component: Error500
},
{
path: '/users',
name: 'Users',
component: Users,
meta: {
requiresAdmin: true
}
},
{
path: '/users/*',
name: 'User',
component: User,
meta: {
requiresAdmin: true
}
},
{
path: '/files',
redirect: {
@@ -123,16 +130,17 @@ router.beforeEach((to, from, next) => {
auth.loggedIn()
.then(() => {
if (to.matched.some(record => record.meta.requiresAdmin)) {
if (store.state.user.admin) {
next()
if (!store.state.user.admin) {
next({ path: '/403' })
return
}
}
next({
path: '/403'
})
return
if (to.matched.some(record => record.meta.disableOnNoAuth)) {
if (store.state.noAuth) {
next({ path: '/403' })
return
}
}
next()

View File

@@ -12,8 +12,16 @@ const state = {
key: '',
items: []
},
css: (() => {
let css = window.CSS
window.CSS = null
return css
})(),
recaptcha: document.querySelector('meta[name="recaptcha"]').getAttribute('content'),
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
version: document.querySelector('meta[name="version"]').getAttribute('content'),
jwt: '',
progress: 0,
schedule: '',

View File

@@ -1,4 +1,4 @@
import i18n from '@/i18n'
import * as i18n from '@/i18n'
import moment from 'moment'
const mutations = {
@@ -27,10 +27,17 @@ const mutations = {
setLoading: (state, value) => { state.loading = value },
setReload: (state, value) => { state.reload = value },
setUser: (state, value) => {
moment.locale(value.locale)
i18n.locale = value.locale
let locale = value.locale
if (locale === '') {
locale = i18n.detectLocale()
}
moment.locale(locale)
i18n.default.locale = locale
state.user = value
},
setCSS: (state, value) => (state.css = value),
setJWT: (state, value) => (state.jwt = value),
multiple: (state, value) => (state.multiple = value),
addSelected: (state, value) => (state.selected.push(value)),
@@ -45,8 +52,12 @@ const mutations = {
resetSelected: (state) => {
state.selected = []
},
listingDisplay: (state, value) => {
state.req.display = value
updateUser: (state, value) => {
if (typeof value !== 'object') return
for (let field in value) {
state.user[field] = value[field]
}
},
updateRequest: (state, value) => {
state.req = value

View File

@@ -18,7 +18,7 @@ export function fetch (url) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -41,7 +41,7 @@ export function remove (url) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('DELETE', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
@@ -62,7 +62,7 @@ export function post (url, content = '', overwrite = false, onupload) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (typeof onupload === 'function') {
request.upload.onprogress = onupload
@@ -95,7 +95,7 @@ export function put (url, content = '', publish = false, date = '') {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('PUT', `${store.state.baseURL}/api/resource${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.setRequestHeader('Publish', publish)
if (date !== '') {
@@ -125,7 +125,7 @@ function moveCopy (items, copy = false) {
promises.push(new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('PATCH', `${store.state.baseURL}/api/resource${from}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.setRequestHeader('Destination', to)
if (copy) {
@@ -162,7 +162,7 @@ export function checksum (url, algo) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/checksum${url}?algo=${algo}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
@@ -226,7 +226,7 @@ export function getSettings () {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/settings/`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -255,7 +255,7 @@ export function updateSettings (param, which) {
let request = new window.XMLHttpRequest()
request.open('PUT', `${store.state.baseURL}/api/settings/`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -278,7 +278,7 @@ export function getUsers () {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/users/`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -299,7 +299,7 @@ export function getUser (id) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/users/${id}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -320,7 +320,7 @@ export function newUser (user) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${store.state.baseURL}/api/users/`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -345,7 +345,7 @@ export function updateUser (user, which) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -370,7 +370,7 @@ export function deleteUser (id) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('DELETE', `${store.state.baseURL}/api/users/${id}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
switch (request.status) {
@@ -395,7 +395,7 @@ export function getShare (url) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/share${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
@@ -414,7 +414,7 @@ export function deleteShare (hash) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('DELETE', `${store.state.baseURL}/api/share/${hash}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
@@ -439,7 +439,7 @@ export function share (url, expires = '', unit = 'hours') {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', url, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {

View File

@@ -16,7 +16,7 @@ function loggedIn () {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/auth/renew`, true)
request.setRequestHeader('Authorization', `Bearer ${cookie('auth')}`)
if (!store.state.noAuth) request.setRequestHeader('Authorization', `Bearer ${cookie('auth')}`)
request.onload = () => {
if (request.status === 200) {
@@ -31,8 +31,8 @@ function loggedIn () {
})
}
function login (user, password) {
let data = {username: user, password: password}
function login (user, password, captcha) {
let data = {username: user, password: password, recaptcha: captcha}
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', `${store.state.baseURL}/api/auth/get`, true)

View File

@@ -1,162 +0,0 @@
<template>
<div class="dashboard">
<ul id="nav">
<li>
<router-link to="/settings/profile">
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.profileSettings') }}
</router-link>
</li>
<li>
<router-link to="/users">
{{ $t('settings.userManagement') }} <i class="material-icons">keyboard_arrow_right</i>
</router-link>
</li>
</ul>
<h1>{{ $t('settings.globalSettings') }}</h1>
<form @submit="saveStaticGen" v-if="$store.state.staticGen.length > 0">
<h2>{{ capitalize($store.state.staticGen) }}</h2>
<p v-for="field in staticGen" :key="field.variable">
<label v-if="field.type !== 'checkbox'">{{ field.name }}</label>
<input v-if="field.type === 'text'" type="text" v-model.trim="field.value">
<input v-else-if="field.type === 'checkbox'" type="checkbox" v-model.trim="field.value">
<template v-if="field.type === 'checkbox'">{{ capitalize(field.name, 'caps') }}</template>
</p>
<p><input type="submit" value="Save"></p>
</form>
<form @submit="saveCommands">
<h2>{{ $t('settings.commands') }}</h2>
<p class="small">{{ $t('settings.commandsHelp') }}</p>
<template v-for="command in commands">
<h3>{{ capitalize(command.name) }}</h3>
<textarea v-model.trim="command.value"></textarea>
</template>
<p><input type="submit" value="Save"></p>
</form>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { getSettings, updateSettings } from '@/utils/api'
export default {
name: 'settings',
data: function () {
return {
commands: [],
staticGen: []
}
},
computed: {
...mapState([ 'user' ])
},
created () {
getSettings()
.then(settings => {
if (this.$store.state.staticGen.length > 0) {
this.parseStaticGen(settings.staticGen)
}
for (let key in settings.commands) {
this.commands.push({
name: key,
value: settings.commands[key].join('\n')
})
}
})
.catch(error => { this.showError(error) })
},
methods: {
...mapMutations([ 'showSuccess', 'showError' ]),
capitalize (name, where = '_') {
if (where === 'caps') where = /(?=[A-Z])/
let splitted = name.split(where)
name = ''
for (let i = 0; i < splitted.length; i++) {
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + ' '
}
return name.slice(0, -1)
},
saveCommands (event) {
event.preventDefault()
let commands = {}
for (let command of this.commands) {
let value = command.value.split('\n')
if (value.length === 1 && value[0] === '') {
value = []
}
commands[command.name] = value
}
updateSettings(commands, 'commands')
.then(() => { this.showSuccess(this.$t('settings.commandsUpdated')) })
.catch(error => { this.showError(error) })
},
saveStaticGen (event) {
event.preventDefault()
let staticGen = {}
for (let field of this.staticGen) {
staticGen[field.variable] = field.value
if (field.original === 'array') {
let val = field.value.split(' ')
if (val[0] === '') {
val.shift()
}
staticGen[field.variable] = val
}
}
updateSettings(staticGen, 'staticGen')
.then(() => { this.showSuccess(this.$t('settings.settingsUpdated')) })
.catch(error => { this.showError(error) })
},
parseStaticGen (staticgen) {
for (let option of staticgen) {
let value = option.value
let field = {
name: option.name,
variable: option.variable,
type: 'text',
original: 'text',
value: value
}
if (Array.isArray(value)) {
field.original = 'array'
field.value = value.join(' ')
this.staticGen.push(field)
continue
}
switch (typeof value) {
case 'boolean':
field.type = 'checkbox'
field.original = 'boolean'
break
}
this.staticGen.push(field)
}
}
}
}
</script>

View File

@@ -6,7 +6,7 @@
<site-header></site-header>
<sidebar></sidebar>
<main>
<router-view v-on:css-updated="updateCSS"></router-view>
<router-view @css="$emit('update:css')"></router-view>
</main>
<prompts></prompts>
</div>
@@ -34,23 +34,10 @@ export default {
}
},
mounted () {
this.updateCSS()
this.$emit('update:css')
},
methods: {
updateCSS () {
let css = this.$store.state.user.css
let style = document.querySelector('style[title="user-css"]')
if (style !== undefined && style !== null) {
style.parentElement.removeChild(style)
}
style = document.createElement('style')
style.title = 'user-css'
style.type = 'text/css'
style.appendChild(document.createTextNode(css))
document.head.appendChild(style)
}
beforeDestroy () {
this.$emit('clean:css')
}
}
</script>

View File

@@ -1,11 +1,12 @@
<template>
<div id="login">
<div id="login" :class="{ recaptcha: recaptcha.length > 0 }">
<form @submit="submit">
<img src="../assets/logo.svg" alt="File Manager">
<h1>File Manager</h1>
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
<input type="text" v-model="username" :placeholder="$t('login.username')">
<input type="password" v-model="password" :placeholder="$t('login.password')">
<div v-if="recaptcha.length" id="recaptcha"></div>
<input type="submit" :value="$t('login.submit')">
</form>
</div>
@@ -13,9 +14,12 @@
<script>
import auth from '@/utils/auth'
import { mapState } from 'vuex'
export default {
name: 'login',
props: ['dependencies'],
computed: mapState(['recaptcha']),
data: function () {
return {
wrong: false,
@@ -23,8 +27,23 @@ export default {
password: ''
}
},
mounted () {
if (this.dependencies) this.setup()
},
watch: {
dependencies: function (val) {
if (val) this.setup()
}
},
methods: {
submit: function (event) {
setup () {
if (this.recaptcha.length === 0) return
window.grecaptcha.render('recaptcha', {
sitekey: this.recaptcha
})
},
submit (event) {
event.preventDefault()
event.stopPropagation()
@@ -33,7 +52,17 @@ export default {
redirect = '/files/'
}
auth.login(this.username, this.password)
let captcha = ''
if (this.recaptcha.length > 0) {
captcha = window.grecaptcha.getResponse()
if (captcha === '') {
this.wrong = true
return
}
}
auth.login(this.username, this.password, captcha)
.then(() => { this.$router.push({ path: redirect }) })
.catch(() => { this.wrong = true })
}

View File

@@ -1,103 +0,0 @@
<template>
<div class="dashboard">
<ul id="nav" v-if="user.admin">
<li>
<router-link to="/settings/global">
{{ $t('settings.globalSettings') }} <i class="material-icons">keyboard_arrow_right</i>
</router-link>
</li>
</ul>
<h1>{{ $t('settings.profileSettings') }}</h1>
<form @submit="updateSettings">
<h3>{{ $t('settings.language') }}</h3>
<p><languages id="locale" :selected.sync="locale"></languages></p>
<h3>{{ $t('settings.customStylesheet') }}</h3>
<textarea v-model="css" name="css"></textarea>
<p><input type="submit" :value="$t('buttons.update')"></p>
</form>
<form @submit="updatePassword">
<h3>{{ $t('settings.changePassword') }}</h3>
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password"></p>
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password"></p>
<p><input type="submit" :value="$t('buttons.update')"></p>
</form>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { updateUser } from '@/utils/api'
import Languages from '@/components/Languages'
export default {
name: 'settings',
components: {
Languages
},
data: function () {
return {
password: '',
passwordConf: '',
css: '',
locale: ''
}
},
computed: {
...mapState([ 'user' ]),
passwordClass () {
if (this.password === '' && this.passwordConf === '') {
return ''
}
if (this.password === this.passwordConf) {
return 'green'
}
return 'red'
}
},
created () {
this.css = this.user.css
this.locale = this.user.locale
},
methods: {
...mapMutations([ 'showSuccess' ]),
updatePassword (event) {
event.preventDefault()
if (this.password !== this.passwordConf) {
return
}
let user = {
ID: this.$store.state.user.ID,
password: this.password
}
updateUser(user, 'password').then(location => {
this.showSuccess(this.$t('settings.passwordUpdated'))
}).catch(e => {
this.$store.commit('showError', e)
})
},
updateSettings (event) {
event.preventDefault()
let user = {...this.$store.state.user}
user.css = this.css
user.locale = this.locale
updateUser(user, 'partial').then(location => {
this.$store.commit('setUser', user)
this.$emit('css-updated')
this.showSuccess(this.$t('settings.settingsUpdated'))
}).catch(e => {
this.$store.commit('showError', e)
})
}
}
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<div class="dashboard">
<ul id="nav" v-if="user.admin">
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
<li :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
<li :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
</ul>
<router-view @css="$emit('css')"></router-view>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'settings',
computed: mapState([ 'user' ])
}
</script>

View File

@@ -1,51 +0,0 @@
<template>
<div class="dashboard">
<ul id="nav">
<li>
<router-link to="/settings/global">
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.globalSettings') }}
</router-link>
</li>
<li></li>
</ul>
<h1>{{ $t('settings.users') }} <router-link to="/users/new"><button>{{ $t('buttons.new') }}</button></router-link></h1>
<table>
<tr>
<th>{{ $t('settings.username') }}</th>
<th>{{ $t('settings.admin') }}</th>
<th>{{ $t('settings.scope') }}</th>
<th></th>
</tr>
<tr v-for="user in users">
<td>{{ user.username }}</td>
<td><i v-if="user.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
<td>{{ user.filesystem }}</td>
<td><router-link :to="'/users/' + user.ID"><i class="material-icons">mode_edit</i></router-link></td>
</tr>
</table>
</div>
</template>
<script>
import * as api from '@/utils/api'
export default {
name: 'users',
data: function () {
return {
users: []
}
},
created () {
api.getUsers().then(users => {
this.users = users
}).catch(error => {
this.$store.commit('showError', error)
})
}
}
</script>

View File

@@ -0,0 +1,187 @@
<template>
<div class="dashboard">
<form class="card" v-if="staticGen.length" @submit.prevent="saveStaticGen">
<div class="card-title">
<h2>{{ capitalize($store.state.staticGen) }}</h2>
</div>
<div class="card-content">
<p v-for="field in staticGen" :key="field.variable">
<label v-if="field.type !== 'checkbox'">{{ field.name }}</label>
<input v-if="field.type === 'text'" type="text" v-model.trim="field.value">
<input v-else-if="field.type === 'checkbox'" type="checkbox" v-model.trim="field.value">
<template v-if="field.type === 'checkbox'">{{ capitalize(field.name, 'caps') }}</template>
</p>
</div>
<div class="card-action">
<input class="flat" type="submit" :value="$t('buttons.update')">
</div>
</form>
<form class="card" @submit.prevent="saveCSS">
<div class="card-title">
<h2>{{ $t('settings.customStylesheet') }}</h2>
</div>
<div class="card-content">
<textarea v-model="css"></textarea>
</div>
<div class="card-action">
<input class="flat" type="submit" :value="$t('buttons.update')">
</div>
</form>
<form class="card" @submit.prevent="saveCommands">
<div class="card-title">
<h2>{{ $t('settings.commands') }}</h2>
</div>
<div class="card-content">
<p class="small">{{ $t('settings.commandsHelp') }}</p>
<div v-for="command in commands" :key="command.name" class="collapsible">
<input :id="command.name" type="checkbox">
<label :for="command.name">
<p>{{ capitalize(command.name) }}</p>
<i class="material-icons">arrow_drop_down</i>
</label>
<div class="collapse">
<textarea v-model.trim="command.value"></textarea>
</div>
</div>
</div>
<div class="card-action">
<input class="flat" type="submit" :value="$t('buttons.update')">
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { getSettings, updateSettings } from '@/utils/api'
export default {
name: 'settings',
data: function () {
return {
commands: [],
staticGen: [],
css: ''
}
},
computed: {
...mapState([ 'user' ])
},
created () {
getSettings()
.then(settings => {
if (this.$store.state.staticGen.length > 0) {
this.parseStaticGen(settings.staticGen)
}
for (let key in settings.commands) {
this.commands.push({
name: key,
value: settings.commands[key].join('\n')
})
}
this.css = settings.css
})
.catch(this.$showError)
},
methods: {
capitalize (name, where = '_') {
if (where === 'caps') where = /(?=[A-Z])/
let splitted = name.split(where)
name = ''
for (let i = 0; i < splitted.length; i++) {
name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + ' '
}
return name.slice(0, -1)
},
saveCommands (event) {
let commands = {}
for (let command of this.commands) {
let value = command.value.split('\n')
if (value.length === 1 && value[0] === '') {
value = []
}
commands[command.name] = value
}
updateSettings(commands, 'commands')
.then(() => { this.$showSuccess(this.$t('settings.commandsUpdated')) })
.catch(this.$showError)
},
saveCSS (event) {
updateSettings(this.css, 'css')
.then(() => {
this.$showSuccess(this.$t('settings.settingsUpdated'))
this.$store.commit('setCSS', this.css)
this.$emit('css')
})
.catch(this.$showError)
},
saveStaticGen (event) {
let staticGen = {}
for (let field of this.staticGen) {
staticGen[field.variable] = field.value
if (field.original === 'array') {
let val = field.value.split(' ')
if (val[0] === '') {
val.shift()
}
staticGen[field.variable] = val
}
}
updateSettings(staticGen, 'staticGen')
.then(() => { this.$showSuccess(this.$t('settings.settingsUpdated')) })
.catch(this.$showError)
},
parseStaticGen (staticgen) {
for (let option of staticgen) {
let value = option.value
let field = {
name: option.name,
variable: option.variable,
type: 'text',
original: 'text',
value: value
}
if (Array.isArray(value)) {
field.original = 'array'
field.value = value.join(' ')
this.staticGen.push(field)
continue
}
switch (typeof value) {
case 'boolean':
field.type = 'checkbox'
field.original = 'boolean'
break
}
this.staticGen.push(field)
}
}
}
}
</script>

View File

@@ -0,0 +1,109 @@
<template>
<div class="dashboard">
<form class="card" @submit="updateSettings">
<div class="card-title">
<h2>{{ $t('settings.profileSettings') }}</h2>
</div>
<div class="card-content">
<h3>{{ $t('settings.language') }}</h3>
<p><languages id="locale" :selected.sync="locale"></languages></p>
<h3>{{ $t('settings.customStylesheet') }}</h3>
<textarea v-model="css" name="css"></textarea>
</div>
<div class="card-action">
<input class="flat" type="submit" :value="$t('buttons.update')">
</div>
</form>
<form class="card" v-if="!user.lockPassword" @submit="updatePassword">
<div class="card-title">
<h2>{{ $t('settings.changePassword') }}</h2>
</div>
<div class="card-content">
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password"></p>
<p><input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password"></p>
</div>
<div class="card-action">
<input class="flat" type="submit" :value="$t('buttons.update')">
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { updateUser } from '@/utils/api'
import Languages from '@/components/Languages'
export default {
name: 'settings',
components: {
Languages
},
data: function () {
return {
password: '',
passwordConf: '',
css: '',
locale: ''
}
},
computed: {
...mapState([ 'user' ]),
passwordClass () {
if (this.password === '' && this.passwordConf === '') {
return ''
}
if (this.password === this.passwordConf) {
return 'green'
}
return 'red'
}
},
created () {
this.css = this.user.css
this.locale = this.user.locale
},
methods: {
updatePassword (event) {
event.preventDefault()
if (this.password !== this.passwordConf) {
return
}
let user = {
ID: this.$store.state.user.ID,
password: this.password
}
updateUser(user, 'password').then(location => {
this.$showSuccess(this.$t('settings.passwordUpdated'))
}).catch(e => {
this.$showError(e)
})
},
updateSettings (event) {
event.preventDefault()
let user = {...this.$store.state.user}
user.css = this.css
user.locale = this.locale
updateUser(user, 'partial').then(location => {
this.$store.commit('setUser', user)
this.$emit('css')
this.$showSuccess(this.$t('settings.settingsUpdated'))
}).catch(e => {
this.$showError(e)
})
}
}
}
</script>

View File

@@ -1,77 +1,81 @@
<template>
<div>
<form @submit="save" class="dashboard">
<ul id="nav">
<li>
<router-link to="/users">
<i class="material-icons">keyboard_arrow_left</i> {{ $t('settings.userManagement') }}
</router-link>
</li>
<li></li>
</ul>
<form @submit="save" class="card">
<div class="card-title">
<h2 v-if="id === 0">{{ $t('settings.newUser') }}</h2>
<h2 v-else>{{ $t('settings.user') }} {{ username }}</h2>
</div>
<h1 v-if="id === 0">{{ $t('settings.newUser') }}</h1>
<h1 v-else>{{ $t('settings.user') }} {{ username }}</h1>
<div class="card-content">
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
<p>
<label for="locale">{{ $t('settings.language') }}</label>
<languages id="locale" :selected.sync="locale"></languages>
</p>
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
<p>
<label for="locale">{{ $t('settings.language') }}</label>
<languages id="locale" :selected.sync="locale"></languages>
</p>
<h2>{{ $t('settings.permissions') }}</h2>
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="lockPassword"> {{ $t('settings.lockPassword') }}</p>
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
<p v-show="$store.state.staticGen.length"><input type="checkbox" :disabled="admin" v-model="allowPublish"> {{ $t('settings.allowPublish') }}</p>
<h3>{{ $t('settings.permissions') }}</h3>
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
<h3>{{ $t('settings.userCommands') }}</h3>
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
<input type="text" v-model.trim="commands">
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowNew"> {{ $t('settings.allowNew') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowEdit"> {{ $t('settings.allowEdit') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="allowCommands"> {{ $t('settings.allowCommands') }}</p>
<p v-show="$store.state.staticGen.length"><input type="checkbox" :disabled="admin" v-model="allowPublish"> {{ $t('settings.allowPublish') }}</p>
<h2>{{ $t('settings.rules') }}</h2>
<h3>{{ $t('settings.userCommands') }}</h3>
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
<input type="text" v-model.trim="commands">
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
<h3>{{ $t('settings.rules') }}</h3>
<i18n path="settings.rulesHelp2" tag="p" class="small">
<code>allow</code><code>disallow</code><code>regex</code>
</i18n>
<p class="small">{{ $t('settings.rulesHelp1') }}</p>
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
<i18n path="settings.rulesHelp2" tag="p" class="small">
<code>allow</code><code>disallow</code><code>regex</code>
</i18n>
<ul class="small">
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
</ul>
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
<textarea v-model.trim="rules"></textarea>
<ul class="small">
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
</ul>
<h2>{{ $t('settings.customStylesheet') }}</h2>
<textarea v-model.trim="rules"></textarea>
<textarea name="css"></textarea>
<h3>{{ $t('settings.customStylesheet') }}</h3>
<p>
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="delete" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
<input type="submit" :value="$t('buttons.save')">
</p>
<textarea name="css"></textarea>
</div>
<div class="card-action">
<button v-if="id !== 0" @click.prevent="deletePrompt" type="button" class="flat delete" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
<input class="flat" type="submit" :value="$t('buttons.save')">
</div>
</form>
<div v-if="$store.state.show === 'deleteUser'" class="prompt">
<h3>Delete User</h3>
<p>Are you sure you want to delete this user?</p>
<div>
<button @click="deleteUser" autofocus>{{ $t('buttons.delete') }}</button>
<button class="cancel"
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
<div class="card-content">
<p>Are you sure you want to delete this user?</p>
</div>
<div class="card-action">
<button class="cancel flat"
@click="closeHovers"
autofocus
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">
{{ $t('buttons.cancel') }}
</button>
<button class="flat"
@click="deleteUser">
{{ $t('buttons.delete') }}
</button>
</div>
</div>
</div>
@@ -93,6 +97,7 @@ export default {
allowEdit: false,
allowCommands: false,
allowPublish: false,
lockPassword: false,
permissions: {},
password: '',
username: '',
@@ -105,7 +110,7 @@ export default {
},
computed: {
passwordPlaceholder () {
if (this.$route.path === '/users/new') return ''
if (this.$route.path === '/settings/users/new') return ''
return this.$t('settings.avoidChanges')
}
},
@@ -120,6 +125,7 @@ export default {
this.allowEdit = true
this.allowNew = true
this.allowPublish = true
this.lockPassword = false
for (let key in this.permissions) {
this.permissions[key] = true
}
@@ -130,7 +136,7 @@ export default {
fetchData () {
let user = this.$route.params[0]
if (this.$route.path === '/users/new') {
if (this.$route.path === '/settings/users/new') {
user = 'base'
}
@@ -141,6 +147,7 @@ export default {
this.allowNew = user.allowNew
this.allowEdit = user.allowEdit
this.allowPublish = user.allowPublish
this.lockPassword = user.lockPassword
this.filesystem = user.filesystem
this.username = user.username
this.commands = user.commands.join(' ')
@@ -166,7 +173,7 @@ export default {
this.rules = this.rules.trim()
}).catch(() => {
this.$router.push({ path: '/users/new' })
this.$router.push({ path: '/settings/users/new' })
})
},
capitalize (name) {
@@ -187,6 +194,7 @@ export default {
this.allowPublish = false
this.permissins = {}
this.allowCommands = false
this.lockPassword = false
this.password = ''
this.username = ''
this.filesystem = ''
@@ -202,22 +210,22 @@ export default {
event.preventDefault()
deleteUser(this.id).then(location => {
this.$router.push({ path: '/users' })
this.$store.commit('showSuccess', this.$t('settings.userDeleted'))
this.$router.push({ path: '/settings/users' })
this.$showSuccess(this.$t('settings.userDeleted'))
}).catch(e => {
this.$store.commit('showError', e)
this.$showError(e)
})
},
save (event) {
event.preventDefault()
let user = this.parseForm()
if (this.$route.path === '/users/new') {
if (this.$route.path === '/settings/users/new') {
newUser(user).then(location => {
this.$router.push({ path: location })
this.$store.commit('showSuccess', this.$t('settings.userCreated'))
this.$showSuccess(this.$t('settings.userCreated'))
}).catch(e => {
this.$store.commit('showError', e)
this.$showError(e)
})
return
@@ -228,9 +236,9 @@ export default {
this.$store.commit('setUser', user)
}
this.$store.commit('showSuccess', this.$t('settings.userUpdated'))
this.$showSuccess(this.$t('settings.userUpdated'))
}).catch(e => {
this.$store.commit('showError', e)
this.$showError(e)
})
},
parseForm () {
@@ -238,6 +246,7 @@ export default {
ID: this.id,
username: this.username,
password: this.password,
lockPassword: this.lockPassword,
filesystem: this.filesystem,
admin: this.admin,
allowCommands: this.allowCommands,

View File

@@ -0,0 +1,48 @@
<template>
<div class="card">
<div class="card-title">
<h2>{{ $t('settings.users') }}</h2>
<router-link to="/settings/users/new"><button class="flat">{{ $t('buttons.new') }}</button></router-link>
</div>
<div class="card-content full">
<table>
<tr>
<th>{{ $t('settings.username') }}</th>
<th>{{ $t('settings.admin') }}</th>
<th>{{ $t('settings.scope') }}</th>
<th></th>
</tr>
<tr v-for="user in users" :key="user.id">
<td>{{ user.username }}</td>
<td><i v-if="user.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
<td>{{ user.filesystem }}</td>
<td class="small">
<router-link :to="'/settings/users/' + user.ID"><i class="material-icons">mode_edit</i></router-link>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import * as api from '@/utils/api'
export default {
name: 'users',
data: function () {
return {
users: []
}
},
created () {
api.getUsers().then(users => {
this.users = users
}).catch(error => {
this.$showError(error)
})
}
}
</script>

View File

@@ -48,6 +48,8 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
scope := "."
database := ""
noAuth := false
reCaptchaKey := ""
reCaptchaSecret := ""
if plugin != "" {
baseURL = "/admin"
@@ -146,6 +148,27 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
}
u.CSS = string(css)
case "view_mode":
if !c.NextArg() {
return nil, c.ArgErr()
}
u.ViewMode = c.Val()
if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
return nil, c.ArgErr()
}
case "recaptcha_key":
if !c.NextArg() {
return nil, c.ArgErr()
}
reCaptchaKey = c.Val()
case "recaptcha_secret":
if !c.NextArg() {
return nil, c.ArgErr()
}
reCaptchaSecret = c.Val()
case "no_auth":
if !c.NextArg() {
noAuth = true
@@ -184,7 +207,7 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
sha := hex.EncodeToString(hasher.Sum(nil))
database = filepath.Join(path, sha+".db")
fmt.Println("[WARNING] A database is going to be created for your File Manager instace at " + database +
fmt.Println("[WARNING] A database is going to be created for your File Manager instance at " + database +
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
}
@@ -204,10 +227,12 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
}
m := &filemanager.FileManager{
NoAuth: noAuth,
BaseURL: "",
PrefixURL: "",
DefaultUser: u,
NoAuth: noAuth,
BaseURL: "",
PrefixURL: "",
ReCaptchaKey: reCaptchaKey,
ReCaptchaSecret: reCaptchaSecret,
DefaultUser: u,
Store: &filemanager.Store{
Config: bolt.ConfigStore{DB: db},
Users: bolt.UsersStore{DB: db},

View File

@@ -24,22 +24,26 @@ import (
)
var (
addr string
config string
database string
scope string
commands string
logfile string
staticg string
locale string
port int
noAuth bool
allowCommands bool
allowEdit bool
allowNew bool
allowPublish bool
showVer bool
version = "master"
addr string
config string
database string
scope string
commands string
logfile string
staticg string
locale string
baseurl string
prefixurl string
viewMode string
recaptchakey string
recaptchasecret string
port int
noAuth bool
allowCommands bool
allowEdit bool
allowNew bool
allowPublish bool
showVer bool
)
func init() {
@@ -49,13 +53,18 @@ func init() {
flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
flag.StringVar(&locale, "locale", "en", "Default locale for new users")
flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
}
@@ -72,8 +81,13 @@ func setupViper() {
viper.SetDefault("AllowNew", true)
viper.SetDefault("AllowPublish", true)
viper.SetDefault("StaticGen", "")
viper.SetDefault("Locale", "en")
viper.SetDefault("Locale", "")
viper.SetDefault("NoAuth", false)
viper.SetDefault("BaseURL", "")
viper.SetDefault("PrefixURL", "")
viper.SetDefault("ViewMode", filemanager.MosaicViewMode)
viper.SetDefault("ReCaptchaKey", "")
viper.SetDefault("ReCaptchaSecret", "")
viper.BindPFlag("Port", flag.Lookup("port"))
viper.BindPFlag("Address", flag.Lookup("address"))
@@ -88,21 +102,18 @@ func setupViper() {
viper.BindPFlag("Locale", flag.Lookup("locale"))
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
viper.SetConfigName("filemanager")
viper.AddConfigPath(".")
}
func printVersion() {
version = strings.TrimSpace(version)
if version == "" {
fmt.Println("filemanager is at an untracked version")
} else {
version = strings.TrimPrefix(version, "v")
fmt.Println("filemanager version", version)
}
fmt.Println("filemanager version", filemanager.Version)
os.Exit(0)
}
@@ -176,9 +187,11 @@ func handler() http.Handler {
}
fm := &filemanager.FileManager{
NoAuth: viper.GetBool("NoAuth"),
BaseURL: "",
PrefixURL: "",
NoAuth: viper.GetBool("NoAuth"),
BaseURL: viper.GetString("BaseURL"),
PrefixURL: viper.GetString("PrefixURL"),
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
DefaultUser: &filemanager.User{
AllowCommands: viper.GetBool("AllowCommands"),
AllowEdit: viper.GetBool("AllowEdit"),
@@ -190,6 +203,7 @@ func handler() http.Handler {
CSS: "",
Scope: viper.GetString("Scope"),
FileSystem: fileutils.Dir(viper.GetString("Scope")),
ViewMode: viper.GetString("ViewMode"),
},
Store: &filemanager.Store{
Config: bolt.ConfigStore{DB: db},

View File

@@ -67,8 +67,6 @@ type Listing struct {
Sort string `json:"sort"`
// And which order.
Order string `json:"order"`
// Displays in mosaic or list.
Display string `json:"display"`
}
// GetInfo gets the file information and, in case of error, returns the

View File

@@ -3,6 +3,7 @@ package filemanager
import (
"crypto/rand"
"errors"
"fmt"
"log"
"net/http"
"os"
@@ -20,6 +21,15 @@ import (
"github.com/robfig/cron"
)
// Version is the current File Manager version.
const (
// Version is the current File Manager version.
Version = "1.3.7"
ListViewMode = "list"
MosaicViewMode = "mosaic"
)
var (
ErrExist = errors.New("the resource already exists")
ErrNotExist = errors.New("the resource does not exist")
@@ -62,6 +72,10 @@ type FileManager struct {
// there will only exist one user, called "admin".
NoAuth bool
// ReCaptcha Site key and secret.
ReCaptchaKey string
ReCaptchaSecret string
// StaticGen is the static websit generator handler.
StaticGen StaticGen
@@ -71,10 +85,28 @@ type FileManager struct {
// A map of events to a slice of commands.
Commands map[string][]string
// Global stylesheet.
CSS string
// NewFS should build a new file system for a given path.
NewFS FSBuilder
}
var commandEvents = []string{
"before_save",
"after_save",
"before_publish",
"after_publish",
"before_copy",
"after_copy",
"before_rename",
"after_rename",
"before_upload",
"after_upload",
"before_delete",
"after_delete",
}
// Command is a command function.
type Command func(r *http.Request, m *FileManager, u *User) error
@@ -108,16 +140,39 @@ func (m *FileManager) Setup() error {
return err
}
// Get the global CSS.
err = m.Store.Config.Get("css", &m.CSS)
if err != nil && err == ErrNotExist {
err = m.Store.Config.Save("css", "")
}
if err != nil {
return err
}
// Tries to get the event commands from the database.
// If they don't exist, initialize them.
err = m.Store.Config.Get("commands", &m.Commands)
if err != nil && err == ErrNotExist {
m.Commands = map[string][]string{
"before_save": {},
"after_save": {},
"before_publish": {},
"after_publish": {},
if err == nil {
// Add hypothetically new command handlers.
for _, command := range commandEvents {
if _, ok := m.Commands[command]; ok {
continue
}
m.Commands[command] = []string{}
}
}
if err != nil && err == ErrNotExist {
m.Commands = map[string][]string{}
// Initialize the command handlers.
for _, command := range commandEvents {
m.Commands[command] = []string{}
}
err = m.Store.Config.Save("commands", m.Commands)
}
@@ -156,6 +211,14 @@ func (m *FileManager) Setup() error {
}
}
// TODO: remove this after 1.5
for _, user := range users {
if user.ViewMode != ListViewMode && user.ViewMode != MosaicViewMode {
user.ViewMode = ListViewMode
m.Store.Users.Update(user, "ViewMode")
}
}
m.DefaultUser.Username = ""
m.DefaultUser.Password = ""
@@ -232,7 +295,7 @@ func (m FileManager) ShareCleaner() {
}
// Runner runs the commands for a certain event type.
func (m FileManager) Runner(event string, path string) error {
func (m FileManager) Runner(event string, path string, destination string, user *User) error {
commands := []string{}
// Get the commands from the File Manager instance itself.
@@ -257,7 +320,15 @@ func (m FileManager) Runner(event string, path string) error {
}
cmd := exec.Command(command, args...)
cmd.Env = append(os.Environ(), "file="+path)
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
cmd.Env = append(cmd.Env, fmt.Sprintf("ROOT=%s", string(user.Scope)))
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", event))
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
if destination != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", destination))
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -286,13 +357,15 @@ var DefaultUser = User{
AllowEdit: true,
AllowNew: true,
AllowPublish: true,
LockPassword: false,
Commands: []string{},
Rules: []*Rule{},
CSS: "",
Admin: true,
Locale: "en",
Locale: "",
Scope: ".",
FileSystem: fileutils.Dir("."),
ViewMode: "mosaic",
}
// User contains the configuration for each user.
@@ -325,6 +398,9 @@ type User struct {
// Locale is the language of the user.
Locale string `json:"locale"`
// Prevents the user to change its password.
LockPassword bool `json:"lockPassword"`
// These indicate if the user can perform certain actions.
AllowNew bool `json:"allowNew"` // Create files and folders
AllowEdit bool `json:"allowEdit"` // Edit/rename files
@@ -333,6 +409,9 @@ type User struct {
// Commands is the list of commands the user can execute.
Commands []string `json:"commands"`
// User view mode for files and folders.
ViewMode string `json:"viewMode"`
}
// Allowed checks if the user has permission to access a directory/file.

View File

@@ -1,8 +1,11 @@
package http
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
@@ -11,6 +14,45 @@ import (
fm "github.com/hacdias/filemanager"
)
type cred struct {
Password string `json:"password"`
Username string `json:"username"`
Recaptcha string `json:"recaptcha"`
}
// recaptcha checks the recaptcha code.
func recaptcha(secret string, response string) (bool, error) {
api := "https://www.google.com/recaptcha/api/siteverify"
body := url.Values{}
body.Set("secret", secret)
body.Add("response", response)
client := &http.Client{}
resp, err := client.Post(api, "application/x-www-form-urlencoded", bytes.NewBufferString(body.Encode()))
if err != nil {
return false, err
}
if resp.StatusCode != http.StatusOK {
return false, nil
}
var data struct {
Success bool `json:"success"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes interface{} `json:"error-codes"`
}
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
return false, err
}
return data.Success, nil
}
// authHandler proccesses the authentication for the user.
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
// NoAuth instances shouldn't call this method.
@@ -19,7 +61,7 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
}
// Receive the credentials from the request and unmarshal them.
var cred fm.User
var cred cred
if r.Body == nil {
return http.StatusForbidden, nil
}
@@ -29,6 +71,19 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
return http.StatusForbidden, nil
}
// If ReCaptcha is enabled, check the code.
if len(c.ReCaptchaSecret) > 0 {
ok, err := recaptcha(c.ReCaptchaSecret, cred.Recaptcha)
if err != nil {
fmt.Println(err)
return http.StatusForbidden, err
}
if !ok {
return http.StatusForbidden, nil
}
}
// Checks if the user exists.
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
if err != nil {
@@ -59,7 +114,6 @@ func renewAuthHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
// claims is the JWT claims.
type claims struct {
fm.User
NoAuth bool `json:"noAuth"`
jwt.StandardClaims
}
@@ -74,7 +128,6 @@ func printToken(c *fm.Context, w http.ResponseWriter) (int, error) {
// Builds the claims.
claims := claims{
u,
c.NoAuth,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
Issuer: "File Manager",

View File

@@ -25,7 +25,7 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
} else {
w.Header().Set("Content-Disposition", "attachment; filename="+c.File.Name)
w.Header().Set("Content-Disposition", "attachment; filename=\""+c.File.Name+"\"")
}
http.ServeFile(w, r, c.File.Path)
@@ -107,7 +107,7 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
defer file.Close()
w.Header().Set("Content-Disposition", "attachment; filename="+name)
w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
_, err = io.Copy(w, file)
return 0, err
}

View File

@@ -6,6 +6,7 @@ import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@@ -24,16 +25,13 @@ func Handler(m *fm.FileManager) http.Handler {
if code >= 400 {
w.WriteHeader(code)
if err == nil {
txt := http.StatusText(code)
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
w.Write([]byte(txt))
}
txt := http.StatusText(code)
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
w.Write([]byte(txt + "\n"))
}
if err != nil {
log.Print(err)
w.Write([]byte(err.Error()))
}
})
}
@@ -53,11 +51,7 @@ func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
// Check if this request is made to the service worker. If so,
// pass it through a template to add the needed variables.
if r.URL.Path == "/sw.js" {
return renderFile(
c, w,
c.Assets.MustString("sw.js"),
"application/javascript",
)
return renderFile(c, w, "sw.js")
}
// Checks if this request is made to the static assets folder. If so, and
@@ -95,11 +89,7 @@ func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
w.Header().Set("x-content-type", "nosniff")
w.Header().Set("x-xss-protection", "1; mode=block")
return renderFile(
c, w,
c.Assets.MustString("index.html"),
"text/html",
)
return renderFile(c, w, "index.html")
}
// staticHandler handles the static assets path.
@@ -109,11 +99,7 @@ func staticHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
return 0, nil
}
return renderFile(
c, w,
c.Assets.MustString("static/manifest.json"),
"application/json",
)
return renderFile(c, w, "static/manifest.json")
}
// apiHandler is the main entry point for the /api endpoint.
@@ -219,11 +205,32 @@ func splitURL(path string) (string, string) {
}
// renderFile renders a file using a template with some needed variables.
func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType string) (int, error) {
tpl := template.Must(template.New("file").Parse(file))
func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error) {
tpl := template.Must(template.New("file").Parse(c.Assets.MustString(file)))
var contentType string
switch filepath.Ext(file) {
case ".html":
contentType = "text/html"
case ".js":
contentType = "application/javascript"
case ".json":
contentType = "application/json"
default:
contentType = "text"
}
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
data := map[string]interface{}{"BaseURL": c.RootURL()}
data := map[string]interface{}{
"BaseURL": c.RootURL(),
"NoAuth": c.NoAuth,
"Version": fm.Version,
"CSS": template.CSS(c.CSS),
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
"ReCaptchaKey": c.ReCaptchaKey,
"ReCaptchaSecret": c.ReCaptchaSecret,
}
if c.StaticGen != nil {
data["StaticGen"] = c.StaticGen.Name()
@@ -242,11 +249,8 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string, contentType s
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
s, err := c.Store.Share.Get(r.URL.Path)
if err == fm.ErrNotExist {
return renderFile(
c, w,
c.Assets.MustString("static/share/404.html"),
"text/html",
)
w.WriteHeader(http.StatusNotFound)
return renderFile(c, w, "static/share/404.html")
}
if err != nil {
@@ -255,17 +259,15 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
if s.Expires && s.ExpireDate.Before(time.Now()) {
c.Store.Share.Delete(s.Hash)
return renderFile(
c, w,
c.Assets.MustString("static/share/404.html"),
"text/html",
)
w.WriteHeader(http.StatusNotFound)
return renderFile(c, w, "static/share/404.html")
}
r.URL.Path = s.Path
info, err := os.Stat(s.Path)
if err != nil {
c.Store.Share.Delete(s.Hash)
return ErrorToHTTP(err, false), err
}

View File

@@ -38,7 +38,7 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
case http.MethodPut:
// Before save command handler.
path := filepath.Join(c.User.Scope, r.URL.Path)
if err := c.Runner("before_save", path); err != nil {
if err := c.Runner("before_save", path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
@@ -48,7 +48,7 @@ func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
// After save command handler.
if err := c.Runner("after_save", path); err != nil {
if err := c.Runner("after_save", path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
@@ -130,8 +130,6 @@ func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
}
listing.ApplySort()
listing.Display = displayMode(w, r, cookieScope)
return renderJSON(w, f)
}
@@ -141,12 +139,22 @@ func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request
return http.StatusForbidden, nil
}
// Fire the before trigger.
if err := c.Runner("before_delete", r.URL.Path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
// Remove the file or folder.
err := c.User.FileSystem.RemoveAll(r.URL.Path)
if err != nil {
return ErrorToHTTP(err, true), err
}
// Fire the after trigger.
if err := c.Runner("after_delete", r.URL.Path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
@@ -160,7 +168,7 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
}
// Discard any invalid upload before returning to avoid connection
// reset fm.Error.
// reset error.
defer func() {
io.Copy(ioutil.Discard, r.Body)
}()
@@ -179,14 +187,19 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
}
// If using POST method, we are trying to create a new file so it is not
// desirable to ovfm.Erride an already existent file. Thus, we check
// desirable to override an already existent file. Thus, we check
// if the file already exists. If so, we just return a 409 Conflict.
if r.Method == http.MethodPost && r.Header.Get("Action") != "ovfm.Erride" {
if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
return http.StatusConflict, errors.New("There is already a file on that path")
}
}
// Fire the before trigger.
if err := c.Runner("before_upload", r.URL.Path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
// Create/Open the file.
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
if err != nil {
@@ -218,6 +231,12 @@ func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Reques
// Writes the ETag Header.
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
w.Header().Set("ETag", etag)
// Fire the after trigger.
if err := c.Runner("after_upload", r.URL.Path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
@@ -256,7 +275,7 @@ func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
path := filepath.Join(c.User.Scope, r.URL.Path)
// Before save command handler.
if err := c.Runner("before_publish", path); err != nil {
if err := c.Runner("before_publish", path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
@@ -266,7 +285,7 @@ func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
// Executed the before publish command.
if err := c.Runner("before_publish", path); err != nil {
if err := c.Runner("before_publish", path, "", c.User); err != nil {
return http.StatusInternalServerError, err
}
@@ -293,40 +312,36 @@ func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request)
}
if action == "copy" {
// Fire the after trigger.
if err := c.Runner("before_copy", src, dst, c.User); err != nil {
return http.StatusInternalServerError, err
}
// Copy the file.
err = c.User.FileSystem.Copy(src, dst)
// Fire the after trigger.
if err := c.Runner("after_copy", src, dst, c.User); err != nil {
return http.StatusInternalServerError, err
}
} else {
// Fire the after trigger.
if err := c.Runner("before_rename", src, dst, c.User); err != nil {
return http.StatusInternalServerError, err
}
// Rename the file.
err = c.User.FileSystem.Rename(src, dst)
// Fire the after trigger.
if err := c.Runner("after_rename", src, dst, c.User); err != nil {
return http.StatusInternalServerError, err
}
}
return ErrorToHTTP(err, true), err
}
// displayMode obtains the display mode from the Cookie.
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
var displayMode string
// Checks the cookie.
if displayCookie, err := r.Cookie("display"); err == nil {
displayMode = displayCookie.Value
}
// If it's invalid, set it to mosaic, which is the default.
if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
displayMode = "mosaic"
}
// Set the cookie.
http.SetCookie(w, &http.Cookie{
Name: "display",
Value: displayMode,
MaxAge: 31536000,
Path: scope,
Secure: r.TLS != nil,
})
return displayMode
}
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {

View File

@@ -13,6 +13,7 @@ import (
type modifySettingsRequest struct {
*modifyRequest
Data struct {
CSS string `json:"css"`
Commands map[string][]string `json:"commands"`
StaticGen map[string]interface{} `json:"staticGen"`
} `json:"data"`
@@ -61,6 +62,7 @@ func settingsHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
type settingsGetRequest struct {
CSS string `json:"css"`
Commands map[string][]string `json:"commands"`
StaticGen []option `json:"staticGen"`
}
@@ -73,6 +75,7 @@ func settingsGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
result := &settingsGetRequest{
Commands: c.Commands,
StaticGen: []option{},
CSS: c.CSS,
}
if c.StaticGen != nil {
@@ -114,6 +117,16 @@ func settingsPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (
return http.StatusOK, nil
}
// Update the global CSS.
if mod.Which == "css" {
if err := c.Store.Config.Save("css", mod.Data.CSS); err != nil {
return http.StatusInternalServerError, err
}
c.CSS = mod.Data.CSS
return http.StatusOK, nil
}
// Update the static generator options.
if mod.Which == "staticGen" {
err = mapstructure.Decode(mod.Data.StaticGen, c.StaticGen)

View File

@@ -44,6 +44,10 @@ func shareGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
}
}
if len(s) == 0 {
return http.StatusNotFound, nil
}
return renderJSON(w, s)
}

View File

@@ -182,6 +182,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
}
u.Password = pw
u.ViewMode = fm.MosaicViewMode
// Saves the user to the database.
err = c.Store.Users.Save(u)
@@ -194,7 +195,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
}
// Set the Location header and return.
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
w.Header().Set("Location", "/settings/users/"+strconv.Itoa(u.ID))
w.WriteHeader(http.StatusCreated)
return 0, nil
}
@@ -268,12 +269,20 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
return http.StatusBadRequest, err
}
// If we're updating the default user. Only for NoAuth
// implementations. Used to change the viewMode.
if id == 0 && c.NoAuth {
c.DefaultUser.ViewMode = u.ViewMode
return http.StatusOK, nil
}
// Updates the CSS and locale.
if which == "partial" {
c.User.CSS = u.CSS
c.User.Locale = u.Locale
c.User.ViewMode = u.ViewMode
err = c.Store.Users.Update(c.User, "CSS", "Locale")
err = c.Store.Users.Update(c.User, "CSS", "Locale", "ViewMode")
if err != nil {
return http.StatusInternalServerError, err
}
@@ -287,6 +296,10 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
return http.StatusBadRequest, fm.ErrEmptyPassword
}
if id == c.User.ID && c.User.LockPassword {
return http.StatusForbidden, nil
}
c.User.Password, err = fm.HashPassword(u.Password)
if err != nil {
return http.StatusInternalServerError, err

23
package-lock.json generated
View File

@@ -4941,6 +4941,11 @@
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-7.0.0.tgz",
"integrity": "sha1-q/sd2CRwZ04DIrU86xqvQSk45L8="
},
"noty": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/noty/-/noty-3.1.2.tgz",
"integrity": "sha512-5Rn8695fMcGTJdn8gIrG0sYXXNIEwc8BFq4EpyHF3rFwoAt7/kgN2lKrIZ0bUxtFZuYFcnfqwU+4uF6vm21bVg=="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -8050,15 +8055,6 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
@@ -8095,6 +8091,15 @@
}
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",

View File

@@ -14,6 +14,7 @@
"filesize": "^3.5.10",
"moment": "^2.18.1",
"normalize.css": "^7.0.0",
"noty": "^3.1.2",
"vue": "^2.3.3",
"vue-i18n": "^7.1.0",
"vue-router": "^2.7.0",

24
publish.sh Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
echo "Building assets"
./build.sh
echo "Updating version number to $1..."
sed -i "s|(untracked)|$1|g" filemanager.go
echo "Commiting..."
git add -A
git commit -m "Version $1"
git push
echo "Creating the tag..."
git tag "v$1"
git push --tags
echo "Commiting untracked version notice..."
sed -i "s|$1|(untracked)|g" filemanager.go
git add -A
git commit -m "[ci skip] auto: setting untracked version"
git push
echo "Done!"

View File

@@ -1 +1 @@
6a08f1e90218ebb33745a5034efe896329ee9675
882c59547968e4fb9a65aa8d1c838409f198e51b