Compare commits

...

74 Commits

Author SHA1 Message Date
Henrique Dias
aeb0f37e20 Version 1.3.9
Former-commit-id: 9c7d15644bcab9e3babc8d645615e50d99187871 [formerly f0a0bf08c328b801cc6ed5add830732c99f92047] [formerly 250bc983264deb841b2115a03610b8e6f473f1ca [formerly b903356b94]]
Former-commit-id: 56bb31f4e44ad39a65f1d1eaa0a8e493287fad7f [formerly e120e69e05cc67f377a83f19f4106b7be945780f]
Former-commit-id: 36fb7783992200295b2f9811b97c2312977023a4
2017-10-31 19:20:36 +00:00
Henrique Dias
59a0daa293 Fix ViewMode related bugs:
- The user will no longer lost their 'ViewMode' option after being updated in the settings.
- The console will not output errors due tot he scroll function when Mosaic mode is on.


Former-commit-id: 97aa6abdc8b864dc7a55dbf03a2e58895ea7613f [formerly ff9e6ff0898f32bd106b644b2e9002b5de45281c] [formerly 556cc12bd5ff1d91776c81f48dd1dceb8bb627b4 [formerly 51104c5ee7]]
Former-commit-id: dd63b2b818a7bd4960a7243866d6b2829f4c03a8 [formerly 45855d70eaa9a2b060d3a89cb70388040ea8e6d9]
Former-commit-id: 6e0ebf10d7fe3e2c234abc9c0ffd447cfbdd7455
2017-10-31 18:23:31 +00:00
Henrique Dias
c716d126eb [ci skip] auto: setting untracked version
Former-commit-id: 88d8cc322b27fc1165a53779e7226db8fd6f90ab [formerly 7f0e5a193dba73cb133bd4d72cd0d81bdc354736] [formerly da79e8c40409e5b5995749556155cccd8a0325cd [formerly cc5f3ac1dd]]
Former-commit-id: 47f0176b53e3a5b2eb402eba29a07f51b7d0c7a7 [formerly c1d2d78eb8827f3386080b0309e6cef66bfed9b1]
Former-commit-id: 64c1ec091ce58eb6b38b0281b7a728b2ca07fe1a
2017-10-30 16:24:48 +00:00
Henrique Dias
5dc210c000 Version 1.3.8
Former-commit-id: f69ca91fc441142e07dfe8dd5cb92c419a1cd44a [formerly 679e456d16586b10e00c5aceb1cea75eb62e10dc] [formerly 50f4889e1eb2797c0c899af48acb1305fa33b671 [formerly 1e700ef167]]
Former-commit-id: ba0df3d330275d2fa1a5e02c608051f3f1422124 [formerly 77d49e18d479776cf670eb008878364fd4c7292a]
Former-commit-id: 2bfe39e4b6371996559557eeb00a15a895e4321a
2017-10-30 16:24:27 +00:00
Henrique Dias
3bb3fdb956 GOFMT
Former-commit-id: bbc11e551ed79fc41af61d67bfb9a882dfc8e655 [formerly 4ef42ee3251bdf613b7e24b50692629fbf8965ed] [formerly 10abcd7f0e34753069a80a683df94084f03fdb4b [formerly 1d4cbd622c]]
Former-commit-id: 7ac0c18ad61cd491f22b906e1efd87892d729cb2 [formerly b45c7e312058490ae90c8162e6cc1264613a9126]
Former-commit-id: 1c7e8ebd2f1b1dc5bd25df9f30a77c9b73ccad4d
2017-10-30 15:47:59 +00:00
Henrique Dias
b96a4a63c0 whitelist .htaccess to be edited; close #258
Former-commit-id: 1114b97a9b0cf236d96ce283bbf8d482cc85170f [formerly 6419fbd11f28ce1dc5ebdfcde0d34adabd016052] [formerly c1135ecdb720db11f4e1182c5456c484f5f58ed1 [formerly 72ee240ce3]]
Former-commit-id: 09fc62ffc432b822ea495b19bc5daafbd3ed8912 [formerly c1eb12242b30a1d90299427c387e0b2a25ce0f24]
Former-commit-id: e097526998a0d9c6382bd9d97af3bf161e8fcd0c
2017-10-30 15:36:40 +00:00
Henrique Dias
3d54b2bd90 fix #260
Former-commit-id: 0d8742754bb756ad3a83599850dae5f477282430 [formerly 5cb7d75b695d8400fc2af87edd551d6450e7365f] [formerly a6a814c40a5ff4f195c4ab470d4fccc92bd8c1c8 [formerly 99c8c92c6c]]
Former-commit-id: 45eba5ff05f8e64fbf33d9d670e19a0cf4880656 [formerly 88dc856045b9d51596f36ce387b1c4f3e85a7d3c]
Former-commit-id: 1eadaef460060da8ae71df3c66f242c844992725
2017-10-30 15:24:06 +00:00
Henrique Dias
95d43a344c [ci skip] auto: setting untracked version
Former-commit-id: 4f9b713ad6cd1f90e7569c9eb083b2740b2f6a60 [formerly ffa130fe00c78b2431ceb16bf1e18345a98826a3] [formerly 5aecd62f16317a53810677fc49a40af297d6bd91 [formerly f4f7147717]]
Former-commit-id: cd5e3739e28213efeaa59cb1fe68b25b35068d46 [formerly 10edbd82feb4d35296c1edf1ab977860b7504470]
Former-commit-id: 4067d711f14eb26232204a89a6263b989ee0494f
2017-10-29 13:33:42 +00:00
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
119 changed files with 4443 additions and 3779 deletions

View File

@@ -1,13 +1,13 @@
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}

View File

@@ -1,4 +1,4 @@
assets/
testdata/
caddy/
.github/
assets/
testdata/
caddy/
.github/

View File

@@ -1,14 +1,14 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# 4 space indentation
[*.go]
indent_style = tab
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# 4 space indentation
[*.go]
indent_style = tab
indent_size = 4

View File

@@ -1,2 +1,2 @@
build/*.js
config/*.js
build/*.js
config/*.js

View File

@@ -1,27 +1,27 @@
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

View File

@@ -1,24 +1,24 @@
### Instructions (remove before submitting):
1. Are you asking for help with using Caddy or File Manager? Please use our forum instead: https://forum.caddyserver.com.
2. If you are filing a bug report, please answer the following questions.
3. If your issue is not a bug report, you do not need to use this template.
4. If not using with Caddy, ignore questions 1 and 2.
### 1. Have you downloaded File Manager from caddyserver.com? If yes, when have you done that? If no, and you are running a custom build, which is the revision of File Manager's repository?
### 2. What is your entire Caddyfile?
```text
(Put Caddyfile here)
```
### 3. What are you trying to do?
### 4. What did you expect to see?
### 5. What did you see instead (give full error messages and/or log)?
### 6. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?
### Instructions (remove before submitting):
1. Are you asking for help with using Caddy or File Manager? Please use our forum instead: https://forum.caddyserver.com.
2. If you are filing a bug report, please answer the following questions.
3. If your issue is not a bug report, you do not need to use this template.
4. If not using with Caddy, ignore questions 1 and 2.
### 1. Have you downloaded File Manager from caddyserver.com? If yes, when have you done that? If no, and you are running a custom build, which is the revision of File Manager's repository?
### 2. What is your entire Caddyfile?
```text
(Put Caddyfile here)
```
### 3. What are you trying to do?
### 4. What did you expect to see?
### 5. What did you see instead (give full error messages and/or log)?
### 6. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?

2
.gitignore vendored
View File

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

View File

@@ -1,29 +1,29 @@
build:
main: cmd/filemanager/main.go
binary: filemanager
goos:
- darwin
- linux
- windows
- freebsd
- netbsd
- openbsd
goarch:
- amd64
- 386
- arm
- arm64
ignore:
- goos: openbsd
goarch: arm
goarm: 6
- goos: freebsd
goarch: arm
goarm: 6
archive:
name_template: "{{.Os}}-{{.Arch}}-{{ .ProjectName }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip
build:
main: cmd/filemanager/main.go
binary: filemanager
goos:
- darwin
- linux
- windows
- freebsd
- netbsd
- openbsd
goarch:
- amd64
- 386
- arm
- arm64
ignore:
- goos: openbsd
goarch: arm
goarm: 6
- goos: freebsd
goarch: arm
goarm: 6
archive:
name_template: "{{.Os}}-{{.Arch}}-{{ .ProjectName }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip

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,46 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hacdias@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hacdias@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,14 +1,14 @@
# Contributing
If you want to contribute or want to build the code from source, you will need to have the most recent version of Go and, if you want to change the static assets (JS, CSS, ...), Node.js installed on your computer. To start developing, you just need to do the following:
1. `go get github.com/hacdias/filemanager`
2. `cd $GOPATH/src/github.com/hacdias/filemanager`
3. `npm install`
4. `npm run dev` - regenerates the static assets automatically
5. `go install github.com/hacdias/filemanager/cmd/filemanager`
6. Execute `$GOPATH/bin/filemanager`
The steps 3 and 4 are only required **if you want to develop the front-end**. Otherwise, you can ignore them. Before pulling, if you made any change on assets folder, you must run the `build.sh` script on the root of this repository.
If you are using this as a Caddy plugin, you should use its [official instructions for plugins](https://github.com/mholt/caddy/wiki/Extending-Caddy#2-plug-in-your-plugin) and import `github.com/hacdias/filemanager/caddy/filemanager`.
# Contributing
If you want to contribute or want to build the code from source, you will need to have the most recent version of Go and, if you want to change the static assets (JS, CSS, ...), Node.js installed on your computer. To start developing, you just need to do the following:
1. `go get github.com/hacdias/filemanager`
2. `cd $GOPATH/src/github.com/hacdias/filemanager`
3. `npm install`
4. `npm run dev` - regenerates the static assets automatically
5. `go install github.com/hacdias/filemanager/cmd/filemanager`
6. Execute `$GOPATH/bin/filemanager`
The steps 3 and 4 are only required **if you want to develop the front-end**. Otherwise, you can ignore them. Before pulling, if you made any change on assets folder, you must run the `build.sh` script on the root of this repository.
If you are using this as a Caddy plugin, you should use its [official instructions for plugins](https://github.com/mholt/caddy/wiki/Extending-Caddy#2-plug-in-your-plugin) and import `github.com/hacdias/filemanager/caddy/filemanager`.

View File

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

View File

@@ -1,22 +1,22 @@
FROM golang:alpine
COPY . /go/src/github.com/hacdias/filemanager
WORKDIR /go/src/github.com/hacdias/filemanager
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 mv filemanager /go/bin/filemanager
FROM alpine:latest
COPY --from=0 /go/bin/filemanager /usr/local/bin/filemanager
VOLUME /srv
EXPOSE 80
COPY Docker.json /etc/config.json
ENTRYPOINT ["/usr/local/bin/filemanager"]
CMD ["--config", "/etc/config.json"]
FROM golang:alpine
COPY . /go/src/github.com/hacdias/filemanager
WORKDIR /go/src/github.com/hacdias/filemanager
RUN apk add --no-cache git
RUN go get ./...
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
RUN CGO_ENABLED=0 go build -a
RUN mv filemanager /go/bin/filemanager
FROM scratch
COPY --from=0 /go/bin/filemanager /filemanager
VOLUME /srv
EXPOSE 80
COPY Docker.json /config.json
ENTRYPOINT ["/filemanager"]
CMD ["--config", "/config.json"]

View File

@@ -1,201 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

156
README.md
View File

@@ -1,78 +1,78 @@
![Preview](https://user-images.githubusercontent.com/5447088/28537288-39be4288-70a2-11e7-8ce9-0813d59f46b7.gif)
# filemanager
[![Build](https://img.shields.io/travis/hacdias/filemanager.svg?style=flat-square)](https://travis-ci.org/hacdias/filemanager)
[![Go Report Card](https://goreportcard.com/badge/github.com/hacdias/filemanager?style=flat-square)](https://goreportcard.com/report/hacdias/filemanager)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/hacdias/filemanager)
filemanager provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
# Table of contents
+ [Getting started](#getting-started)
+ [Features](#features)
- [Users](#users)
- [Search](#search)
+ [Contributing](#contributing)
+ [Donate](#donate)
# Getting started
You can find the Getting Started guide on the [documentation](https://henriquedias.com/filemanager/quick-start/).
# Features
Easy login system.
![Login Page](https://user-images.githubusercontent.com/5447088/28432382-975493dc-6d7f-11e7-9190-23f8037159dc.jpg)
Listings of your files, available in two styles: mosaic and list. You can delete, move, rename, upload and create new files, as well as directories. Single files can be downloaded directly, and multiple files as *.zip*, *.tar*, *.tar.gz*, *.tar.bz2* or *.tar.xz*.
![Mosaic Listing](https://user-images.githubusercontent.com/5447088/28432384-9771bb4c-6d7f-11e7-8564-3a9bd6a3ce3a.jpg)
File Manager editor is powered by [Codemirror](https://codemirror.net/) and if you're working with markdown files with metadata, both parts will be separated from each other so you can focus on the content.
![Markdown Editor](https://user-images.githubusercontent.com/5447088/28432383-9756fdac-6d7f-11e7-8e58-fec49470d15f.jpg)
On the settings page, a regular user can set its own custom CSS to personalize the experience and change its password. For admins, they can manage the permissions of each user, set commands which can be executed when certain events are triggered (such as before saving and after saving) and change plugin's settings.
![Settings](https://user-images.githubusercontent.com/5447088/28432385-9776ec66-6d7f-11e7-90a5-891bacd4d02f.jpg)
We also allow the users to search in the directories and execute commands if allowed.
## Users
We support multiple users and each user can have its own scope and custom stylesheet. The administrator is able to choose which permissions should be given to the users, as well as the commands they can execute. Each user also have a set of rules, in which he can be prevented or allowed to access some directories (regular expressions included!).
![Users](https://user-images.githubusercontent.com/5447088/28432386-977f388a-6d7f-11e7-9006-87d16f05f1f8.jpg)
## Search
FileManager allows you to search through your files and it has some options. By default, your search will be something like this:
```
this are keywords
```
If you search for that it will look at every file that contains "this", "are" or "keywords" on their name. If you want to search for an exact term, you should surround your search by double quotes:
```
"this is the name"
```
That will search for any file that contains "this is the name" on its name. It won't search for each separated term this time.
By default, every search will be case sensitive. Although, you can make a case insensitive search by adding `case:insensitive` to the search terms, like this:
```
this are keywords case:insensitive
```
# Contributing
The contributing guidelines can be found [here](https://github.com/hacdias/filemanager/blob/master/CONTRIBUTING.md).
# Donate
Enjoying this project? You can [donate to its creator](https://henriquedias.com/donate/). He will appreciate.
![Preview](https://user-images.githubusercontent.com/5447088/28537288-39be4288-70a2-11e7-8ce9-0813d59f46b7.gif)
# filemanager
[![Build](https://img.shields.io/travis/hacdias/filemanager.svg?style=flat-square)](https://travis-ci.org/hacdias/filemanager)
[![Go Report Card](https://goreportcard.com/badge/github.com/hacdias/filemanager?style=flat-square)](https://goreportcard.com/report/hacdias/filemanager)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/hacdias/filemanager)
filemanager provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
# Table of contents
+ [Getting started](#getting-started)
+ [Features](#features)
- [Users](#users)
- [Search](#search)
+ [Contributing](#contributing)
+ [Donate](#donate)
# Getting started
You can find the Getting Started guide on the [documentation](https://henriquedias.com/filemanager/quick-start/).
# Features
Easy login system.
![Login Page](https://user-images.githubusercontent.com/5447088/28432382-975493dc-6d7f-11e7-9190-23f8037159dc.jpg)
Listings of your files, available in two styles: mosaic and list. You can delete, move, rename, upload and create new files, as well as directories. Single files can be downloaded directly, and multiple files as *.zip*, *.tar*, *.tar.gz*, *.tar.bz2* or *.tar.xz*.
![Mosaic Listing](https://user-images.githubusercontent.com/5447088/28432384-9771bb4c-6d7f-11e7-8564-3a9bd6a3ce3a.jpg)
File Manager editor is powered by [Codemirror](https://codemirror.net/) and if you're working with markdown files with metadata, both parts will be separated from each other so you can focus on the content.
![Markdown Editor](https://user-images.githubusercontent.com/5447088/28432383-9756fdac-6d7f-11e7-8e58-fec49470d15f.jpg)
On the settings page, a regular user can set its own custom CSS to personalize the experience and change its password. For admins, they can manage the permissions of each user, set commands which can be executed when certain events are triggered (such as before saving and after saving) and change plugin's settings.
![Settings](https://user-images.githubusercontent.com/5447088/28432385-9776ec66-6d7f-11e7-90a5-891bacd4d02f.jpg)
We also allow the users to search in the directories and execute commands if allowed.
## Users
We support multiple users and each user can have its own scope and custom stylesheet. The administrator is able to choose which permissions should be given to the users, as well as the commands they can execute. Each user also have a set of rules, in which he can be prevented or allowed to access some directories (regular expressions included!).
![Users](https://user-images.githubusercontent.com/5447088/28432386-977f388a-6d7f-11e7-9006-87d16f05f1f8.jpg)
## Search
FileManager allows you to search through your files and it has some options. By default, your search will be something like this:
```
this are keywords
```
If you search for that it will look at every file that contains "this", "are" or "keywords" on their name. If you want to search for an exact term, you should surround your search by double quotes:
```
"this is the name"
```
That will search for any file that contains "this is the name" on its name. It won't search for each separated term this time.
By default, every search will be case sensitive. Although, you can make a case insensitive search by adding `case:insensitive` to the search terms, like this:
```
this are keywords case:insensitive
```
# Contributing
The contributing guidelines can be found [here](https://github.com/hacdias/filemanager/blob/master/CONTRIBUTING.md).
# Donate
Enjoying this project? You can [donate to its creator](https://henriquedias.com/donate/). He will appreciate.

View File

@@ -1,31 +1,31 @@
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('./config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.assetsRoot, config.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
})
})
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('./config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.assetsRoot, config.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
})
})

View File

@@ -1,48 +1,48 @@
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

View File

@@ -1,26 +1,26 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '{{ .BaseURL }}/',
build: {
env: {
NODE_ENV: '"production"'
},
productionSourceMap: true,
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: {
NODE_ENV: '"development"'
},
produceSourceMap: true
}
}
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '{{ .BaseURL }}/',
build: {
env: {
NODE_ENV: '"production"'
},
productionSourceMap: true,
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: {
NODE_ENV: '"development"'
},
produceSourceMap: true
}
}

View File

@@ -1,17 +1,17 @@
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// In the production build, this file is replaced with an actual service worker
// file that will precache your site's local assets.
// See https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', () => {
self.clients.matchAll({ type: 'window' }).then(windowClients => {
for (let windowClient of windowClients) {
// Force open pages to refresh, so that they have a chance to load the
// fresh navigation response from the local dev server.
windowClient.navigate(windowClient.url);
}
});
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// In the production build, this file is replaced with an actual service worker
// file that will precache your site's local assets.
// See https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', () => {
self.clients.matchAll({ type: 'window' }).then(windowClients => {
for (let windowClient of windowClients) {
// Force open pages to refresh, so that they have a chance to load the
// fresh navigation response from the local dev server.
windowClient.navigate(windowClient.url);
}
});
});

View File

@@ -1,55 +1,55 @@
(function() {
'use strict';
// Check to make sure service workers are supported in the current browser,
// and that the current page is accessed from a secure origin. Using a
// service worker from an insecure origin will trigger JS console errors.
const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.addEventListener('load', function() {
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || isLocalhost)) {
navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
.then(function(registration) {
// updatefound is fired if service-worker.js changes.
registration.onupdatefound = function() {
// updatefound is also fired the very first time the SW is installed,
// and there's no need to prompt for a reload at that point.
// So check here to see if the page is already controlled,
// i.e. whether there's an existing service worker.
if (navigator.serviceWorker.controller) {
// The updatefound event implies that registration.installing is set
const installingWorker = registration.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
// At this point, the old content will have been purged and the
// fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in the page's interface.
break;
case 'redundant':
throw new Error('The installing ' +
'service worker became redundant.');
default:
// Ignore
}
};
}
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
}
});
})();
(function() {
'use strict';
// Check to make sure service workers are supported in the current browser,
// and that the current page is accessed from a secure origin. Using a
// service worker from an insecure origin will trigger JS console errors.
const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.addEventListener('load', function() {
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || isLocalhost)) {
navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
.then(function(registration) {
// updatefound is fired if service-worker.js changes.
registration.onupdatefound = function() {
// updatefound is also fired the very first time the SW is installed,
// and there's no need to prompt for a reload at that point.
// So check here to see if the page is already controlled,
// i.e. whether there's an existing service worker.
if (navigator.serviceWorker.controller) {
// The updatefound event implies that registration.installing is set
const installingWorker = registration.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
// At this point, the old content will have been purged and the
// fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in the page's interface.
break;
case 'redundant':
throw new Error('The installing ' +
'service worker became redundant.');
default:
// Ignore
}
};
}
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
}
});
})();

View File

@@ -1,70 +1,70 @@
var path = require('path')
var config = require('./config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = config.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
var path = require('path')
var config = require('./config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = config.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}

View File

@@ -1,12 +1,12 @@
var utils = require('./utils')
var config = require('./config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.produceSourceMap,
extract: isProduction
})
}
var utils = require('./utils')
var config = require('./config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.produceSourceMap,
extract: isProduction
})
}

View File

@@ -1,69 +1,69 @@
var path = require('path')
var utils = require('./utils')
var config = require('./config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './assets/src/main.js'
},
output: {
path: config.assetsRoot,
filename: '[name].js',
publicPath: config.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.(yml|yaml)$/,
loader: 'yml-loader'
},
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
// limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
var path = require('path')
var utils = require('./utils')
var config = require('./config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './assets/src/main.js'
},
output: {
path: config.assetsRoot,
filename: '[name].js',
publicPath: config.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.(yml|yaml)$/,
loader: 'yml-loader'
},
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
// limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

View File

@@ -1,81 +1,81 @@
var fs = require('fs')
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('./config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
var CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(baseWebpackConfig, {
watch: true,
module: {
rules: utils.styleLoaders({
sourceMap: config.dev.produceSourceMap,
extract: true
})
},
devtool: '#cheap-module-eval-source-map',
output: {
path: config.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.index,
template: 'assets/index.html',
inject: true,
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency',
serviceWorkerLoader: `<script>${fs.readFileSync(path.join(__dirname,
'./service-worker-dev.js'), 'utf-8')}</script>`
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.assetsSubDirectory,
ignore: ['.*']
},
{
from: path.resolve(__dirname, '../../node_modules/codemirror/mode/*/*'),
to: path.join(config.assetsSubDirectory, 'js/codemirror/mode/[name]/[name].js')
}
])
]
})
var fs = require('fs')
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('./config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
var CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(baseWebpackConfig, {
watch: true,
module: {
rules: utils.styleLoaders({
sourceMap: config.dev.produceSourceMap,
extract: true
})
},
devtool: '#cheap-module-eval-source-map',
output: {
path: config.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.index,
template: 'assets/index.html',
inject: true,
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency',
serviceWorkerLoader: `<script>${fs.readFileSync(path.join(__dirname,
'./service-worker-dev.js'), 'utf-8')}</script>`
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.assetsSubDirectory,
ignore: ['.*']
},
{
from: path.resolve(__dirname, '../../node_modules/codemirror/mode/*/*'),
to: path.join(config.assetsSubDirectory, 'js/codemirror/mode/[name]/[name].js')
}
])
]
})

View File

@@ -7,6 +7,8 @@
<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">
@@ -23,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

@@ -1,5 +1,5 @@
<svg id="content" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144">
<circle cx="72" cy="72" r="72" fill="#2979ff"/>
<circle cx="72" cy="72" r="48" fill="#40c4ff"/>
<circle cx="72" cy="72" r="24" fill="#fff"/>
<svg id="content" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144">
<circle cx="72" cy="72" r="72" fill="#2979ff"/>
<circle cx="72" cy="72" r="48" fill="#40c4ff"/>
<circle cx="72" cy="72" r="24" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -1,22 +1,22 @@
<template>
<select v-on:change="change" :value="selected">
<option value="en">{{ $t('languages.en') }}</option>
<option value="fr">{{ $t('languages.fr') }}</option>
<option value="pt">{{ $t('languages.pt') }}</option>
<option value="ja">{{ $t('languages.ja') }}</option>
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
</select>
</template>
<script>
export default {
name: 'languages',
props: [ 'selected' ],
methods: {
change (event) {
this.$emit('update:selected', event.target.value)
}
}
}
</script>
<template>
<select v-on:change="change" :value="selected">
<option value="en">{{ $t('languages.en') }}</option>
<option value="fr">{{ $t('languages.fr') }}</option>
<option value="pt">{{ $t('languages.pt') }}</option>
<option value="ja">{{ $t('languages.ja') }}</option>
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
</select>
</template>
<script>
export default {
name: 'languages',
props: [ 'selected' ],
methods: {
change (event) {
this.$emit('update:selected', event.target.value)
}
}
}
</script>

View File

@@ -1,265 +1,265 @@
<template>
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
<div id="input">
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
<i class="material-icons">arrow_back</i>
</button>
<i v-else class="material-icons">search</i>
<input type="text"
@keyup="keyup"
@keyup.enter="submit"
ref="input"
:autofocus="active"
v-model.trim="value"
:aria-label="$t('search.writeToSearch')"
:placeholder="placeholder">
</div>
<div id="result">
<div>
<template v-if="search.length === 0 && commands.length === 0">
<p>{{ text }}</p>
<template v-if="value.length === 0">
<div class="boxes">
<h3>{{ $t('search.types') }}</h3>
<div>
<div tabindex="0"
role="button"
@click="init('type:image')"
:aria-label="$t('search.images')">
<i class="material-icons">insert_photo</i>
<p>{{ $t('search.images') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:audio')"
:aria-label="$t('search.music')">
<i class="material-icons">volume_up</i>
<p>{{ $t('search.music') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:video')"
:aria-label="$t('search.video')">
<i class="material-icons">movie</i>
<p>{{ $t('search.video') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:pdf')"
:aria-label="$t('search.pdf')">
<i class="material-icons">picture_as_pdf</i>
<p>{{ $t('search.pdf') }}</p>
</div>
</div>
</div>
</template>
</template>
<ul v-else-if="search.length > 0">
<li v-for="s in search">
<router-link @click.native="close" :to="'./' + s.path">
<i v-if="s.dir" class="material-icons">folder</i>
<i v-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span>
</router-link>
</li>
</ul>
<pre v-else-if="commands.length > 0">
<template v-for="c in commands">{{ c }}</template>
</pre>
</div>
<p id="renew"><i class="material-icons spin">autorenew</i></p>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import * as api from '@/utils/api'
export default {
name: 'search',
data: function () {
return {
value: '',
active: false,
ongoing: false,
scrollable: null,
search: [],
commands: [],
reload: false
}
},
watch: {
show (val, old) {
this.active = (val === 'search')
// If the hover was search and now it's something else
// we should blur the input.
if (old === 'search' && val !== 'search') {
if (this.reload) {
this.$store.commit('setReload', true)
}
document.body.style.overflow = 'auto'
this.reset()
this.$refs.input.blur()
}
// If we are starting to show the search box, we should
// focus the input.
if (val === 'search') {
this.reload = false
this.$refs.input.focus()
document.body.style.overflow = 'hidden'
}
}
},
computed: {
...mapState(['user', 'show']),
// Placeholder value.
placeholder: function () {
if (this.user.allowCommands && this.user.commands.length > 0) {
return this.$t('search.searchOrCommand')
}
return this.$t('search.search')
},
// The text that is shown on the results' box while
// there is no search result or command output to show.
text: function () {
if (this.ongoing) {
return ''
}
if (this.value.length === 0) {
if (this.user.allowCommands && this.user.commands.length > 0) {
return `${this.$t('search.searchOrSupportedCommand')} ${this.user.commands.join(', ')}.`
}
this.$t('search.type')
}
if (!this.supported() || !this.user.allowCommands) {
return this.$t('search.pressToSearch')
} else {
return this.$t('search.pressToExecute')
}
}
},
mounted: function () {
// Gets the result div which will be scrollable.
this.scrollable = document.querySelector('#search #result')
// Adds the keydown event on window for the ESC key, so
// when it's pressed, it closes the search window.
window.addEventListener('keydown', (event) => {
if (event.keyCode === 27) {
this.$store.commit('closeHovers')
}
})
},
methods: {
// Sets the search to active.
open (event) {
this.$store.commit('showHover', 'search')
},
// Closes the search and prevents the event
// of propagating so it doesn't trigger the
// click event on #search.
close (event) {
event.stopPropagation()
event.preventDefault()
this.$store.commit('closeHovers')
},
// Checks if the current input is a supported command.
supported () {
let pieces = this.value.split(' ')
for (let i = 0; i < this.user.commands.length; i++) {
if (pieces[0] === this.user.commands[i]) {
return true
}
}
return false
},
// Initializes the search with a default value.
init (string) {
this.value = string + ' '
this.$refs.input.focus()
},
// Resets the search box value.
reset () {
this.value = ''
this.active = false
this.ongoing = false
this.search = []
this.commands = []
},
// When the user presses a key, if it is ESC
// then it will close the search box. Otherwise,
// it will set the search box to active and clean
// the search results, as well as commands'.
keyup (event) {
if (event.keyCode === 27) {
this.close(event)
return
}
this.search.length = 0
this.commands.length = 0
},
// Submits the input to the server and sets ongoing to true.
submit (event) {
this.ongoing = true
let path = this.$route.path
if (this.$store.state.req.kind !== 'listing') {
path = url.removeLastDir(path) + '/'
}
// In case of being a command.
if (this.supported() && this.user.allowCommands) {
api.command(path, this.value,
(event) => {
this.commands.push(event.data)
this.scrollable.scrollTop = this.scrollable.scrollHeight
},
(event) => {
this.reload = true
this.ongoing = false
this.scrollable.scrollTop = this.scrollable.scrollHeight
}
)
return
}
// In case of being a search.
api.search(path, this.value,
(event) => {
let response = JSON.parse(event.data)
if (response.path[0] === '/') {
response.path = response.path.substring(1)
}
this.search.push(response)
this.scrollable.scrollTop = this.scrollable.scrollHeight
},
(event) => {
this.ongoing = false
this.scrollable.scrollTop = this.scrollable.scrollHeight
}
)
}
}
}
</script>
<template>
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
<div id="input">
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
<i class="material-icons">arrow_back</i>
</button>
<i v-else class="material-icons">search</i>
<input type="text"
@keyup="keyup"
@keyup.enter="submit"
ref="input"
:autofocus="active"
v-model.trim="value"
:aria-label="$t('search.writeToSearch')"
:placeholder="placeholder">
</div>
<div id="result">
<div>
<template v-if="search.length === 0 && commands.length === 0">
<p>{{ text }}</p>
<template v-if="value.length === 0">
<div class="boxes">
<h3>{{ $t('search.types') }}</h3>
<div>
<div tabindex="0"
role="button"
@click="init('type:image')"
:aria-label="$t('search.images')">
<i class="material-icons">insert_photo</i>
<p>{{ $t('search.images') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:audio')"
:aria-label="$t('search.music')">
<i class="material-icons">volume_up</i>
<p>{{ $t('search.music') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:video')"
:aria-label="$t('search.video')">
<i class="material-icons">movie</i>
<p>{{ $t('search.video') }}</p>
</div>
<div tabindex="0"
role="button"
@click="init('type:pdf')"
:aria-label="$t('search.pdf')">
<i class="material-icons">picture_as_pdf</i>
<p>{{ $t('search.pdf') }}</p>
</div>
</div>
</div>
</template>
</template>
<ul v-else-if="search.length > 0">
<li v-for="s in search">
<router-link @click.native="close" :to="'./' + s.path">
<i v-if="s.dir" class="material-icons">folder</i>
<i v-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span>
</router-link>
</li>
</ul>
<pre v-else-if="commands.length > 0">
<template v-for="c in commands">{{ c }}</template>
</pre>
</div>
<p id="renew"><i class="material-icons spin">autorenew</i></p>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import * as api from '@/utils/api'
export default {
name: 'search',
data: function () {
return {
value: '',
active: false,
ongoing: false,
scrollable: null,
search: [],
commands: [],
reload: false
}
},
watch: {
show (val, old) {
this.active = (val === 'search')
// If the hover was search and now it's something else
// we should blur the input.
if (old === 'search' && val !== 'search') {
if (this.reload) {
this.$store.commit('setReload', true)
}
document.body.style.overflow = 'auto'
this.reset()
this.$refs.input.blur()
}
// If we are starting to show the search box, we should
// focus the input.
if (val === 'search') {
this.reload = false
this.$refs.input.focus()
document.body.style.overflow = 'hidden'
}
}
},
computed: {
...mapState(['user', 'show']),
// Placeholder value.
placeholder: function () {
if (this.user.allowCommands && this.user.commands.length > 0) {
return this.$t('search.searchOrCommand')
}
return this.$t('search.search')
},
// The text that is shown on the results' box while
// there is no search result or command output to show.
text: function () {
if (this.ongoing) {
return ''
}
if (this.value.length === 0) {
if (this.user.allowCommands && this.user.commands.length > 0) {
return `${this.$t('search.searchOrSupportedCommand')} ${this.user.commands.join(', ')}.`
}
this.$t('search.type')
}
if (!this.supported() || !this.user.allowCommands) {
return this.$t('search.pressToSearch')
} else {
return this.$t('search.pressToExecute')
}
}
},
mounted: function () {
// Gets the result div which will be scrollable.
this.scrollable = document.querySelector('#search #result')
// Adds the keydown event on window for the ESC key, so
// when it's pressed, it closes the search window.
window.addEventListener('keydown', (event) => {
if (event.keyCode === 27) {
this.$store.commit('closeHovers')
}
})
},
methods: {
// Sets the search to active.
open (event) {
this.$store.commit('showHover', 'search')
},
// Closes the search and prevents the event
// of propagating so it doesn't trigger the
// click event on #search.
close (event) {
event.stopPropagation()
event.preventDefault()
this.$store.commit('closeHovers')
},
// Checks if the current input is a supported command.
supported () {
let pieces = this.value.split(' ')
for (let i = 0; i < this.user.commands.length; i++) {
if (pieces[0] === this.user.commands[i]) {
return true
}
}
return false
},
// Initializes the search with a default value.
init (string) {
this.value = string + ' '
this.$refs.input.focus()
},
// Resets the search box value.
reset () {
this.value = ''
this.active = false
this.ongoing = false
this.search = []
this.commands = []
},
// When the user presses a key, if it is ESC
// then it will close the search box. Otherwise,
// it will set the search box to active and clean
// the search results, as well as commands'.
keyup (event) {
if (event.keyCode === 27) {
this.close(event)
return
}
this.search.length = 0
this.commands.length = 0
},
// Submits the input to the server and sets ongoing to true.
submit (event) {
this.ongoing = true
let path = this.$route.path
if (this.$store.state.req.kind !== 'listing') {
path = url.removeLastDir(path) + '/'
}
// In case of being a command.
if (this.supported() && this.user.allowCommands) {
api.command(path, this.value,
(event) => {
this.commands.push(event.data)
this.scrollable.scrollTop = this.scrollable.scrollHeight
},
(event) => {
this.reload = true
this.ongoing = false
this.scrollable.scrollTop = this.scrollable.scrollHeight
}
)
return
}
// In case of being a search.
api.search(path, this.value,
(event) => {
let response = JSON.parse(event.data)
if (response.path[0] === '/') {
response.path = response.path.substring(1)
}
this.search.push(response)
this.scrollable.scrollTop = this.scrollable.scrollHeight
},
(event) => {
this.ongoing = false
this.scrollable.scrollTop = this.scrollable.scrollHeight
}
)
}
}
}
</script>

View File

@@ -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,17 +1,17 @@
<template>
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
<i class="material-icons">content_copy</i>
<span>{{ $t('buttons.copyFile') }}</span>
</button>
</template>
<script>
export default {
name: 'copy-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'copy')
}
}
}
</script>
<template>
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
<i class="material-icons">content_copy</i>
<span>{{ $t('buttons.copyFile') }}</span>
</button>
</template>
<script>
export default {
name: 'copy-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'copy')
}
}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
<i class="material-icons">delete</i>
<span>{{ $t('buttons.delete') }}</span>
</button>
</template>
<script>
export default {
name: 'delete-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'delete')
}
}
}
</script>
<template>
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
<i class="material-icons">delete</i>
<span>{{ $t('buttons.delete') }}</span>
</button>
</template>
<script>
export default {
name: 'delete-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'delete')
}
}
}
</script>

View File

@@ -1,39 +1,39 @@
<template>
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
<i class="material-icons">file_download</i>
<span>{{ $t('buttons.download') }}</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import * as api from '@/utils/api'
export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['selectedCount'])
},
methods: {
download: function (event) {
// If we are not on a listing, download the current file.
if (this.req.kind !== 'listing') {
api.download(null, this.$route.path)
return
}
// If we are on a listing and there is one element selected,
// download it.
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url)
return
}
// Otherwise show the prompt to choose the formt of the download.
this.$store.commit('showHover', 'download')
}
}
}
</script>
<template>
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
<i class="material-icons">file_download</i>
<span>{{ $t('buttons.download') }}</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import * as api from '@/utils/api'
export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['selectedCount'])
},
methods: {
download: function (event) {
// If we are not on a listing, download the current file.
if (this.req.kind !== 'listing') {
api.download(null, this.$route.path)
return
}
// If we are on a listing and there is one element selected,
// download it.
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url)
return
}
// Otherwise show the prompt to choose the formt of the download.
this.$store.commit('showHover', 'download')
}
}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
<i class="material-icons">info</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'info-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'info')
}
}
}
</script>
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
<i class="material-icons">info</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'info-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'info')
}
}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
<i class="material-icons">forward</i>
<span>{{ $t('buttons.moveFile') }}</span>
</button>
</template>
<script>
export default {
name: 'move-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'move')
}
}
}
</script>
<template>
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
<i class="material-icons">forward</i>
<span>{{ $t('buttons.moveFile') }}</span>
</button>
</template>
<script>
export default {
name: 'move-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'move')
}
}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
<i class="material-icons">mode_edit</i>
<span>{{ $t('buttons.rename') }}</span>
</button>
</template>
<script>
export default {
name: 'rename-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'rename')
}
}
}
</script>
<template>
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
<i class="material-icons">mode_edit</i>
<span>{{ $t('buttons.rename') }}</span>
</button>
</template>
<script>
export default {
name: 'rename-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'rename')
}
}
}
</script>

View File

@@ -1,21 +1,21 @@
<template>
<button @click="show"
:aria-label="$t('buttons.schedule')"
:title="$t('buttons.schedule')"
id="schedule-button"
class="action">
<i class="material-icons">alarm</i>
<span>{{ $t('buttons.schedule') }}</span>
</button>
</template>
<script>
export default {
name: 'schedule-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'schedule')
}
}
}
</script>
<template>
<button @click="show"
:aria-label="$t('buttons.schedule')"
:title="$t('buttons.schedule')"
id="schedule-button"
class="action">
<i class="material-icons">alarm</i>
<span>{{ $t('buttons.schedule') }}</span>
</button>
</template>
<script>
export default {
name: 'schedule-button',
methods: {
show: function (event) {
this.$store.commit('showHover', 'schedule')
}
}
}
</script>

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

@@ -1,17 +1,17 @@
<template>
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
<i class="material-icons">file_upload</i>
<span>{{ $t('buttons.upload') }}</span>
</button>
</template>
<script>
export default {
name: 'upload-button',
methods: {
upload: function (event) {
document.getElementById('upload-input').click()
}
}
}
</script>
<template>
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
<i class="material-icons">file_upload</i>
<span>{{ $t('buttons.upload') }}</span>
</button>
</template>
<script>
export default {
name: 'upload-button',
methods: {
upload: function (event) {
document.getElementById('upload-input').click()
}
}
}
</script>

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,36 @@ 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 = 0
let la = 0
if (this.index > this.selected[0]) {
fi = this.selected[0] + 1
la = this.index
} else {
fi = this.index
la = this.selected[0] - 1
}
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)

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,78 +1,84 @@
<template>
<div class="prompt">
<h3>{{ $t('prompts.rename') }}</h3>
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
<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"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import * as api from '@/utils/api'
export default {
name: 'rename',
data: function () {
return {
name: ''
}
},
computed: mapState(['req', 'selected', 'selectedCount']),
methods: {
cancel: function (event) {
this.$store.commit('closeHovers')
},
oldName: function () {
// Get the current name of the file we are editing.
if (this.req.kind !== 'listing') {
return this.req.name
}
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return
}
return this.req.items[this.selected[0]].name
},
submit: function (event) {
let oldLink = ''
let newLink = ''
if (this.req.kind !== 'listing') {
oldLink = this.req.url
} else {
oldLink = this.req.items[this.selected[0]].url
}
this.name = encodeURIComponent(this.name)
newLink = url.removeLastDir(oldLink) + '/' + this.name
api.move([{ from: oldLink, to: newLink }])
.then(() => {
if (this.req.kind !== 'listing') {
this.$router.push({ path: newLink })
return
}
this.$store.commit('setReload', true)
}).catch(error => {
this.$store.commit('showError', error)
})
this.$store.commit('closeHovers')
}
}
}
</script>
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.rename') }}</h2>
</div>
<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>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import * as api from '@/utils/api'
export default {
name: 'rename',
data: function () {
return {
name: ''
}
},
computed: mapState(['req', 'selected', 'selectedCount']),
methods: {
cancel: function (event) {
this.$store.commit('closeHovers')
},
oldName: function () {
// Get the current name of the file we are editing.
if (this.req.kind !== 'listing') {
return this.req.name
}
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return
}
return this.req.items[this.selected[0]].name
},
submit: function (event) {
let oldLink = ''
let newLink = ''
if (this.req.kind !== 'listing') {
oldLink = this.req.url
} else {
oldLink = this.req.items[this.selected[0]].url
}
this.name = encodeURIComponent(this.name)
newLink = url.removeLastDir(oldLink) + '/' + this.name
api.move([{ from: oldLink, to: newLink }])
.then(() => {
if (this.req.kind !== 'listing') {
this.$router.push({ path: newLink })
return
}
this.$store.commit('setReload', true)
}).catch(error => {
this.$showError(error)
})
this.$store.commit('closeHovers')
}
}
}
</script>

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

@@ -1,184 +1,184 @@
@import "~codemirror/lib/codemirror.css";
@import "~codemirror/theme/ttcn.css";
#editor {
max-width: 800px;
margin: 0 auto;
}
#editor .CodeMirror {
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
margin: 2em 0;
border-radius: .5em;
}
#editor h2 {
color: rgba(0, 0, 0, 0.3);
font-weight: 500;
}
.CodeMirror {
height: auto;
}
.markdown .CodeMirror {
padding: .75em;
}
.cm-s-markdown .CodeMirror-gutter {
border-right: 1px solid #eff3f5;
padding-right: 5px;
margin-right: 15px;
min-width: 2.5em;
padding-bottom: 30px;
}
.cm-s-markdown .CodeMirror-cursor {
border-right: 2px solid #667880;
}
.cm-s-markdown .CodeMirror-lines {
margin: 0;
}
.cm-s-markdown {
color: #3D494E;
}
.cm-s-markdown span.cm-header {
color: #3D494E;
font-weight: bold;
}
.cm-s-markdown span.cm-variable-2 {
color: #3D494E;
}
.cm-s-markdown span.cm-meta {
color: #516066;
}
.cm-s-markdown span.cm-hr {
color: #516066;
}
.cm-s-markdown span.cm-comment {
color: #868f93;
}
.cm-s-markdown span.cm-qualifier {
color: #868f93;
}
.cm-s-markdown span.cm-number {
color: #197987;
}
.cm-s-markdown span.cm-variable {
color: #197987;
}
.cm-s-markdown span.cm-builtin {
color: #197987;
}
.cm-s-markdown span.cm-link {
color: #197987;
text-decoration: underline;
}
.cm-s-markdown span.cm-tag {
color: #197987;
}
.cm-s-markdown span.cm-string {
color: #48abb9;
}
.cm-s-markdown span.cm-string-2 {
color: #48abb9;
}
.cm-s-markdown span.cm-quote {
color: #48abb9;
}
.cm-s-markdown span.cm-atom {
color: #48abb9;
}
.cm-s-markdown span.cm-property {
color: #82a367;
}
.cm-s-markdown span.cm-operator {
color: #82a367;
}
.cm-s-markdown span.cm-variable-3 {
color: #82a367;
}
.cm-s-markdown span.cm-attribute {
color: #90bb74;
}
.cm-s-markdown span.cm-def {
color: #90bb74;
}
.cm-s-markdown span.cm-keyword {
color: #ec6c45;
}
.cm-s-markdown span.cm-bracket {
color: #ec6c45;
}
.cm-s-markdown span.cm-error {
color: #e45346;
}
.cm-s-markdown span.cm-em {
font-style: italic;
}
.cm-s-markdown span.cm-strong {
font-weight: bold;
}
.cm-s-markdown .cm-header-1 {
font-size: 200%;
line-height: 200%;
}
.cm-s-markdown .cm-header-2 {
font-size: 160%;
line-height: 160%;
}
.cm-s-markdown .cm-header-3 {
font-size: 125%;
line-height: 125%;
}
.cm-s-markdown .cm-header-4 {
font-size: 110%;
line-height: 110%;
}
.cm-s-markdown .cm-comment {
background: rgba(0, 0, 0, .05);
border-radius: 2px;
}
.cm-s-markdown .cm-link {
color: #7f8c8d;
}
.cm-s-markdown .cm-url {
color: #aab2b3;
}
.cm-s-markdown .cm-strikethrough {
text-decoration: line-through;
}
@import "~codemirror/lib/codemirror.css";
@import "~codemirror/theme/ttcn.css";
#editor {
max-width: 800px;
margin: 0 auto;
}
#editor .CodeMirror {
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
margin: 2em 0;
border-radius: .5em;
}
#editor h2 {
color: rgba(0, 0, 0, 0.3);
font-weight: 500;
}
.CodeMirror {
height: auto;
}
.markdown .CodeMirror {
padding: .75em;
}
.cm-s-markdown .CodeMirror-gutter {
border-right: 1px solid #eff3f5;
padding-right: 5px;
margin-right: 15px;
min-width: 2.5em;
padding-bottom: 30px;
}
.cm-s-markdown .CodeMirror-cursor {
border-right: 2px solid #667880;
}
.cm-s-markdown .CodeMirror-lines {
margin: 0;
}
.cm-s-markdown {
color: #3D494E;
}
.cm-s-markdown span.cm-header {
color: #3D494E;
font-weight: bold;
}
.cm-s-markdown span.cm-variable-2 {
color: #3D494E;
}
.cm-s-markdown span.cm-meta {
color: #516066;
}
.cm-s-markdown span.cm-hr {
color: #516066;
}
.cm-s-markdown span.cm-comment {
color: #868f93;
}
.cm-s-markdown span.cm-qualifier {
color: #868f93;
}
.cm-s-markdown span.cm-number {
color: #197987;
}
.cm-s-markdown span.cm-variable {
color: #197987;
}
.cm-s-markdown span.cm-builtin {
color: #197987;
}
.cm-s-markdown span.cm-link {
color: #197987;
text-decoration: underline;
}
.cm-s-markdown span.cm-tag {
color: #197987;
}
.cm-s-markdown span.cm-string {
color: #48abb9;
}
.cm-s-markdown span.cm-string-2 {
color: #48abb9;
}
.cm-s-markdown span.cm-quote {
color: #48abb9;
}
.cm-s-markdown span.cm-atom {
color: #48abb9;
}
.cm-s-markdown span.cm-property {
color: #82a367;
}
.cm-s-markdown span.cm-operator {
color: #82a367;
}
.cm-s-markdown span.cm-variable-3 {
color: #82a367;
}
.cm-s-markdown span.cm-attribute {
color: #90bb74;
}
.cm-s-markdown span.cm-def {
color: #90bb74;
}
.cm-s-markdown span.cm-keyword {
color: #ec6c45;
}
.cm-s-markdown span.cm-bracket {
color: #ec6c45;
}
.cm-s-markdown span.cm-error {
color: #e45346;
}
.cm-s-markdown span.cm-em {
font-style: italic;
}
.cm-s-markdown span.cm-strong {
font-weight: bold;
}
.cm-s-markdown .cm-header-1 {
font-size: 200%;
line-height: 200%;
}
.cm-s-markdown .cm-header-2 {
font-size: 160%;
line-height: 160%;
}
.cm-s-markdown .cm-header-3 {
font-size: 125%;
line-height: 125%;
}
.cm-s-markdown .cm-header-4 {
font-size: 110%;
line-height: 110%;
}
.cm-s-markdown .cm-comment {
background: rgba(0, 0, 0, .05);
border-radius: 2px;
}
.cm-s-markdown .cm-link {
color: #7f8c8d;
}
.cm-s-markdown .cm-url {
color: #aab2b3;
}
.cm-s-markdown .cm-strikethrough {
text-decoration: line-through;
}

View File

@@ -1,137 +1,137 @@
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../assets/fonts/material/icons.woff2) format('woff2');
}
.prompt .file-list ul li:before,
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'liga';
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../assets/fonts/material/icons.woff2) format('woff2');
}
.prompt .file-list ul li:before,
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'liga';
}

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,113 +1,113 @@
@media (max-width: 1024px) {
nav {
width: 10em
}
}
@media (max-width: 1024px) {
#listing.list .item.header,
main {
width: calc(100% - 13em)
}
}
@media (max-width: 736px) {
#more {
display: inherit
}
header .overlay {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.1);
}
#dropdown {
position: fixed;
top: 1em;
right: 1em;
display: block;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transform: scale(0);
transition: .1s ease-in-out transform;
transform-origin: top right;
z-index: 99999;
}
#dropdown > div {
display: block;
}
#dropdown.active {
transform: scale(1);
}
#dropdown .action {
display: flex;
align-items: center;
border-radius: 0;
width: 100%;
}
#dropdown .action span:not(.counter) {
display: inline-block;
padding: .4em;
}
#dropdown .counter {
left: 2.25em;
}
#file-selection {
position: fixed;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
background: #fff;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
width: 95%;
max-width: 20em;
}
#file-selection .action {
border-radius: 50%;
width: auto;
}
#file-selection > span {
display: inline-block;
margin-left: 1em;
color: #6f6f6f;
margin-right: auto;
}
nav {
top: 0;
z-index: 99999;
background: #fff;
height: 100%;
width: 16em;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transition: .1s ease left;
left: -17em;
}
nav.active {
left: 0;
}
header .search-button,
header>div:first-child>.action {
display: inherit;
}
header img {
display: none;
}
#listing {
margin-bottom: 5em;
}
#listing.list .item.header,
main {
width: calc(100% - 2em);
}
main {
margin: 0 1em;
width: calc(100% - 2em);
}
#search {
display: none;
}
#search.active {
display: block;
}
}
@media (max-width: 1024px) {
nav {
width: 10em
}
}
@media (max-width: 1024px) {
#listing.list .item.header,
main {
width: calc(100% - 13em)
}
}
@media (max-width: 736px) {
#more {
display: inherit
}
header .overlay {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.1);
}
#dropdown {
position: fixed;
top: 1em;
right: 1em;
display: block;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transform: scale(0);
transition: .1s ease-in-out transform;
transform-origin: top right;
z-index: 99999;
}
#dropdown > div {
display: block;
}
#dropdown.active {
transform: scale(1);
}
#dropdown .action {
display: flex;
align-items: center;
border-radius: 0;
width: 100%;
}
#dropdown .action span:not(.counter) {
display: inline-block;
padding: .4em;
}
#dropdown .counter {
left: 2.25em;
}
#file-selection {
position: fixed;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
background: #fff;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
width: 95%;
max-width: 20em;
}
#file-selection .action {
border-radius: 50%;
width: auto;
}
#file-selection > span {
display: inline-block;
margin-left: 1em;
color: #6f6f6f;
margin-right: auto;
}
nav {
top: 0;
z-index: 99999;
background: #fff;
height: 100%;
width: 16em;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transition: .1s ease left;
left: -17em;
}
nav.active {
left: 0;
}
header .search-button,
header>div:first-child>.action {
display: inherit;
}
header img {
display: none;
}
#listing {
margin-bottom: 5em;
}
#listing.list .item.header,
main {
width: calc(100% - 2em);
}
main {
margin: 0 1em;
width: calc(100% - 2em);
}
#search {
display: none;
}
#search.active {
display: block;
}
}

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

@@ -1,198 +1,200 @@
permanent: 永久
buttons:
cancel: キャンセル
close: 閉じる
copy: コピー
copyFile: ファイルをコピー
copyToClipboard: クリップボードにコピー
create: 作成
delete: 削除
download: ダウンロード
info: 情報
more: More
move: 移動
moveFile: ファイルを移動
new: 新規
next:
ok: OK
replace: 置き換える
previous:
rename: 名前を変更
reportIssue: 問題を報告
save: 保存
search: 検索
select: 選択
share: シェア
publish: 発表
selectMultiple: 複数選択
schedule: スケジュール
switchView: 表示を切り替わる
toggleSidebar: サイドバーを表示する
update: 更新
upload: アップロード
permalink: 固定リンク
errors:
forbidden: アクセスが拒否されました
internal: 内部エラーが発生しました。
notFound: リソースが見つからなりませんでした。
files:
folders: フォルダ
files: ファイル
body: 本文
clear: クリアー
closePreview: プレビューを閉じる
home: ホーム
lastModified: 最終変更
loading: ローディング...
lonely: ここには何もない...
metadata: メタデータ
multipleSelectionEnabled: 複数選択有効
name: 名前
size: サイズ
sortByName: 名前によるソート
sortBySize: サイズによるソート
sortByLastModified: 最終変更日付によるソート
help:
click: ファイルやディレクトリを選択
ctrl:
click: 複数のファイルやディレクトリを選択
f: 検索を有効にする
s: ファイルを保存またはカレントディレクトリをダウンロード
del: 選択した項目を削除
doubleClick: ファイルディレクトリをオープン
esc: 選択をクリアーまたはプロンプトを閉じる
f1: このヘルプを表示
f2: ファイルの名前を変更
help: ヘルプ
login:
password: パスワード
submit: ログイン
username: ユーザ名
wrongCredentials: ユーザ名またはパスワードが間違っています。
prompts:
copy: コピー
copyMessage: コピーの目標ディレクトリを選択してください:
currentlyNavigating: 現在閲覧しているディレクトリ:
deleteMessageMultiple: '{count} つのファイルを本当に削除してよろしいですか。'
deleteMessageSingle: このファイル/フォルダを本当に削除してよろしいですか。
deleteTitle: ファイルを削除
displayName: 名前:
download: ファイルをダウンロード
downloadMessage: 圧縮形式を選択してください。
error: あるエラーが発生しました。
fileInfo: ファイル情報
filesSelected: '{count} つのファイルは選択されました。'
lastModified: 最終変更
move: 移動
moveMessage: 移動の目標ディレクトリを選択してください:
newDir: 新しいディレクトリを作成
newDirMessage: 新しいディレクトリの名前を入力してください
newFile: 新しいファイルを作成
newFileMessage: 新しいファイルの名前を入力してください。
numberDirs: ディレクトリ個数
numberFiles: ファイル個数
replace: 置き換える
replaceMessage: >
アップロードするファイルの中でかち合う名前が一つあります。
既存のファイルを置き換えりませんか。
rename: 名前を変更
renameMessage: 名前を変更しようファイルは:
show: 表示
size: サイズ
schedule: スケジュール
scheduleMessage: このポストの発表日付をスケジュールしてください。
newArchetype: ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。
settings:
admin: 管理者
administrator: 管理者
allowCommands: コマンドの実行
allowEdit: ファイルやディレクトリの編集、名前変更と削除
allowNew: ファイルとディレクトリの作成
allowPublish: ポストとぺーじの発表
avoidChanges: "(変更を避けるために空白にしてください)"
changePassword: パスワードを変更
commands: コマンド
commandsHelp: "\
ここで、名前付きイベントに実行するコマンドを設定することができます。\
一行にコマンド一つを入力してください。\
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
環境変数 file はファイルのパスに割り当てられます。"
commandsUpdated: コマンドは更新されました!
customStylesheet: カスタムスタイルシ ート
examples:
globalSettings: グローバル設定
language: 言語
newPassword: 新しいパスワード
newPasswordConfirm: 新しいパスワードを確認します
newUser: 新しいユーザー
password: パスワード
passwordUpdated: パスワードは更新されました!
permissions: 権限
permissionsHelp: "\
あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\
\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。\
ユーザーの管理は管理者の権限として保留されました。"
profileSettings: プロファイル設定
ruleExample1: "\
各フォルダに名前はドットで始まるファイル(例えば、.git、.gitignore\
へのアクセスを制限します。"
ruleExample2: 範囲のルートパスに名前は Caddyfile のファイルへのアクセスを制限します。
rules: 規則
rulesHelp1: "\
ここに、あなたはこのユーザーの許可または拒否規則を設定できます。\
ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。\
正規表現(regex)のサポートと範囲に相対のパスが提供されています。"
rulesHelp2: "\
一行に規則一つを入力してください、\
その間に規則はキーワード {0} や {1} で始める必要があります。\
そして正規表現を使う場合、{2} と入力し、表現やパスを入力してください。"
scope: 範囲
settingsUpdated: 設定は更新されました!
user: ユーザー
userCommands: ユーザーのコマンド
userCommandsHelp: "\
空白区切りの有効のコマンドのリストを指定してください。\
例:"
userCreated: ユーザーは作成されました!
userDeleted: ユーザーは削除されました!
userManagement: ユーザー管理
username: ユーザー
users: ユーザー
userUpdated: ユーザーは更新されました!
sidebar:
help: ヘルプ
logout: ログアウト
myFiles: 私のファイル
newFile: 新しいファイルを作成
newFolder: 新しいフォルダを作成
servedWith: サービス提供者
settings: 設定
siteSettings: サイト設定
hugoNew: Hugo New
preview: プレビュー
search:
images: 画像
music: 音楽
pdf: PDF
pressToExecute: Enter を押して実行します。
pressToSearch: Enter を押して検索します。
search: 検索...
searchOrCommand: コマンドを検索または実行します。
searchOrSupportedCommand: サポートしているコマンドを検索または実行します:
type: キーワードを入力し、Enter を押して検索します。
types: 種類
video: ビデオ
writeToSearch: ここにキーワードを入力してください
languages:
en: English
fr: Français
pt: Português
ja: 日本語
zhCN: 中文 (简体)
zhTW: 中文 (繁體)
time:
unit: 時間単位
seconds:
minutes:
hours: 時間
days:
permanent: 永久
buttons:
cancel: キャンセル
close: 閉じる
copy: コピー
copyFile: ファイルをコピー
copyToClipboard: クリップボードにコピー
create: 作成
delete: 削除
download: ダウンロード
info: 情報
more: More
move: 移動
moveFile: ファイルを移動
new: 新規
next:
ok: OK
replace: 置き換える
previous:
rename: 名前を変更
reportIssue: 問題を報告
save: 保存
search: 検索
select: 選択
share: シェア
publish: 発表
selectMultiple: 複数選択
schedule: スケジュール
switchView: 表示を切り替わる
toggleSidebar: サイドバーを表示する
update: 更新
upload: アップロード
permalink: 固定リンク
success:
linkCopied: リンクがコピーされました
errors:
forbidden: アクセスが拒否されました。
internal: 内部エラーが発生しました。
notFound: リソースが見つからなりませんでした。
files:
folders: フォルダ
files: ファイル
body: 本文
clear: クリアー
closePreview: プレビューを閉じる
home: ホーム
lastModified: 最終変更
loading: ローディング...
lonely: ここには何もない...
metadata: メタデータ
multipleSelectionEnabled: 複数選択有効
name: 名前
size: サイズ
sortByName: 名前によるソート
sortBySize: サイズによるソート
sortByLastModified: 最終変更日付によるソート
help:
click: ファイルやディレクトリを選択
ctrl:
click: 複数のファイルディレクトリを選択
f: 検索を有効にする
s: ファイルを保存またはカレントディレクトリをダウンロード
del: 選択した項目を削除
doubleClick: ファイルやディレクトリをオープン
esc: 選択をクリアーまたはプロンプトを閉じる
f1: このヘルプを表示
f2: ファイルの名前を変更
help: ヘルプ
login:
password: パスワード
submit: ログイン
username: ユーザ名
wrongCredentials: ユーザ名またはパスワードが間違っています。
prompts:
copy: コピー
copyMessage: コピーの目標ディレクトリを選択してください:
currentlyNavigating: 現在閲覧しているディレクトリ:
deleteMessageMultiple: '{count} つのファイルを本当に削除してよろしいですか。'
deleteMessageSingle: このファイル/フォルダを本当に削除してよろしいですか。
deleteTitle: ファイルを削除
displayName: 名前:
download: ファイルをダウンロード
downloadMessage: 圧縮形式を選択してください。
error: あるエラーが発生しました。
fileInfo: ファイル情報
filesSelected: '{count} つのファイルは選択されました。'
lastModified: 最終変更
move: 移動
moveMessage: 移動の目標ディレクトリを選択してください
newDir: 新しいディレクトリを作成
newDirMessage: 新しいディレクトリの名前を入力してください。
newFile: 新しいファイルを作成
newFileMessage: 新しいファイルの名前を入力してください。
numberDirs: ディレクトリ個数
numberFiles: ファイル個数
replace: 置き換える
replaceMessage: >
アップロードするファイルの中でかち合う名前が一つあります。
既存のファイルを置き換えりませんか。
rename: 名前を変更
renameMessage: 名前を変更しようファイルは:
show: 表示
size: サイズ
schedule: スケジュール
scheduleMessage: このポストの発表日付をスケジュールしてください。
newArchetype: ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。
settings:
admin: 管理者
administrator: 管理者
allowCommands: コマンドの実行
allowEdit: ファイルやディレクトリの編集、名前変更と削除
allowNew: ファイルとディレクトリの作成
allowPublish: ポストとぺーじの発表
avoidChanges: "(変更を避けるために空白にしてください)"
changePassword: パスワードを変更
commands: コマンド
commandsHelp: "\
ここで、名前付きイベントに実行するコマンドを設定することができます。\
一行にコマンド一つを入力してください。\
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
環境変数 FILE はファイルのパスに割り当てられます。"
commandsUpdated: コマンドは更新されました!
customStylesheet: カスタムスタイルシ ート
examples:
globalSettings: グローバル設定
language: 言語
lockPassword: 新しいパスワードを変更に禁止
newPassword: 新しいパスワード
newPasswordConfirm: 新しいパスワードを確認します
newUser: 新しいユーザー
password: パスワード
passwordUpdated: パスワードは更新されました!
permissions: 権限
permissionsHelp: "\
あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\
\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。\
ユーザーの管理は管理者の権限として保留されました。"
profileSettings: プロファイル設定
ruleExample1: "\
各フォルダに名前はドットで始まるファイル(例えば、.git、.gitignore\
へのアクセスを制限します。"
ruleExample2: 範囲のルートパスに名前は Caddyfile のファイルへのアクセスを制限します。
rules: 規則
rulesHelp1: "\
ここに、あなたはこのユーザーの許可または拒否規則を設定できます。\
ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。\
正規表現(regex)のサポートと範囲に相対のパスが提供されています。"
rulesHelp2: "\
一行に規則一つを入力してください、\
その間に規則はキーワード {0} や {1} で始める必要があります。\
そして正規表現を使う場合、{2} と入力し、表現やパスを入力してください。"
scope: 範囲
settingsUpdated: 設定は更新されました!
user: ユーザー
userCommands: ユーザーのコマンド
userCommandsHelp: "\
空白区切りの有効のコマンドのリストを指定してください。\
例:"
userCreated: ユーザーは作成されました!
userDeleted: ユーザーは削除されました!
userManagement: ユーザー管理
username: ユーザー名
users: ユーザー
userUpdated: ユーザーは更新されました!
sidebar:
help: ヘルプ
logout: ログアウト
myFiles: 私のファイル
newFile: 新しいファイルを作成
newFolder: 新しいフォルダを作成
settings: 設定
siteSettings: サイト設定
hugoNew: Hugo New
preview: プレビュー
search:
images: 画像
music: 音楽
pdf: PDF
pressToExecute: Enter を押して実行します。
pressToSearch: Enter を押して検索します。
search: 検索...
searchOrCommand: コマンドを検索または実行します。
searchOrSupportedCommand: サポートしているコマンドを検索または実行します:
type: キーワードを入力し、Enter を押して検索します。
types: 種類
video: ビデオ
writeToSearch: ここにキーワードを入力してください
languages:
en: English
fr: Français
pt: Português
ja: 日本語
zhCN: 中文 (简体)
zhTW: 中文 (繁體)
time:
unit: 時間単位
seconds:
minutes:
hours: 時間
days:

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

@@ -1,196 +1,198 @@
permanent: 永久
buttons:
cancel: 取消
close: 关闭
copy: 复制
copyFile: 复制文件
copyToClipboard: 复制到剪贴板
create: 创建
delete: 删除
download: 下载
info: 信息
more: 更多
move: 移动
moveFile: 移动文件
new:
next: 下一个
ok: 确定
replace: 替换
previous: 上一个
rename: 重命名
reportIssue: 报告问题
save: 保存
search: 搜索
select: 选择
share: 分享
publish: 发布
selectMultiple: 选择多个
schedule: 计划
switchView: 切换显示方式
toggleSidebar: 切换侧边栏
update: 更新
upload: 上传
permalink: 获取永久链接
errors:
forbidden: 你被禁止访问。
internal: 内部出现麻烦了。
notFound: 找不到文件
files:
folders: 文件
files: 文件
body: Body
clear: 清空
closePreview: 关闭预览
home: 主页
lastModified: 最后修改
loading: 加载中...
lonely: 这里没有任何文件...
metadata: 元数据
multipleSelectionEnabled: 多选模式已开启
name: 名称
size: 大小
sortByName: 名称排序
sortBySize: 大小排序
sortByLastModified: 最后修改时间排序
help:
click: 选择文件或目录
ctrl:
click: 选择多个文件或目录
f: 打开搜索框
s: 保存文件或下载当前文件夹
del: 删除所选的文件/文件夹
doubleClick: 打开文件/文件夹
esc: 清除已选项或关闭提示信息
f1: 显示该帮助信息
f2: 重命名文件/文件夹
help: 帮助
login:
password: 密码
submit: 登录
username: 用户名
wrongCredentials: 用户名或密码错误
prompts:
copy: 复制
copyMessage: 请选择欲复制至的目录:
currentlyNavigating: 当前目录:
deleteMessageMultiple: 你确定要删除这 {count} 个文件吗?
deleteMessageSingle: 你确定要删除这个文件/文件夹吗?
deleteTitle: 删除文件
displayName: 名称:
download: 下载文件
downloadMessage: 请选择要下载的压缩格式。
error: 出了一点问题...
fileInfo: 文件信息
filesSelected: 已选择 {count} 个文件。
lastModified: 最后修改
move: 移动
moveMessage: 请选择欲移动至的目录:
newDir: 新建目录
newDirMessage: 输入新目录的名称。
newFile: 新建文件
newFileMessage: 请输入新文件的名称。
numberDirs: 目录数
numberFiles: 文件数
replace: 替换
replaceMessage: "\
您尝试上传的文件中有一个与现有文件的名称存在冲突。\
是否替换现有的同名文件?"
rename: 重命名
renameMessage: 请输入新名称,旧名称为:
show: 揭示
size: 大小
schedule: 计划
scheduleMessage: 请选择发布这篇帖子的日期。
newArchetype: 创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。
settings:
admin: 管理员
administrator: 管理员
allowCommands: 执行命令(Linux 代码)
allowEdit: 编辑、重命名或删除文件/目录
allowNew: 创建新文件和目录
allowPublish: 发布新的帖子与页面
avoidChanges: '(留空以避免更改)'
changePassword: 更改密码
commands: 命令(linux 代码)
commandsHelp: "\
在这里,您可以设置在指定事件下执行的命令,一行一条。\
若事件与文件相关,如“在保存文件前”,\
则文件的路径会被赋值给环境变量 \"file\"。"
commandsUpdated: 命令已更新!
customStylesheet: 自定义样式表
examples: 例子
globalSettings: 全局设置
language: 语言
newPassword: 您的新密码
newPasswordConfirm: 重输一遍新密码
newUser: 新建用户
password: 密码
passwordUpdated: 密码已更新!
permissions: 权限
permissionsHelp: "\
您可以将该用户设置为管理员,也可以单独选择各项权限。\
如果选择了“管理员”,则其他的选项会被自动勾上,\
同时该用户可以管理其他用户。"
profileSettings: 配置文件设置
ruleExample1: "\
阻止用户访问所有文件夹下任何以 . 开头的文件\
(隐藏文件, 例如: .git, .gitignore)。"
ruleExample2: 阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。
rules: 规则
rulesHelp1: "\
您可以为该用户制定一组黑名单或白名单式的规则,\
被屏蔽的文件将不会显示在列表中,用户也无权限访问,\
支持相对于目录范围的路径。"
rulesHelp2: "\
每行一条规则,且必须以关键词 {0} 或 {1} 开头。\
如要使用正则表达式,请在加上 {2} 之后再附上表达式或路径。"
scope: 目录范围
settingsUpdated: 设置已更新!
user: 用户
userCommands: 用户命令(Linux 代码)
userCommandsHelp: "\
指定该用户可以执行的命令(Linux 代码),用空格分隔。\
例如:"
userCreated: 用户已创建!
userDeleted: 用户已删除!
userManagement: 用户管理
username: 用户
users: 用户
userUpdated: 用户已更新!
sidebar:
help: 帮助
logout: 登出
myFiles: 我的文件
newFile: 新建文件
newFolder: 新建文件夹
servedWith: '服务提供者:'
settings: 设置
siteSettings: 网站设置
hugoNew: Hugo New
preview: 预览
search:
images: 图像
music: 音乐
pdf: PDF
pressToExecute: 按回车键执行。
pressToSearch: 按回车键搜索。
search: 搜索...
searchOrCommand: 搜索或者执行命令(Linux 代码)...
searchOrSupportedCommand: 搜索或使用您可以使用的命令(一次只能执行一个命令)
type: 键入并按回车键进行搜索。
types: 类型
video: 视频
writeToSearch: 请输入要搜索的内容
languages:
en: English
fr: Français
pt: Português
ja: 日本語
zhCN: 中文 (简体)
zhTW: 中文 (繁體)
time:
unit: 时间单位
seconds:
minutes: 分钟
hours: 小时
days:
permanent: 永久
buttons:
cancel: 取消
close: 关闭
copy: 复制
copyFile: 复制文件
copyToClipboard: 复制到剪贴板
create: 创建
delete: 删除
download: 下载
info: 信息
more: 更多
move: 移动
moveFile: 移动文件
new:
next: 下一个
ok: 确定
replace: 替换
previous: 上一个
rename: 重命名
reportIssue: 报告问题
save: 保存
search: 搜索
select: 选择
share: 分享
publish: 发布
selectMultiple: 选择多个
schedule: 计划
switchView: 切换显示方式
toggleSidebar: 切换侧边栏
update: 更新
upload: 上传
permalink: 获取永久链接
success:
linkCopied: 链接已复制!
errors:
forbidden: 你被禁止访问
internal: 内部出现麻烦了。
notFound: 找不到文件
files:
folders: 文件夹
files: 文件
body: Body
clear: 清空
closePreview: 关闭预览
home: 主页
lastModified: 最后修改
loading: 加载中...
lonely: 这里没有任何文件...
metadata: 元数据
multipleSelectionEnabled: 多选模式已开启
name: 名称
size: 大小
sortByName: 名称排序
sortBySize: 按大小排序
sortByLastModified: 按最后修改时间排序
help:
click: 选择文件或目录
ctrl:
click: 选择多个文件或目录
f: 打开搜索框
s: 保存文件或下载当前文件夹
del: 删除所选的文件/文件夹
doubleClick: 打开文件/文件夹
esc: 清除已选项或关闭提示信息
f1: 显示该帮助信息
f2: 重命名文件/文件夹
help: 帮助
login:
password: 密码
submit: 登录
username: 用户名
wrongCredentials: 用户名或密码错误
prompts:
copy: 复制
copyMessage: 请选择欲复制至的目录:
currentlyNavigating: 当前目录:
deleteMessageMultiple: 你确定要删除这 {count} 个文件吗?
deleteMessageSingle: 你确定要删除这个文件/文件夹吗?
deleteTitle: 删除文件
displayName: 名称:
download: 下载文件
downloadMessage: 请选择要下载的压缩格式。
error: 出了一点问题...
fileInfo: 文件信息
filesSelected: 已选择 {count} 个文件。
lastModified: 最后修改
move: 移动
moveMessage: 选择欲移动至的目录:
newDir: 新建目录
newDirMessage: 请输入新目录的名称。
newFile: 新建文件
newFileMessage: 请输入新文件的名称。
numberDirs: 目录数
numberFiles: 文件数
replace: 替换
replaceMessage: "\
您尝试上传的文件中有一个与现有文件的名称存在冲突。\
是否替换现有的同名文件?"
rename: 重命名
renameMessage: 请输入新名称,旧名称为:
show: 揭示
size: 大小
schedule: 计划
scheduleMessage: 请选择发布这篇帖子的日期。
newArchetype: 创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。
settings:
admin: 管理员
administrator: 管理员
allowCommands: 执行命令(Linux 代码)
allowEdit: 编辑、重命名或删除文件/目录
allowNew: 创建新文件和目录
allowPublish: 发布新的帖子与页面
avoidChanges: '(留空以避免更改)'
changePassword: 更改密码
commands: 命令(linux 代码)
commandsHelp: "\
在这里,您可以设置在指定事件下执行的命令,一行一条。\
若事件与文件相关,如“在保存文件前”,\
则文件的路径会被赋值给环境变量 \"FILE\"。"
commandsUpdated: 命令已更新!
customStylesheet: 自定义样式表
examples: 例子
globalSettings: 全局设置
language: 语言
lockPassword: 禁止用户修改密码
newPassword: 您的新密码
newPasswordConfirm: 重输一遍新密码
newUser: 新建用户
password: 密码
passwordUpdated: 密码已更新!
permissions: 权限
permissionsHelp: "\
您可以将该用户设置为管理员,也可以单独选择各项权限。\
如果选择了“管理员”,则其他的选项会被自动勾上,\
同时该用户可以管理其他用户。"
profileSettings: 配置文件设置
ruleExample1: "\
阻止用户访问所有文件夹下任何以 . 开头的文件\
(隐藏文件, 例如: .git, .gitignore)。"
ruleExample2: 阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。
rules: 规则
rulesHelp1: "\
您可以为该用户制定一组黑名单或白名单式的规则,\
被屏蔽的文件将不会显示在列表中,用户也无权限访问,\
支持相对于目录范围的路径。"
rulesHelp2: "\
每行一条规则,且必须以关键词 {0} 或 {1} 开头。\
如要使用正则表达式,请在加上 {2} 之后再附上表达式或路径。"
scope: 目录范围
settingsUpdated: 设置已更新!
user: 用户
userCommands: 用户命令(Linux 代码)
userCommandsHelp: "\
指定该用户可以执行的命令(Linux 代码),用空格分隔。\
例如:"
userCreated: 用户已创建!
userDeleted: 用户已删除!
userManagement: 用户管理
username: 用户名
users: 用户
userUpdated: 用户已更新!
sidebar:
help: 帮助
logout: 登出
myFiles: 我的文件
newFile: 新建文件
newFolder: 新建文件夹
settings: 设置
siteSettings: 网站设置
hugoNew: Hugo New
preview: 预览
search:
images: 图像
music: 音乐
pdf: PDF
pressToExecute: 按回车键执行。
pressToSearch: 按回车键搜索。
search: 搜索...
searchOrCommand: 搜索或者执行命令(Linux 代码)...
searchOrSupportedCommand: 搜索或使用您可以使用的命令(一次只能执行一个命令)
type: 键入并按回车键进行搜索。
types: 类型
video: 视频
writeToSearch: 请输入要搜索的内容
languages:
en: English
fr: Français
pt: Português
ja: 日本語
zhCN: 中文 (简体)
zhTW: 中文 (繁體)
time:
unit: 时间单位
seconds:
minutes: 分钟
hours: 小时
days:

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,9 +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

@@ -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,60 +1,60 @@
// Most of the code from this file comes from:
// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js
import * as CodeMirror from 'codemirror'
import store from '@/store'
// Make CodeMirror available globally so the modes' can register themselves.
window.CodeMirror = CodeMirror
CodeMirror.modeURL = store.state.baseURL + '/static/js/codemirror/mode/%N/%N.js'
var loading = {}
function splitCallback (cont, n) {
var countDown = n
return function () {
if (--countDown === 0) cont()
}
}
function ensureDeps (mode, cont) {
var deps = CodeMirror.modes[mode].dependencies
if (!deps) return cont()
var missing = []
for (var i = 0; i < deps.length; ++i) {
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
}
if (!missing.length) return cont()
var split = splitCallback(cont, missing.length)
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
}
CodeMirror.requireMode = function (mode, cont) {
if (typeof mode !== 'string') mode = mode.name
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
var file = CodeMirror.modeURL.replace(/%N/g, mode)
var script = document.createElement('script')
script.src = file
var others = document.getElementsByTagName('script')[0]
var list = loading[mode] = [cont]
CodeMirror.on(script, 'load', function () {
ensureDeps(mode, function () {
for (var i = 0; i < list.length; ++i) list[i]()
})
})
others.parentNode.insertBefore(script, others)
}
CodeMirror.autoLoadMode = function (instance, mode) {
if (CodeMirror.modes.hasOwnProperty(mode)) return
CodeMirror.requireMode(mode, function () {
instance.setOption('mode', mode)
})
}
export default CodeMirror
// Most of the code from this file comes from:
// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js
import * as CodeMirror from 'codemirror'
import store from '@/store'
// Make CodeMirror available globally so the modes' can register themselves.
window.CodeMirror = CodeMirror
CodeMirror.modeURL = store.state.baseURL + '/static/js/codemirror/mode/%N/%N.js'
var loading = {}
function splitCallback (cont, n) {
var countDown = n
return function () {
if (--countDown === 0) cont()
}
}
function ensureDeps (mode, cont) {
var deps = CodeMirror.modes[mode].dependencies
if (!deps) return cont()
var missing = []
for (var i = 0; i < deps.length; ++i) {
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
}
if (!missing.length) return cont()
var split = splitCallback(cont, missing.length)
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
}
CodeMirror.requireMode = function (mode, cont) {
if (typeof mode !== 'string') mode = mode.name
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
var file = CodeMirror.modeURL.replace(/%N/g, mode)
var script = document.createElement('script')
script.src = file
var others = document.getElementsByTagName('script')[0]
var list = loading[mode] = [cont]
CodeMirror.on(script, 'load', function () {
ensureDeps(mode, function () {
for (var i = 0; i < list.length; ++i) list[i]()
})
})
others.parentNode.insertBefore(script, others)
}
CodeMirror.autoLoadMode = function (instance, mode) {
if (CodeMirror.modes.hasOwnProperty(mode)) return
CodeMirror.requireMode(mode, function () {
instance.setOption('mode', mode)
})
}
export default CodeMirror

View File

@@ -1,4 +1,4 @@
export default function (name) {
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
return document.cookie.replace(re, '$1')
}
export default function (name) {
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
return document.cookie.replace(re, '$1')
}

View File

@@ -1,28 +1,28 @@
export default function getRule (rules) {
for (let i = 0; i < rules.length; i++) {
rules[i] = rules[i].toLowerCase()
}
let result = null
let find = Array.prototype.find
find.call(document.styleSheets, styleSheet => {
result = find.call(styleSheet.cssRules, cssRule => {
let found = false
if (cssRule instanceof window.CSSStyleRule) {
for (let i = 0; i < rules.length; i++) {
if (cssRule.selectorText.toLowerCase() === rules[i]) {
found = true
}
}
}
return found
})
return result != null
})
return result
}
export default function getRule (rules) {
for (let i = 0; i < rules.length; i++) {
rules[i] = rules[i].toLowerCase()
}
let result = null
let find = Array.prototype.find
find.call(document.styleSheets, styleSheet => {
result = find.call(styleSheet.cssRules, cssRule => {
let found = false
if (cssRule instanceof window.CSSStyleRule) {
for (let i = 0; i < rules.length; i++) {
if (cssRule.selectorText.toLowerCase() === rules[i]) {
found = true
}
}
}
return found
})
return result != null
})
return result
}

View File

@@ -1,12 +1,12 @@
function removeLastDir (url) {
var arr = url.split('/')
if (arr.pop() === '') {
arr.pop()
}
return arr.join('/')
}
export default {
removeLastDir: removeLastDir
}
function removeLastDir (url) {
var arr = url.split('/')
if (arr.pop() === '') {
arr.pop()
}
return arr.join('/')
}
export default {
removeLastDir: removeLastDir
}

View File

@@ -210,7 +210,7 @@ export default {
}
},
scroll (event) {
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
if (this.req.kind !== 'listing' || this.$store.state.user.viewMode === 'mosaic') return
let top = 112 - window.scrollY

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

@@ -1,13 +1,13 @@
<template>
<div>
<h2 class="message">
<i class="material-icons">error</i>
<span>{{ $t('errors.forbidden') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'forbidden'}
</script>
<template>
<div>
<h2 class="message">
<i class="material-icons">error</i>
<span>{{ $t('errors.forbidden') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'forbidden'}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<h2 class="message">
<i class="material-icons">gps_off</i>
<span>{{ $t('errors.notFound') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'not-found'}
</script>
<template>
<div>
<h2 class="message">
<i class="material-icons">gps_off</i>
<span>{{ $t('errors.notFound') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'not-found'}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<div>
<h2 class="message">
<i class="material-icons">error_outline</i>
<span>{{ $t('errors.internal') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'internal-error'}
</script>
<template>
<div>
<h2 class="message">
<i class="material-icons">error_outline</i>
<span>{{ $t('errors.internal') }}</span>
</h2>
</div>
</template>
<script>
export default {name: 'internal-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,92 @@
<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="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="password">{{ $t('settings.password') }}</label>
<input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password">
</p>
<h2>{{ $t('settings.permissions') }}</h2>
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
<p>
<label for="scope">{{ $t('settings.scope') }}</label>
<input type="text" v-model="filesystem" id="scope">
</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>
<p>
<label for="locale">{{ $t('settings.language') }}</label>
<languages id="locale" :selected.sync="locale"></languages>
</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" :disabled="admin" v-model="lockPassword"> {{ $t('settings.lockPassword') }}</p>
<h2>{{ $t('settings.rules') }}</h2>
<h3>{{ $t('settings.permissions') }}</h3>
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
<p class="small">{{ $t('settings.rulesHelp1') }}</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>
<i18n path="settings.rulesHelp2" tag="p" class="small">
<code>allow</code><code>disallow</code><code>regex</code>
</i18n>
<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"><strong>{{ $t('settings.examples') }}</strong></p>
<h3>{{ $t('settings.rules') }}</h3>
<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">{{ $t('settings.rulesHelp1') }}</p>
<textarea v-model.trim="rules"></textarea>
<i18n path="settings.rulesHelp2" tag="p" class="small">
<code>allow</code><code>disallow</code><code>regex</code>
</i18n>
<h2>{{ $t('settings.customStylesheet') }}</h2>
<p class="small"><strong>{{ $t('settings.examples') }}</strong></p>
<textarea name="css"></textarea>
<ul class="small">
<li><code>disallow regex \\/\\..+</code> - {{ $t('settings.ruleExample1') }}</li>
<li><code>disallow /Caddyfile</code> - {{ $t('settings.ruleExample2') }}</li>
</ul>
<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 v-model.trim="rules"></textarea>
<h3>{{ $t('settings.customStylesheet') }}</h3>
<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>
@@ -87,12 +102,14 @@ export default {
components: { Languages },
data: () => {
return {
originalUser: null,
id: 0,
admin: false,
allowNew: false,
allowEdit: false,
allowCommands: false,
allowPublish: false,
lockPassword: false,
permissions: {},
password: '',
username: '',
@@ -105,7 +122,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 +137,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,17 +148,19 @@ export default {
fetchData () {
let user = this.$route.params[0]
if (this.$route.path === '/users/new') {
if (this.$route.path === '/settings/users/new') {
user = 'base'
}
getUser(user).then(user => {
this.originalUser = user
this.id = user.ID
this.admin = user.admin
this.allowCommands = user.allowCommands
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 +186,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 +207,7 @@ export default {
this.allowPublish = false
this.permissins = {}
this.allowCommands = false
this.lockPassword = false
this.password = ''
this.username = ''
this.filesystem = ''
@@ -202,22 +223,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,28 +249,27 @@ 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 () {
let user = {
ID: this.id,
username: this.username,
password: this.password,
filesystem: this.filesystem,
admin: this.admin,
allowCommands: this.allowCommands,
allowNew: this.allowNew,
allowEdit: this.allowEdit,
allowPublish: this.allowPublish,
permissions: this.permissions,
css: this.css,
locale: this.locale,
commands: this.commands.split(' '),
rules: []
}
let user = this.originalUser
user.username = this.username
user.password = this.password
user.lockPassword = this.lockPassword
user.filesystem = this.filesystem
user.admin = this.admin
user.allowCommands = this.allowCommands
user.allowNew = this.allowNew
user.allowEdit = this.allowEdit
user.allowPublish = this.allowPublish
user.permissions = this.permissions
user.css = this.css
user.locale = this.locale
user.commands = this.commands.split(' ')
user.rules = []
let rules = this.rules.split('\n')

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>

Some files were not shown because too many files have changed in this diff Show More