From 55b52ea87b6c126ccffac29d2a574c150f16031a Mon Sep 17 00:00:00 2001 From: Serge Koba Date: Wed, 24 Jan 2018 18:51:31 +0200 Subject: [PATCH] Base commit --- .gitignore | 7 + .idea/.rakeTasks | 7 + .idea/boxroom-engine.iml | 29 ++ .../inspectionProfiles/profiles_settings.xml | 5 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/workspace.xml | 30 ++ Gemfile | 14 + Gemfile.lock | 152 +++++++ MIT-LICENSE | 20 + README.md | 31 +- Rakefile | 36 ++ app/assets/config/boxroom_manifest.js | 2 + app/assets/images/boxroom/.keep | 0 app/assets/images/boxroom/clipboard.png | Bin 0 -> 715 bytes app/assets/images/boxroom/clipboard_add.png | Bin 0 -> 640 bytes app/assets/images/boxroom/copy.png | Bin 0 -> 606 bytes app/assets/images/boxroom/delete.png | Bin 0 -> 663 bytes app/assets/images/boxroom/edit.png | Bin 0 -> 554 bytes app/assets/images/boxroom/exclamation.png | Bin 0 -> 1542 bytes app/assets/images/boxroom/extend.png | Bin 0 -> 661 bytes app/assets/images/boxroom/failed.png | Bin 0 -> 1629 bytes app/assets/images/boxroom/file.png | Bin 0 -> 485 bytes app/assets/images/boxroom/file_add.png | Bin 0 -> 607 bytes app/assets/images/boxroom/fileicons/7z.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/ai.png | Bin 0 -> 692 bytes app/assets/images/boxroom/fileicons/aif.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/aiff.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/audio.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/bz2.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/c.png | Bin 0 -> 557 bytes app/assets/images/boxroom/fileicons/conf.png | Bin 0 -> 592 bytes app/assets/images/boxroom/fileicons/cpp.png | Bin 0 -> 568 bytes app/assets/images/boxroom/fileicons/cs.png | Bin 0 -> 723 bytes app/assets/images/boxroom/fileicons/css.png | Bin 0 -> 630 bytes app/assets/images/boxroom/fileicons/csv.png | Bin 0 -> 702 bytes app/assets/images/boxroom/fileicons/divx.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/doc.png | Bin 0 -> 677 bytes app/assets/images/boxroom/fileicons/docx.png | Bin 0 -> 677 bytes app/assets/images/boxroom/fileicons/dot.png | Bin 0 -> 677 bytes app/assets/images/boxroom/fileicons/fla.png | Bin 0 -> 625 bytes app/assets/images/boxroom/fileicons/gif.png | Bin 0 -> 562 bytes app/assets/images/boxroom/fileicons/gz.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/htm.png | Bin 0 -> 702 bytes app/assets/images/boxroom/fileicons/html.png | Bin 0 -> 702 bytes app/assets/images/boxroom/fileicons/image.png | Bin 0 -> 562 bytes app/assets/images/boxroom/fileicons/java.png | Bin 0 -> 568 bytes app/assets/images/boxroom/fileicons/jpeg.png | Bin 0 -> 562 bytes app/assets/images/boxroom/fileicons/jpg.png | Bin 0 -> 562 bytes app/assets/images/boxroom/fileicons/js.png | Bin 0 -> 568 bytes app/assets/images/boxroom/fileicons/mdb.png | Bin 0 -> 672 bytes app/assets/images/boxroom/fileicons/mdbx.png | Bin 0 -> 672 bytes app/assets/images/boxroom/fileicons/mov.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/mp3.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/mpg.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/ogg.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/pdf.png | Bin 0 -> 665 bytes app/assets/images/boxroom/fileicons/php.png | Bin 0 -> 676 bytes app/assets/images/boxroom/fileicons/pl.png | Bin 0 -> 568 bytes app/assets/images/boxroom/fileicons/png.png | Bin 0 -> 562 bytes app/assets/images/boxroom/fileicons/ppt.png | Bin 0 -> 692 bytes app/assets/images/boxroom/fileicons/pptx.png | Bin 0 -> 692 bytes app/assets/images/boxroom/fileicons/ps.png | Bin 0 -> 717 bytes app/assets/images/boxroom/fileicons/py.png | Bin 0 -> 568 bytes app/assets/images/boxroom/fileicons/ram.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/rar.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/rb.png | Bin 0 -> 669 bytes app/assets/images/boxroom/fileicons/rm.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/rtf.png | Bin 0 -> 592 bytes app/assets/images/boxroom/fileicons/sql.png | Bin 0 -> 376 bytes app/assets/images/boxroom/fileicons/swf.png | Bin 0 -> 660 bytes app/assets/images/boxroom/fileicons/tar.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/tgz.png | Bin 0 -> 646 bytes app/assets/images/boxroom/fileicons/txt.png | Bin 0 -> 592 bytes app/assets/images/boxroom/fileicons/video.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/wav.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/wma.png | Bin 0 -> 721 bytes app/assets/images/boxroom/fileicons/wmv.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/xls.png | Bin 0 -> 672 bytes app/assets/images/boxroom/fileicons/xlsx.png | Bin 0 -> 672 bytes app/assets/images/boxroom/fileicons/xml.png | Bin 0 -> 630 bytes app/assets/images/boxroom/fileicons/xvid.png | Bin 0 -> 654 bytes app/assets/images/boxroom/fileicons/zip.png | Bin 0 -> 646 bytes app/assets/images/boxroom/folder.png | Bin 0 -> 28239 bytes app/assets/images/boxroom/folder_add.png | Bin 0 -> 857 bytes app/assets/images/boxroom/group.png | Bin 0 -> 870 bytes app/assets/images/boxroom/group_add.png | Bin 0 -> 845 bytes app/assets/images/boxroom/group_grey.png | Bin 0 -> 745 bytes app/assets/images/boxroom/information.png | Bin 0 -> 2267 bytes app/assets/images/boxroom/logo.png | Bin 0 -> 4714 bytes app/assets/images/boxroom/move.png | Bin 0 -> 661 bytes app/assets/images/boxroom/permissions.png | Bin 0 -> 636 bytes app/assets/images/boxroom/share.png | Bin 0 -> 765 bytes app/assets/images/boxroom/spinner.gif | Bin 0 -> 1609 bytes app/assets/images/boxroom/tick.png | Bin 0 -> 582 bytes app/assets/images/boxroom/user.png | Bin 0 -> 712 bytes app/assets/images/boxroom/user_add.png | Bin 0 -> 785 bytes .../javascripts/boxroom/application.js.coffee | 51 +++ .../javascripts/boxroom/files.js.coffee | 28 ++ .../stylesheets/boxroom/application.scss | 88 ++++ app/controllers/boxroom/admins_controller.rb | 28 ++ .../boxroom/application_controller.rb | 78 ++++ .../boxroom/clipboard_controller.rb | 77 ++++ app/controllers/boxroom/files_controller.rb | 64 +++ app/controllers/boxroom/folders_controller.rb | 89 ++++ app/controllers/boxroom/groups_controller.rb | 58 +++ .../boxroom/permissions_controller.rb | 17 + .../boxroom/reset_password_controller.rb | 43 ++ .../boxroom/sessions_controller.rb | 46 ++ .../boxroom/share_links_controller.rb | 65 +++ app/controllers/boxroom/signup_controller.rb | 29 ++ app/controllers/boxroom/users_controller.rb | 73 ++++ app/helpers/boxroom/application_helper.rb | 4 + app/helpers/boxroom/folders_helper.rb | 17 + app/jobs/boxroom/application_job.rb | 4 + app/mailers/boxroom/application_mailer.rb | 6 + app/mailers/boxroom/user_mailer.rb | 18 + app/models/boxroom/application_record.rb | 5 + app/models/boxroom/clipboard.rb | 45 ++ app/models/boxroom/folder.rb | 113 +++++ app/models/boxroom/group.rb | 57 +++ app/models/boxroom/permission.rb | 6 + app/models/boxroom/permitted_params.rb | 33 ++ app/models/boxroom/share_link.rb | 40 ++ app/models/boxroom/user.rb | 113 +++++ app/models/boxroom/user_file.rb | 35 ++ app/views/boxroom/admins/new.html.erb | 28 ++ .../clipboard/_clipboard_empty.de.html.erb | 2 + .../clipboard/_clipboard_empty.en.html.erb | 2 + .../clipboard/_clipboard_empty.es.html.erb | 2 + .../clipboard/_clipboard_empty.fr.html.erb | 2 + .../clipboard/_clipboard_empty.it.html.erb | 2 + .../clipboard/_clipboard_empty.nl.html.erb | 2 + .../clipboard/_clipboard_empty.zh-CN.html.erb | 2 + app/views/boxroom/clipboard/_show.html.erb | 70 +++ app/views/boxroom/files/edit.html.erb | 14 + app/views/boxroom/files/new.html.erb | 25 ++ app/views/boxroom/folders/_form.html.erb | 9 + app/views/boxroom/folders/edit.html.erb | 6 + app/views/boxroom/folders/new.html.erb | 6 + app/views/boxroom/folders/show.html.erb | 92 ++++ app/views/boxroom/groups/_form.html.erb | 11 + app/views/boxroom/groups/edit.html.erb | 4 + app/views/boxroom/groups/index.html.erb | 29 ++ app/views/boxroom/groups/new.html.erb | 4 + app/views/boxroom/permissions/_form.html.erb | 41 ++ .../reset_password/_message.de.html.erb | 2 + .../reset_password/_message.en.html.erb | 2 + .../reset_password/_message.es.html.erb | 2 + .../reset_password/_message.fr.html.erb | 2 + .../reset_password/_message.it.html.erb | 2 + .../reset_password/_message.nl.html.erb | 2 + .../reset_password/_message.zh-CN.html.erb | 2 + .../boxroom/reset_password/edit.html.erb | 18 + app/views/boxroom/reset_password/new.html.erb | 16 + app/views/boxroom/sessions/new.html.erb | 21 + app/views/boxroom/share_links/index.html.erb | 24 + app/views/boxroom/share_links/new.html.erb | 48 ++ app/views/boxroom/shared/_footer.html.erb | 5 + app/views/boxroom/shared/_header.html.erb | 11 + app/views/boxroom/shared/_menu.html.erb | 13 + app/views/boxroom/signup/edit.html.erb | 26 ++ .../reset_password_email.de.text.erb | 18 + .../reset_password_email.en.text.erb | 17 + .../reset_password_email.es.text.erb | 17 + .../reset_password_email.fr.text.erb | 17 + .../reset_password_email.it.text.erb | 17 + .../reset_password_email.nl.text.erb | 17 + .../reset_password_email.zh-CN.text.erb | 16 + .../user_mailer/share_link_email.de.text.erb | 20 + .../user_mailer/share_link_email.en.text.erb | 20 + .../user_mailer/share_link_email.es.text.erb | 20 + .../user_mailer/share_link_email.fr.text.erb | 20 + .../user_mailer/share_link_email.it.text.erb | 20 + .../user_mailer/share_link_email.nl.text.erb | 20 + .../share_link_email.zh-CN.text.erb | 20 + .../user_mailer/signup_email.de.text.erb | 9 + .../user_mailer/signup_email.en.text.erb | 9 + .../user_mailer/signup_email.es.text.erb | 9 + .../user_mailer/signup_email.fr.text.erb | 9 + .../user_mailer/signup_email.it.text.erb | 9 + .../user_mailer/signup_email.nl.text.erb | 10 + .../user_mailer/signup_email.zh-CN.text.erb | 8 + app/views/boxroom/users/_form.html.erb | 46 ++ app/views/boxroom/users/edit.html.erb | 4 + app/views/boxroom/users/index.html.erb | 60 +++ app/views/boxroom/users/new.html.erb | 4 + .../layouts/boxroom/application.html.erb | 39 ++ bin/rails | 14 + boxroom.gemspec | 26 ++ config/locales/de.yml | 412 ++++++++++++++++++ config/locales/en.yml | 405 +++++++++++++++++ config/locales/es.yml | 401 +++++++++++++++++ config/locales/fr.yml | 401 +++++++++++++++++ config/locales/it.yml | 412 ++++++++++++++++++ config/locales/nl.yml | 406 +++++++++++++++++ config/locales/zh-CN.yml | 404 +++++++++++++++++ config/routes.rb | 43 ++ db/migrate/20100930062939_create_users.rb | 20 + db/migrate/20100930091426_create_folders.rb | 14 + db/migrate/20100930091451_create_groups.rb | 12 + .../20101002122244_create_user_files.rb | 17 + .../20101005071402_create_permissions.rb | 16 + .../20101005071508_create_groups_users.rb | 12 + ...045148_drop_column_user_id_from_folders.rb | 9 + ...414_drop_column_user_id_from_user_files.rb | 9 + ...23402_drop_column_access_key_from_users.rb | 9 + .../20110616215033_create_share_links.rb | 15 + ...075110_add_column_signup_token_to_users.rb | 8 + ...column_signup_token_expires_at_to_users.rb | 7 + ...1_alter_column_type_is_admin_from_users.rb | 9 + ..._columns_message_user_id_to_share_links.rb | 6 + ...8082245_populate_user_id_in_share_links.rb | 9 + lib/boxroom.rb | 24 + lib/boxroom/configuration.rb | 10 + lib/boxroom/engine.rb | 9 + lib/boxroom/version.rb | 3 + lib/tasks/boxroom_tasks.rake | 4 + test/boxroom_test.rb | 7 + test/dummy/Rakefile | 6 + test/dummy/app/assets/config/manifest.js | 5 + test/dummy/app/assets/images/.keep | 0 .../app/assets/javascripts/application.js | 13 + test/dummy/app/assets/javascripts/cable.js | 13 + .../app/assets/javascripts/channels/.keep | 0 .../app/assets/stylesheets/application.css | 15 + .../app/channels/application_cable/channel.rb | 4 + .../channels/application_cable/connection.rb | 4 + .../app/controllers/application_controller.rb | 3 + test/dummy/app/controllers/concerns/.keep | 0 test/dummy/app/helpers/application_helper.rb | 2 + test/dummy/app/jobs/application_job.rb | 2 + test/dummy/app/mailers/application_mailer.rb | 4 + test/dummy/app/models/application_record.rb | 3 + test/dummy/app/models/concerns/.keep | 0 .../app/views/layouts/application.html.erb | 14 + test/dummy/app/views/layouts/mailer.html.erb | 13 + test/dummy/app/views/layouts/mailer.text.erb | 1 + test/dummy/bin/bundle | 3 + test/dummy/bin/rails | 4 + test/dummy/bin/rake | 4 + test/dummy/bin/setup | 38 ++ test/dummy/bin/update | 29 ++ test/dummy/bin/yarn | 11 + test/dummy/config.ru | 5 + test/dummy/config/application.rb | 18 + test/dummy/config/boot.rb | 5 + test/dummy/config/cable.yml | 10 + test/dummy/config/database.yml | 25 ++ test/dummy/config/environment.rb | 5 + test/dummy/config/environments/development.rb | 54 +++ test/dummy/config/environments/production.rb | 91 ++++ test/dummy/config/environments/test.rb | 42 ++ .../application_controller_renderer.rb | 8 + test/dummy/config/initializers/assets.rb | 14 + .../initializers/backtrace_silencers.rb | 7 + .../config/initializers/cookies_serializer.rb | 5 + .../initializers/filter_parameter_logging.rb | 4 + test/dummy/config/initializers/inflections.rb | 16 + test/dummy/config/initializers/mime_types.rb | 4 + .../config/initializers/wrap_parameters.rb | 14 + test/dummy/config/locales/en.yml | 33 ++ test/dummy/config/puma.rb | 56 +++ test/dummy/config/routes.rb | 3 + test/dummy/config/secrets.yml | 32 ++ test/dummy/config/spring.rb | 6 + test/dummy/lib/assets/.keep | 0 test/dummy/log/.keep | 0 test/dummy/package.json | 5 + test/dummy/public/404.html | 67 +++ test/dummy/public/422.html | 67 +++ test/dummy/public/500.html | 66 +++ .../public/apple-touch-icon-precomposed.png | 0 test/dummy/public/apple-touch-icon.png | 0 test/dummy/public/favicon.ico | 0 test/factories.rb | 45 ++ test/fixtures/textfile.txt | 1 + test/integration/navigation_test.rb | 8 + test/test_helper.rb | 29 ++ test/unit/clipboard_test.rb | 134 ++++++ test/unit/folder_test.rb | 207 +++++++++ test/unit/group_test.rb | 72 +++ test/unit/share_link_test.rb | 78 ++++ test/unit/user_file_test.rb | 137 ++++++ test/unit/user_test.rb | 237 ++++++++++ 285 files changed, 7651 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .idea/.rakeTasks create mode 100644 .idea/boxroom-engine.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/workspace.xml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 MIT-LICENSE create mode 100644 Rakefile create mode 100644 app/assets/config/boxroom_manifest.js create mode 100644 app/assets/images/boxroom/.keep create mode 100755 app/assets/images/boxroom/clipboard.png create mode 100755 app/assets/images/boxroom/clipboard_add.png create mode 100755 app/assets/images/boxroom/copy.png create mode 100755 app/assets/images/boxroom/delete.png create mode 100755 app/assets/images/boxroom/edit.png create mode 100755 app/assets/images/boxroom/exclamation.png create mode 100755 app/assets/images/boxroom/extend.png create mode 100755 app/assets/images/boxroom/failed.png create mode 100755 app/assets/images/boxroom/file.png create mode 100755 app/assets/images/boxroom/file_add.png create mode 100755 app/assets/images/boxroom/fileicons/7z.png create mode 100755 app/assets/images/boxroom/fileicons/ai.png create mode 100755 app/assets/images/boxroom/fileicons/aif.png create mode 100755 app/assets/images/boxroom/fileicons/aiff.png create mode 100755 app/assets/images/boxroom/fileicons/audio.png create mode 100755 app/assets/images/boxroom/fileicons/bz2.png create mode 100755 app/assets/images/boxroom/fileicons/c.png create mode 100755 app/assets/images/boxroom/fileicons/conf.png create mode 100755 app/assets/images/boxroom/fileicons/cpp.png create mode 100755 app/assets/images/boxroom/fileicons/cs.png create mode 100755 app/assets/images/boxroom/fileicons/css.png create mode 100755 app/assets/images/boxroom/fileicons/csv.png create mode 100755 app/assets/images/boxroom/fileicons/divx.png create mode 100755 app/assets/images/boxroom/fileicons/doc.png create mode 100755 app/assets/images/boxroom/fileicons/docx.png create mode 100755 app/assets/images/boxroom/fileicons/dot.png create mode 100755 app/assets/images/boxroom/fileicons/fla.png create mode 100755 app/assets/images/boxroom/fileicons/gif.png create mode 100755 app/assets/images/boxroom/fileicons/gz.png create mode 100755 app/assets/images/boxroom/fileicons/htm.png create mode 100755 app/assets/images/boxroom/fileicons/html.png create mode 100755 app/assets/images/boxroom/fileicons/image.png create mode 100755 app/assets/images/boxroom/fileicons/java.png create mode 100755 app/assets/images/boxroom/fileicons/jpeg.png create mode 100755 app/assets/images/boxroom/fileicons/jpg.png create mode 100755 app/assets/images/boxroom/fileicons/js.png create mode 100755 app/assets/images/boxroom/fileicons/mdb.png create mode 100755 app/assets/images/boxroom/fileicons/mdbx.png create mode 100755 app/assets/images/boxroom/fileicons/mov.png create mode 100755 app/assets/images/boxroom/fileicons/mp3.png create mode 100755 app/assets/images/boxroom/fileicons/mpg.png create mode 100755 app/assets/images/boxroom/fileicons/ogg.png create mode 100755 app/assets/images/boxroom/fileicons/pdf.png create mode 100755 app/assets/images/boxroom/fileicons/php.png create mode 100755 app/assets/images/boxroom/fileicons/pl.png create mode 100755 app/assets/images/boxroom/fileicons/png.png create mode 100755 app/assets/images/boxroom/fileicons/ppt.png create mode 100755 app/assets/images/boxroom/fileicons/pptx.png create mode 100755 app/assets/images/boxroom/fileicons/ps.png create mode 100755 app/assets/images/boxroom/fileicons/py.png create mode 100755 app/assets/images/boxroom/fileicons/ram.png create mode 100755 app/assets/images/boxroom/fileicons/rar.png create mode 100755 app/assets/images/boxroom/fileicons/rb.png create mode 100755 app/assets/images/boxroom/fileicons/rm.png create mode 100755 app/assets/images/boxroom/fileicons/rtf.png create mode 100755 app/assets/images/boxroom/fileicons/sql.png create mode 100755 app/assets/images/boxroom/fileicons/swf.png create mode 100755 app/assets/images/boxroom/fileicons/tar.png create mode 100755 app/assets/images/boxroom/fileicons/tgz.png create mode 100755 app/assets/images/boxroom/fileicons/txt.png create mode 100755 app/assets/images/boxroom/fileicons/video.png create mode 100755 app/assets/images/boxroom/fileicons/wav.png create mode 100755 app/assets/images/boxroom/fileicons/wma.png create mode 100755 app/assets/images/boxroom/fileicons/wmv.png create mode 100755 app/assets/images/boxroom/fileicons/xls.png create mode 100755 app/assets/images/boxroom/fileicons/xlsx.png create mode 100755 app/assets/images/boxroom/fileicons/xml.png create mode 100755 app/assets/images/boxroom/fileicons/xvid.png create mode 100755 app/assets/images/boxroom/fileicons/zip.png create mode 100644 app/assets/images/boxroom/folder.png create mode 100644 app/assets/images/boxroom/folder_add.png create mode 100755 app/assets/images/boxroom/group.png create mode 100644 app/assets/images/boxroom/group_add.png create mode 100644 app/assets/images/boxroom/group_grey.png create mode 100755 app/assets/images/boxroom/information.png create mode 100644 app/assets/images/boxroom/logo.png create mode 100755 app/assets/images/boxroom/move.png create mode 100755 app/assets/images/boxroom/permissions.png create mode 100755 app/assets/images/boxroom/share.png create mode 100755 app/assets/images/boxroom/spinner.gif create mode 100755 app/assets/images/boxroom/tick.png create mode 100755 app/assets/images/boxroom/user.png create mode 100755 app/assets/images/boxroom/user_add.png create mode 100644 app/assets/javascripts/boxroom/application.js.coffee create mode 100644 app/assets/javascripts/boxroom/files.js.coffee create mode 100644 app/assets/stylesheets/boxroom/application.scss create mode 100644 app/controllers/boxroom/admins_controller.rb create mode 100644 app/controllers/boxroom/application_controller.rb create mode 100644 app/controllers/boxroom/clipboard_controller.rb create mode 100644 app/controllers/boxroom/files_controller.rb create mode 100644 app/controllers/boxroom/folders_controller.rb create mode 100644 app/controllers/boxroom/groups_controller.rb create mode 100644 app/controllers/boxroom/permissions_controller.rb create mode 100644 app/controllers/boxroom/reset_password_controller.rb create mode 100644 app/controllers/boxroom/sessions_controller.rb create mode 100644 app/controllers/boxroom/share_links_controller.rb create mode 100644 app/controllers/boxroom/signup_controller.rb create mode 100644 app/controllers/boxroom/users_controller.rb create mode 100644 app/helpers/boxroom/application_helper.rb create mode 100644 app/helpers/boxroom/folders_helper.rb create mode 100644 app/jobs/boxroom/application_job.rb create mode 100644 app/mailers/boxroom/application_mailer.rb create mode 100644 app/mailers/boxroom/user_mailer.rb create mode 100644 app/models/boxroom/application_record.rb create mode 100644 app/models/boxroom/clipboard.rb create mode 100644 app/models/boxroom/folder.rb create mode 100644 app/models/boxroom/group.rb create mode 100644 app/models/boxroom/permission.rb create mode 100644 app/models/boxroom/permitted_params.rb create mode 100644 app/models/boxroom/share_link.rb create mode 100644 app/models/boxroom/user.rb create mode 100644 app/models/boxroom/user_file.rb create mode 100644 app/views/boxroom/admins/new.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.de.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.en.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.es.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.fr.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.it.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.nl.html.erb create mode 100644 app/views/boxroom/clipboard/_clipboard_empty.zh-CN.html.erb create mode 100644 app/views/boxroom/clipboard/_show.html.erb create mode 100644 app/views/boxroom/files/edit.html.erb create mode 100644 app/views/boxroom/files/new.html.erb create mode 100644 app/views/boxroom/folders/_form.html.erb create mode 100644 app/views/boxroom/folders/edit.html.erb create mode 100644 app/views/boxroom/folders/new.html.erb create mode 100644 app/views/boxroom/folders/show.html.erb create mode 100644 app/views/boxroom/groups/_form.html.erb create mode 100644 app/views/boxroom/groups/edit.html.erb create mode 100644 app/views/boxroom/groups/index.html.erb create mode 100644 app/views/boxroom/groups/new.html.erb create mode 100644 app/views/boxroom/permissions/_form.html.erb create mode 100644 app/views/boxroom/reset_password/_message.de.html.erb create mode 100644 app/views/boxroom/reset_password/_message.en.html.erb create mode 100644 app/views/boxroom/reset_password/_message.es.html.erb create mode 100644 app/views/boxroom/reset_password/_message.fr.html.erb create mode 100644 app/views/boxroom/reset_password/_message.it.html.erb create mode 100644 app/views/boxroom/reset_password/_message.nl.html.erb create mode 100644 app/views/boxroom/reset_password/_message.zh-CN.html.erb create mode 100644 app/views/boxroom/reset_password/edit.html.erb create mode 100644 app/views/boxroom/reset_password/new.html.erb create mode 100644 app/views/boxroom/sessions/new.html.erb create mode 100644 app/views/boxroom/share_links/index.html.erb create mode 100644 app/views/boxroom/share_links/new.html.erb create mode 100644 app/views/boxroom/shared/_footer.html.erb create mode 100644 app/views/boxroom/shared/_header.html.erb create mode 100644 app/views/boxroom/shared/_menu.html.erb create mode 100644 app/views/boxroom/signup/edit.html.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.de.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.en.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.es.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.fr.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.it.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.nl.text.erb create mode 100644 app/views/boxroom/user_mailer/reset_password_email.zh-CN.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.de.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.en.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.es.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.fr.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.it.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.nl.text.erb create mode 100644 app/views/boxroom/user_mailer/share_link_email.zh-CN.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.de.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.en.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.es.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.fr.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.it.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.nl.text.erb create mode 100644 app/views/boxroom/user_mailer/signup_email.zh-CN.text.erb create mode 100644 app/views/boxroom/users/_form.html.erb create mode 100644 app/views/boxroom/users/edit.html.erb create mode 100644 app/views/boxroom/users/index.html.erb create mode 100644 app/views/boxroom/users/new.html.erb create mode 100644 app/views/layouts/boxroom/application.html.erb create mode 100755 bin/rails create mode 100644 boxroom.gemspec create mode 100644 config/locales/de.yml create mode 100644 config/locales/en.yml create mode 100644 config/locales/es.yml create mode 100644 config/locales/fr.yml create mode 100644 config/locales/it.yml create mode 100644 config/locales/nl.yml create mode 100644 config/locales/zh-CN.yml create mode 100644 config/routes.rb create mode 100644 db/migrate/20100930062939_create_users.rb create mode 100644 db/migrate/20100930091426_create_folders.rb create mode 100644 db/migrate/20100930091451_create_groups.rb create mode 100644 db/migrate/20101002122244_create_user_files.rb create mode 100644 db/migrate/20101005071402_create_permissions.rb create mode 100644 db/migrate/20101005071508_create_groups_users.rb create mode 100644 db/migrate/20110106045148_drop_column_user_id_from_folders.rb create mode 100644 db/migrate/20110106045414_drop_column_user_id_from_user_files.rb create mode 100644 db/migrate/20110529123402_drop_column_access_key_from_users.rb create mode 100644 db/migrate/20110616215033_create_share_links.rb create mode 100644 db/migrate/20120411075110_add_column_signup_token_to_users.rb create mode 100644 db/migrate/20120411081345_add_column_signup_token_expires_at_to_users.rb create mode 100644 db/migrate/20130307082111_alter_column_type_is_admin_from_users.rb create mode 100644 db/migrate/20130626210927_add_columns_message_user_id_to_share_links.rb create mode 100644 db/migrate/20130628082245_populate_user_id_in_share_links.rb create mode 100644 lib/boxroom.rb create mode 100644 lib/boxroom/configuration.rb create mode 100644 lib/boxroom/engine.rb create mode 100644 lib/boxroom/version.rb create mode 100644 lib/tasks/boxroom_tasks.rake create mode 100644 test/boxroom_test.rb create mode 100644 test/dummy/Rakefile create mode 100644 test/dummy/app/assets/config/manifest.js create mode 100644 test/dummy/app/assets/images/.keep create mode 100644 test/dummy/app/assets/javascripts/application.js create mode 100644 test/dummy/app/assets/javascripts/cable.js create mode 100644 test/dummy/app/assets/javascripts/channels/.keep create mode 100644 test/dummy/app/assets/stylesheets/application.css create mode 100644 test/dummy/app/channels/application_cable/channel.rb create mode 100644 test/dummy/app/channels/application_cable/connection.rb create mode 100644 test/dummy/app/controllers/application_controller.rb create mode 100644 test/dummy/app/controllers/concerns/.keep create mode 100644 test/dummy/app/helpers/application_helper.rb create mode 100644 test/dummy/app/jobs/application_job.rb create mode 100644 test/dummy/app/mailers/application_mailer.rb create mode 100644 test/dummy/app/models/application_record.rb create mode 100644 test/dummy/app/models/concerns/.keep create mode 100644 test/dummy/app/views/layouts/application.html.erb create mode 100644 test/dummy/app/views/layouts/mailer.html.erb create mode 100644 test/dummy/app/views/layouts/mailer.text.erb create mode 100755 test/dummy/bin/bundle create mode 100755 test/dummy/bin/rails create mode 100755 test/dummy/bin/rake create mode 100755 test/dummy/bin/setup create mode 100755 test/dummy/bin/update create mode 100755 test/dummy/bin/yarn create mode 100644 test/dummy/config.ru create mode 100644 test/dummy/config/application.rb create mode 100644 test/dummy/config/boot.rb create mode 100644 test/dummy/config/cable.yml create mode 100644 test/dummy/config/database.yml create mode 100644 test/dummy/config/environment.rb create mode 100644 test/dummy/config/environments/development.rb create mode 100644 test/dummy/config/environments/production.rb create mode 100644 test/dummy/config/environments/test.rb create mode 100644 test/dummy/config/initializers/application_controller_renderer.rb create mode 100644 test/dummy/config/initializers/assets.rb create mode 100644 test/dummy/config/initializers/backtrace_silencers.rb create mode 100644 test/dummy/config/initializers/cookies_serializer.rb create mode 100644 test/dummy/config/initializers/filter_parameter_logging.rb create mode 100644 test/dummy/config/initializers/inflections.rb create mode 100644 test/dummy/config/initializers/mime_types.rb create mode 100644 test/dummy/config/initializers/wrap_parameters.rb create mode 100644 test/dummy/config/locales/en.yml create mode 100644 test/dummy/config/puma.rb create mode 100644 test/dummy/config/routes.rb create mode 100644 test/dummy/config/secrets.yml create mode 100644 test/dummy/config/spring.rb create mode 100644 test/dummy/lib/assets/.keep create mode 100644 test/dummy/log/.keep create mode 100644 test/dummy/package.json create mode 100644 test/dummy/public/404.html create mode 100644 test/dummy/public/422.html create mode 100644 test/dummy/public/500.html create mode 100644 test/dummy/public/apple-touch-icon-precomposed.png create mode 100644 test/dummy/public/apple-touch-icon.png create mode 100644 test/dummy/public/favicon.ico create mode 100644 test/factories.rb create mode 100644 test/fixtures/textfile.txt create mode 100644 test/integration/navigation_test.rb create mode 100644 test/test_helper.rb create mode 100644 test/unit/clipboard_test.rb create mode 100644 test/unit/folder_test.rb create mode 100644 test/unit/group_test.rb create mode 100644 test/unit/share_link_test.rb create mode 100644 test/unit/user_file_test.rb create mode 100644 test/unit/user_test.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71f82cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ diff --git a/.idea/.rakeTasks b/.idea/.rakeTasks new file mode 100644 index 0000000..c6865d9 --- /dev/null +++ b/.idea/.rakeTasks @@ -0,0 +1,7 @@ + + diff --git a/.idea/boxroom-engine.iml b/.idea/boxroom-engine.iml new file mode 100644 index 0000000..31a7fe2 --- /dev/null +++ b/.idea/boxroom-engine.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..0eefe32 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de6e841 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e62cd16 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..b08841a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,30 @@ + + + + + + + + + + 1516812529653 + + + + + + + + + + \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..40aeee6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org' + +# Declare your gem's dependencies in boxroom.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use a debugger +# gem 'byebug', group: [:development, :test] diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..de6d594 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,152 @@ +PATH + remote: . + specs: + boxroom (0.1.0) + acts_as_tree + dynamic_form + jquery-fileupload-rails + paperclip + rails (~> 5.0.2) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.0.2) + actionpack (= 5.0.2) + nio4r (>= 1.2, < 3.0) + websocket-driver (~> 0.6.1) + actionmailer (5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.0.2) + actionview (= 5.0.2) + activesupport (= 5.0.2) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.2) + activesupport (= 5.0.2) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.2) + activesupport (= 5.0.2) + globalid (>= 0.3.6) + activemodel (5.0.2) + activesupport (= 5.0.2) + activerecord (5.0.2) + activemodel (= 5.0.2) + activesupport (= 5.0.2) + arel (~> 7.0) + activesupport (5.0.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + acts_as_tree (2.7.0) + activerecord (>= 3.0.0) + arel (7.1.4) + builder (3.2.3) + climate_control (0.2.0) + cocaine (0.5.8) + climate_control (>= 0.0.3, < 1.0) + concurrent-ruby (1.0.5) + crass (1.0.2) + dynamic_form (1.1.4) + erubis (2.7.0) + ffi (1.9.18) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (0.9.0) + concurrent-ruby (~> 1.0) + jquery-fileupload-rails (0.4.7) + actionpack (>= 3.1) + railties (>= 3.1) + sass (>= 3.2) + loofah (2.1.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mimemagic (0.3.2) + mini_mime (0.1.4) + mini_portile2 (2.3.0) + minitest (5.10.3) + nio4r (2.0.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + paperclip (5.1.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + cocaine (~> 0.5.5) + mime-types + mimemagic (~> 0.3.0) + rack (2.0.1) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.0.2) + actioncable (= 5.0.2) + actionmailer (= 5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + activemodel (= 5.0.2) + activerecord (= 5.0.2) + activesupport (= 5.0.2) + bundler (>= 1.3.0, < 2.0) + railties (= 5.0.2) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.2) + activesupport (>= 4.2.0, < 6.0) + nokogiri (~> 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.0.2) + actionpack (= 5.0.2) + activesupport (= 5.0.2) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.2.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + sass (3.5.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.11) + thor (0.20.0) + thread_safe (0.3.6) + tzinfo (1.2.4) + thread_safe (~> 0.1) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + boxroom! + sqlite3 + +BUNDLED WITH + 1.16.0 diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..5aa9b99 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2018 Serge Koba + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 6766e93..151fa4d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ -# boxroom-engine -Boxroom web file manager Rails engine +# Boxroom +This is a Rails engine built based on code of [Boxroom](https://github.com/mischa78/boxroom) project. + +# Features + + +## Install +- add to Gemfile +- run `rails boxroom:install:migrations` +- run `rails db:migrate` + +## Config +- Create `config/initializers/boxroom.rb` +```ruby +Boxroom.configure do |config| + config.site_name = 'Boxroom' + config.logo = 'boxroom/logo.png' +end +``` +- mount engine in `config/routes.rb` +```ruby +mount Boxroom::Engine => "/boxroom" +``` + +## Contributing +Please feel free to leave an issue or PR. + +## License +The engine is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..add3afd --- /dev/null +++ b/Rakefile @@ -0,0 +1,36 @@ +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Boxroom' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.md') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' + + +load 'rails/tasks/statistics.rake' + + + +require 'bundler/gem_tasks' + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task default: :test diff --git a/app/assets/config/boxroom_manifest.js b/app/assets/config/boxroom_manifest.js new file mode 100644 index 0000000..6ca6727 --- /dev/null +++ b/app/assets/config/boxroom_manifest.js @@ -0,0 +1,2 @@ +//= link_directory ../javascripts/boxroom .js +//= link_directory ../stylesheets/boxroom .css diff --git a/app/assets/images/boxroom/.keep b/app/assets/images/boxroom/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/boxroom/clipboard.png b/app/assets/images/boxroom/clipboard.png new file mode 100755 index 0000000000000000000000000000000000000000..08647f1bee97cbd550c7871e7463ab8038d320ac GIT binary patch literal 715 zcmV;+0yO=JP)rTXax}q1&ey@rJno~JopdzRq^9cdZ~Db2gQrvxe-izXpn?NsDY^c zkZ6-lvf172I`i^by zP$(dB=LIxPMKBl;j^kq;hGF9H^<5;BNqkCgFu+B@pyLcFn5KzZt%k!#OHfr+s3b|S zEKB%&KA%`ebCgbJXmrR(Boe6C>%uNeA+#iI;MX(_Swn^H4{N`}Q*fNlI0FXYY8=R7 zA%N8f8?fozbR4v88`+&5pC93**Z&S z^Hd`#*~DO=RH2{TYPBf10g3v%ozJ6CE<^KrQLR?7wz-LgRO(qFms?iskvHH(!!Xcn zHqmakq5Cue5Rb=&U#17WwYUg_;v{;|cbQC0vF#xMSLH4^MB77&4h@S$=fz^NJ58a` zAnyG1GZR04qjRAOVc+md390Pu85y=2i9+ZBMNQc2h$ zV!&67Mx%lm0mw>O2hRA11J3sx3>Mv$zsSaW&Q}kv=hZY)x-feth{` xc}&~703eN&8z?@1mBIhW9~ayfhPVF-FaRZ;J`t+B(rf?#002ovPDHLkV1nhuPn-Y% literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/clipboard_add.png b/app/assets/images/boxroom/clipboard_add.png new file mode 100755 index 0000000000000000000000000000000000000000..1d0841d57bdf3c2a7582265e31a35cd5006eb1f7 GIT binary patch literal 640 zcmV-`0)PF9P)1R5%fRQ_pJ?K@|RGvzx3nA+Du`ZB>ZqN}(P!B0W}m&_h9x zRzQ401tu(FP=nDS_JXrL5iUCCSs3X)QeyOwkSxWX{t6GHp%`P=Z&4UF3|^X zXJ_8`zWHX}8^$?D+Rl!$EFYPgjN^EKVw$F{vk!|)Pu_Dsjn&n{y>Yg{7|Jw1VnE+> zU}$h`=9*3O*AMU3zI?=u`zKgPbFt@}xn+KZ{u6>2D4(33DOV~L9KA3P)67EGO~T;t zxmo+*>C1Ng@l`mEgBOdBA)s?nG9`JQClh=1$UpBO5*2Q@+p-F@8xu`I zzTFm_0jv+NqR%euySWw66hJ7JyHZjnlj-3GDuWG7OkKoUznNEg$q@o^1ZrwcB&Y56 z_%XQVClq6j&1e%HZW-*sa22e*oDhU`F``E^qH)sJ@T(X(CA%7puz|7jqm<{odiv5$ z2)bL(gyg3!-`b1S=QV_)e%7Rhjb{OBKfe9MTJTO6XWk(a3e!dT>SUwP?QgHZt?G*8 zywtw|9_>W^vm52-zaMI~y!~{|yM+%OH*kHIkR_3+kIRvxrIm?{?G{;|;qkA{rD#?x az5N9aCjDsLQy&=s0000eK^(>3%7-n|AZ{BBiR=rqUgi>lAz-rIIXo?H>qsx3K@q zhZRMUtljy%w6x*{0S27=d^w6^*tU&NYin=$Tv7YR(YpTxdh!v*er`gGGo@lSMl^^9 z0}L)N;kqvR{XV`fFR$?VGxkjuqm4bJQNSjO6bdD(Vp%xqbkN${L;Ls`@0v|KsMVTh z-R=kWyr%BuO)N2BY0YM>HNYglx%>0kS=47{GNdZ=BPIf~ z=!5lj^c-hICT3S&T^J;tJ6to0m|RPG>rc0P*Eu;kzdk+v-Z0)8AAkI+(U>zxY3?kE zZ0#Ea>dZYn{E}Xdq-qlrF(-2dsc0hWMAz==X=2bzW$I?%b-fIUisfF&*~kSG6H2f< s8yln6kl*)45ouUO7Z>LoM*j*h052ROJm1I;B>(^b07*qoM6N<$f`R`H!~g&Q literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/delete.png b/app/assets/images/boxroom/delete.png new file mode 100755 index 0000000000000000000000000000000000000000..70b59dc9debb268312321d0b10acd2f59f93c346 GIT binary patch literal 663 zcmV;I0%-k-P)I^#n zUT|vg3=v?B0yLT{7Bl;EImu8}t2{IFUY0^Yb4O?iifc1J27k5V$$FKm@>}!F+yzL@`vW zRF*VNJ1BUmxNA2jaBHx-y)9{6-sqR4J-RFhm+JLuDi%wqw!QGjvIb3Fv&Pmo>Rc?( zczs_weYnTx>#5Z1HQ6v8t~8q;LV-X8w(0i&=Ac6W_Oaer?8E_4l-3kweZw%GBjl%W zJo!ykr=os;7W!?Ar@KjDdwFLKqKikv;jCtwf6KD`3FjvYY;6y7bV}P3ZSB zJB)6@!(PMK7Fef^f*|aLVR(S(3c~&U7I~t|%fPB$aBdbph%*AgKBB%P(XIu?UUvxM xeB1;4p%%}~;yUBA6K@`VFTZv3%Qwy@d zguo;Tw385C>=1~T5W-W?Ve#xiJ9O_9MD!O#e?TaB^5Wg7-7aB7D3U=WnC8z_`#xDC zVh#4ehh=8x`+YO-&FG@hC{qam$e;#3hQnbQyn$}7*JFdh;AbI66Dn~979rU7`Fy)P z&)Y>&lxnrw9p)v_nJk=fyWMIAl>M{c@0Ti-iY*?GA4MXOeY4rTQY;pA?RNVGyXYao zR0Gy(wOF&+l)K$-ztia~dA;5phr{7*eE#lrl4 ze=?iRih)4jjJgA}<9t3}ve|5c)oKm>XTWukF5f=FWKT?6$Np`_LGft|G~v` z5nTkvZ!!Ff09e)s_5p|<-C?(2uXvRThPuZbPIH(G5=eH>7M2=!!k;#3axC-o~?^s85mMFH&YC-B~z08O5XXa;m838^Gv%P(A&LRm(YLVo&)4dcW5+;^z%v70@ptTu(Cl?07iO^W zWDQiB0XZ8d{?LvHvnkouNz7jMK&f%?fOP;S$&y zqkfBHMKm>ip%>krsI3ADMp7L-2D-g@*{cyLbp6wz#oMYuKDxd~*u^RDnrV zx&G!*f4GnuVh4^!X5jPl0SJ6irzi!GG9w_iZwa$GY;HCG)t5kp=a%@_cACsiyOg;$ zzjh|01lep>L!CjI*UPyZU@|%EjlSMaHn*C8fhrXmkARKmj9Z)AUtG@CURCLx<{@bq z9gV}}Nfkmm{@8z@kt3TYW_1zKJrt2>ylay+`b z0LvB-jE)UGSG2bN){T{&R9<|uLQJ356rMVFh zF#B@&=#v;+kL5KmxsolRNw7Csy=JQ|$~YFCgp#B}34%=3f!2;yAu7eSv!3UavJec7 zch(W*O18yHq`@B)LyUuQ&GB;iJOBe;Y8!1XvD@x!aH{f{ZdWhFhwpEJV9>9D$<^%H zQaBt2r`2KWW?XZ;LcX9SQOEOSAQb5cFmevJU9~TU^=pUW`7>{8ayD@cq#Ui`_E#Xd ztT8YaOAb(~{>XY6OQCiCw&pf5Y_oF~n#YDB)!$N-JZ`;PLsfUd-H+WaSbU;2JbHC@ z8}`Ft{?Z7*LtXI2+P;xc_96IvtiCzEaHz6keC30(p<4WHL0>V&&P|}nC sh>sn2tVv|;LMej%SMK}Q@!tXr0HW%-%;VJ5rvLx|07*qoM6N<$f<%AnVgLXD literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/extend.png b/app/assets/images/boxroom/extend.png new file mode 100755 index 0000000000000000000000000000000000000000..a305904473c5f4ca83d8e7baf7b3c948eeb1601c GIT binary patch literal 661 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/7z.png b/app/assets/images/boxroom/fileicons/7z.png new file mode 100755 index 0000000000000000000000000000000000000000..6d6333afc059e4866b2a6191662ab1c599d23292 GIT binary patch literal 646 zcmV;10(t$3P)t8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/ai.png b/app/assets/images/boxroom/fileicons/ai.png new file mode 100755 index 0000000000000000000000000000000000000000..9500998e8cd9d7b737f85926d1ba066d4a7ef459 GIT binary patch literal 692 zcmV;l0!#ggP)2eRD>|( z)Nqeiph^>HG#a=tK8~}M$~CHcO~i&o{Fv{L1_rXQZ2>8TDfE2}&r|4j6}GnngqJo@ ztJP8;50;m25!^8rxSQ4_b@&{_$w$E3%h2mjabV&%Hs|uBhBlGBa=C2m6bc2rs@Gcy zndA*Y5WzaJ2B|9w|bl1SKP6ESXsRF7}=OW{@omjy*FN zhCtS~{~=LfC@2{rJuiam#suFVA2yhbgFqW~b|#aOWMTrD5Llk4V>7-7$5945ofuhx z??;eS#3URU!jK4(GqAV=T^>FxjF^eW{D?WM_jAV#1_2BK2a{yT1`&vqpxcdRDJS{8 z@Ec&?^ags(o==e)O&@DJeR&azJ;9@o_{%n zH~G^%ZTfIGE;7htbJ;SM*IQ34IL#)kg_lhXk8UD2G-Uo4d`+^a;D01s={k<(wp!R& zTQh4p`Y8Tvwfa+;OhN@rz=y_Z(E(a~jfA|HYq*xX<|vE5wzk^)OQjj?A++nZxrTlJ a5nupeS6{4aY8+qy0000 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/aif.png b/app/assets/images/boxroom/fileicons/aif.png new file mode 100755 index 0000000000000000000000000000000000000000..73ce1ca4f9ea9a658968afc3c4aa2d66e0f673ec GIT binary patch literal 721 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/c.png b/app/assets/images/boxroom/fileicons/c.png new file mode 100755 index 0000000000000000000000000000000000000000..7aca3f88f3f3d68d26711337210718a13d7e5723 GIT binary patch literal 557 zcmV+|0@D47P)aqF=zlFCd7YL0p_V37y2TLqQZ=T-+Q~q=FTMptc1yX_B^ycd$>hIJGKpN42B8S1Tg~y0 zYl0Zw3ObmSL=l{Z0ea;LqI>h;G)=G{K7eD~s~NzUrrAFMb|(U>X@P+g8BIh1NT1a|dC%X(iTfNocI(RfM@bRmk8S vv7w<^A)Vg*_X=y;t*xq8v^uDtUjhsO?e5-h%&dbT00000NkvXXu0mjf*ZcIj literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/conf.png b/app/assets/images/boxroom/fileicons/conf.png new file mode 100755 index 0000000000000000000000000000000000000000..ed841a02a7d1bd376af2e50c7bf0032938f7fef7 GIT binary patch literal 592 zcmV-W07YdLMY{D}OC*kDqm4uQBfsLLM+hJYc3D|3B}B}ie6k_vX#a1@E@DgewSww{VZ;-|L69_SjA)GM!HK!nh6mlfCWj-5Uf)G;3vo zn2RS#B76J$@0kns%Q1#zn^|B98jaL$uCFB<45Z)hORv{c|7~Yy7wgw0e&E^M0J}QA-5QGH(JUUX@<#Jhu!=b#c)s&>s zXy|>hSd{NclFk*3eFY$>0l-rDIF4g^kjvdsqFa=^C;jKQZ1X@D1TAgQiN}TZ=;SDh1q6CkCwC#3#4s=TBd1gL2WBJi8Jb5TOI5Hy2jZ1*d3q`j zmzM5K!O3OT7f^uqip)mHM!OOQ=jURM4-Vw{#)cH)*zoer`uc4T*m<1k!`m4t36c24 zO_rwe`|wa>1#Fft(=_jO6YoRK7QxdDes5!pMDTn|46)2wfGuC0on1UyTl*ycv22!I e5W{x-5nup#`Ak&HNL}M{P)uFAh=%3-b?vd(vNl%ZDFOH>BS0e$Wa4_9`yP;LH+$iritCmyIb2z&q;RDKR0Yrzf6kPft9a$*jJVN@ZT%4Fun^ zwVj<)5sgl=NjHpy2!|8WvZnrhg{#%OX0dpQtHY>#2`~Ue%pi+G!JMQ300005BzeS3TD@xVaOQZ%Z_x9aQhEgZ&tI9yd%Q`4}nY5S5@mkY1Q#zuZcB15vG zM1)MYEAxws{pDq4zQ6G}iu3cKND@rTLQL0p2^1CZiTExt#w`*+bYH{EclGxf3jdPaciM%`_UH4dYd@+m7@6s;3Lfw(4~TlL0-5s;F{lOW_L3gD=UED^;T2_gU?sj*8Wj_F9!!F zgp!gQByxy_1R*c4S}ZMXJ@6IQw1spuI!);YA@N^;0RZmVHj(I`HCg}w002ovPDHLk FV1j(%MvMRe literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/css.png b/app/assets/images/boxroom/fileicons/css.png new file mode 100755 index 0000000000000000000000000000000000000000..b5769e617bcf1f0ba6457a30311e271e72906a22 GIT binary patch literal 630 zcmV-+0*U>JP)rql58e@ zZ?dylZ4n2)otb^_`{sRb#+aCy0T074#%X$m$H#Xm<&mt_P_Nh34^uB_ex073U8zv8 z%8(K@is`szSx9efP4ZcUUs_SHng>HNsajR2+})vEDxp{`qEINP`Rm-=3gsn;*Qo`} z8j-zWu(Y6WODNPV+I8WQux;D;kjWq(i+y~b$-JY;yb_R20l{RUWbltjJRVne!r?H! zSytY!zztRq5cr=6SE80=w`qX_4=n`8LlHiNIDV2~0S^reLg3HI36>WZQKIHCJ})i7 zIX}lU(?qBt;PZsbr8OW}_Qr9LPNncBkw7F8Q5YQ?!^i+gZ+JU z_w-<3a1gqwef|CD?CQeL!$Y)Cg`DdP=zAxq3Wi5Uklo%!Zg*GNVS$5O4nKBwFfcUK zut2$*ci#d9Uq+*NJ3Wo$>MHEZOW0ReNF|e)oSH(EVutEv(}H<@dWskA?e}gWMi$mL zHXyyH{Aq0s{7QMxao4+r(!~YDQ8o+v_?Vxq={*LOq{Z}J+u{G5%N*tk+PD7R+v~W_ z=br~&luroJ%rL3@CSN1XoXZ^M@~N$aj+?VHr>(bl0Z$mRTy~hl?f5Ie03v@)L^UGB QyZ`_I07*qoM6N<$f{HLDZ2$lO literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/csv.png b/app/assets/images/boxroom/fileicons/csv.png new file mode 100755 index 0000000000000000000000000000000000000000..99eb086e5b84d064988e38b4ea0ae142a2820a36 GIT binary patch literal 702 zcmV;v0zv(WP)HlPxAXa5(3lbMAM)d+xc~gSR&^zt8|p(=GyNz`x~#=XqZ}*M*c4 zx!GBm9BpnYAh!}alpY#-@r#lUtLMR`A}(aHEDPzGnOo#)q)Qw--qr?<73})hLLwCnrd|Y^kZ}~-xbiD%SOek7oKJ)OXHyAW?MNugagGHHqY7ed1L!`0=`(0NcuXnF)5*SVsuc!0 zZDJ4>gRCHczj~k<8qS~Us&?M@InH?oU||r0u0a6R9`G}Mjz4LwIR`C`Clx?yRJ|G! z!^3bL=MMv#wKH2k_h4hGx&O<=#3LG2@a%b&DP+sB`fwO4%gbsEcV0pNVd*F=9#ynB z$){B&Ht|i6#>MBg@bq%-CLh}gJ)vu&G$&B-0 zj1qCii3=C5#HIfb5(tEB#f=+rLAuc3YhJiq0{=pv*W+5{MQE*fUB1w!%o9R@Q zx7tqV=)!5ZeXDMtbL;e}DyEczVHghp?8kAD=YtL@Fe2&(x`$S&$J6o8B-X(4rE|C2J*3Q) zl(Z6|nI=hyFhnq&;oZq6oSvR(ERV6qnu2n<{E_mFU=m_7okJ`Hm?4qN8TcgwgmOV@ z*b@7%f=;J%U3V9X!E!8Qw>{)9XK*dUadS6?d_D^=>qiP=?7D(VrK0s6rgJVht*-xh zJexyu1=Hf-rj5eEr6hK(0dKe4SE|)2fA!+wi7pHV1JUpI=YQ`XV@zWUg#8lD<8#o6 zzZai2j_(P{FrF?Tgjx|bPD+8sle@2J+SfHY2lrPV9336)X(evT0k=#zu7$K`ufp>k z_?c9sHpZ@*$ZNIQcb%a)&I1L`3^pv)8CC|wdQotEp--W-C!j5mCxCzYuVC@)dt`v|# zUbETUpqwEpQbgP>Y+`#a2iK)l4HKVxP?;F(t#*O7<{6D|QQW@ZZ9JxUOWa4x o25Bl}6Q2CcFGThMwC0v0PlkSYa{oO<%8H$7N{BE5L1y(l6o2nvN*ym|3Zg3@ZyfKWFFAy99k zP^;NmOt)rdC%!k?T|F3a;O)2je!h9{%Ztd57lDpf5JHRs*oA+e7h3BNItb8cG*DSw zgk-Pq4?)Xv&&=fH{W=AGHmwm*5-0L;90xPS;ss78TV`KoXb2*Ir8rNn;Z|4n?%sm) z`CgREWsHrC;M~N-MRGqUVnZVRdNBO-)~S{~pS?84NgeR~$kbh`P!pad)I;BO>c>D6k|b6tEal}s#2FWui=w>@r?NvMDc zSTr_C0!msan~-z4hHLrOBw6?R%F5cdfq^Of$I+g*#x*R*F98Ms!x$rPQvL)J00000 LNkvXXu0mjf(~2%= literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/docx.png b/app/assets/images/boxroom/fileicons/docx.png new file mode 100755 index 0000000000000000000000000000000000000000..1beb2e535981e152cf2ecb3bd112815a63d945fb GIT binary patch literal 677 zcmV;W0$TlvP)cFGThMwC0v0PlkSYa{oO<%8H$7N{BE5L1y(l6o2nvN*ym|3Zg3@ZyfKWFFAy99k zP^;NmOt)rdC%!k?T|F3a;O)2je!h9{%Ztd57lDpf5JHRs*oA+e7h3BNItb8cG*DSw zgk-Pq4?)Xv&&=fH{W=AGHmwm*5-0L;90xPS;ss78TV`KoXb2*Ir8rNn;Z|4n?%sm) z`CgREWsHrC;M~N-MRGqUVnZVRdNBO-)~S{~pS?84NgeR~$kbh`P!pad)I;BO>c>D6k|b6tEal}s#2FWui=w>@r?NvMDc zSTr_C0!msan~-z4hHLrOBw6?R%F5cdfq^Of$I+g*#x*R*F98Ms!x$rPQvL)J00000 LNkvXXu0mjf(~2%= literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/dot.png b/app/assets/images/boxroom/fileicons/dot.png new file mode 100755 index 0000000000000000000000000000000000000000..1beb2e535981e152cf2ecb3bd112815a63d945fb GIT binary patch literal 677 zcmV;W0$TlvP)cFGThMwC0v0PlkSYa{oO<%8H$7N{BE5L1y(l6o2nvN*ym|3Zg3@ZyfKWFFAy99k zP^;NmOt)rdC%!k?T|F3a;O)2je!h9{%Ztd57lDpf5JHRs*oA+e7h3BNItb8cG*DSw zgk-Pq4?)Xv&&=fH{W=AGHmwm*5-0L;90xPS;ss78TV`KoXb2*Ir8rNn;Z|4n?%sm) z`CgREWsHrC;M~N-MRGqUVnZVRdNBO-)~S{~pS?84NgeR~$kbh`P!pad)I;BO>c>D6k|b6tEal}s#2FWui=w>@r?NvMDc zSTr_C0!msan~-z4hHLrOBw6?R%F5cdfq^Of$I+g*#x*R*F98Ms!x$rPQvL)J00000 LNkvXXu0mjf(~2%= literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/fla.png b/app/assets/images/boxroom/fileicons/fla.png new file mode 100755 index 0000000000000000000000000000000000000000..152da354d00a0c75e681063903598532cb4e1756 GIT binary patch literal 625 zcmV-%0*?KOP){1A3@7l*Uli`vF!!WCv?Y_BuUL08!W6=t8bR( z=U;$N4D3qTdb_{Bcj^1KI4Amek7(1xgaC#>1Pp)>c(}Vu57yVSU?-QOgIew2s1g|8=H^EC0rfT~Co{lr}cV857`g z24R@FYGLu#h39R3n4NtcG#a0y!C>MoWDk-s%x8L?)-#<;AianKlF+J{U^Wx9^?S5kmH?)00000 LNkvXXu0mjfcvK%v literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/gif.png b/app/assets/images/boxroom/fileicons/gif.png new file mode 100755 index 0000000000000000000000000000000000000000..c485c20168d83587ffcc62b829bd662a4893f852 GIT binary patch literal 562 zcmV-20?qx2P)VTMP9M*lKBG7g7m|jhl^ecg)V68PA=8 zV5z$oE;DnP=RNOvIoGPHie;8!$>r?s?>~@de?k76QxwJE&B4KoMd(L+drzH0YpawT zZa=qB7Pz+9l0Da8;%T>J2XZFO4p(8+16{)I%?iUhT5DCvpF5WhLGl1C~3}aYF=XovAk3Mjvw?#MV*OjlX!+O#PWdaN@G)~TRBU%dxTMOv2 z~Ijn?`7tLAdnl{QtAU6V{c6fA1C++b1^c5$kU)fmO zq_?uc#depLC#9{hfxV`+-(^K!3(V)L5VfbY^@Erb=k+T=MILwdw8CQ@B4&dNRq^)X+pQ#mMw4^(UuUR$VK@IU1jGE>{1;3 zy{bClf=tp=DwB7`bXswITo437UF7+t8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/htm.png b/app/assets/images/boxroom/fileicons/htm.png new file mode 100755 index 0000000000000000000000000000000000000000..f355d31d4fa02dc5f0017bc857d5226ba865874b GIT binary patch literal 702 zcmV;v0zv(WP)fiyflP-Xi^(( z@uRIREr}o)(Js1*YZ26~D_wNuKd^=1LR`2|!6I%fgl_x;S`cc3pdbdU7;K9csxhX0 zNt?VTOBkB zTpo7gs9)~_!t6_+OITXp!M%kd&JKq$)Ggrg2pDdGCvOV4k?>asYujWcX#38dh|#}$0TA>@liWR^?>eLlRM zeUFj&2tMV$z#|e2bd*46-d4k_vR=cwC15rLHZ~hjKeutBe-sO^7jb}MZtg*|DLSX% z=m9H;pX|ds^E=#KL3p~lphOeaRtbI&Ff!1quc7Vi!Dc?+?twmqn^(>wq_)xH^Wb9xBvhE literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/html.png b/app/assets/images/boxroom/fileicons/html.png new file mode 100755 index 0000000000000000000000000000000000000000..f355d31d4fa02dc5f0017bc857d5226ba865874b GIT binary patch literal 702 zcmV;v0zv(WP)fiyflP-Xi^(( z@uRIREr}o)(Js1*YZ26~D_wNuKd^=1LR`2|!6I%fgl_x;S`cc3pdbdU7;K9csxhX0 zNt?VTOBkB zTpo7gs9)~_!t6_+OITXp!M%kd&JKq$)Ggrg2pDdGCvOV4k?>asYujWcX#38dh|#}$0TA>@liWR^?>eLlRM zeUFj&2tMV$z#|e2bd*46-d4k_vR=cwC15rLHZ~hjKeutBe-sO^7jb}MZtg*|DLSX% z=m9H;pX|ds^E=#KL3p~lphOeaRtbI&Ff!1quc7Vi!Dc?+?twmqn^(>wq_)xH^Wb9xBvhE literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/image.png b/app/assets/images/boxroom/fileicons/image.png new file mode 100755 index 0000000000000000000000000000000000000000..c485c20168d83587ffcc62b829bd662a4893f852 GIT binary patch literal 562 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/php.png b/app/assets/images/boxroom/fileicons/php.png new file mode 100755 index 0000000000000000000000000000000000000000..75938d24446c71c24b924b2baf8441da36e84103 GIT binary patch literal 676 zcmV;V0$crwP)k52oNnFA-Bb^iaKc^z5x59*P&iL(zkv^dz*0iYKY1P&{ah2L%O@mTH8? zvapRNk#@72U6WnsZIjIgO6b5aGw;p!y_tFMk^IyYFyS~3L`t*hj=&+sb=^fF5M=L+ zkKemGI+{nnC(y90^8MM_nJrDDg0dIG(*^iTw@0#>Ouka9^#qHO1TU7C7rz&aF9k}A zLdfU`N~OzT?&RG^58i`qHGzs8L}C;&WBo8Nc&v-y2qExlW#t3U^MyvU39N52tshGRZK!*Rec3_li7rckTZOhZFHWxOw{mB-4Xkx4>gHmSuGaewja@*=($;dhyB9(!*zV zVDlhh?fkQC;>X3^4c}+|1y*yd=9$5{iiY<9p#pdBno>>;Rd`5sJ ziypdOM*=nwSx(=`<;K1i3SI^HsX$^XiXgX?#caJ*yUf6#ssJP!O{a6Y$E9+)il@6h z6@S^&xw&^F9-lyxql}0UMLEwU61NWhg=@79N7Fu`yNjy-7hnM4P62M1AxbC!0000< KMNUMnLSTYn88OoU literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/pl.png b/app/assets/images/boxroom/fileicons/pl.png new file mode 100755 index 0000000000000000000000000000000000000000..d398622dfdf12f588d69d9c32914d09e5b2ef1de GIT binary patch literal 568 zcmV-80>}M{P)uFAh=%3-b?vd(vNl%ZDFOH>BS0e$Wa4_9`yP;LH+$iritCmyIb2z&q;RDKR0Yrzf6kPft9a$*jJVN@ZT%4Fun^ zwVj<)5sgl=NjHpy2!|8WvZnrhg{#%OX0dpQtHY>#2`~Ue%pi+G!JMQ30000VTMP9M*lKBG7g7m|jhl^ecg)V68PA=8 zV5z$oE;DnP=RNOvIoGPHie;8!$>r?s?>~@de?k76QxwJE&B4KoMd(L+drzH0YpawT zZa=qB7Pz+9l0Da8;%T>J2XZFO4p(8+16{)I%?iUhT5DCvpF5WhLGl1C~3}aYF=XovAk3Mjvw?#MV*OjlX!+O#PWdaN@G)~TRBU%dxTMOv2 z~Ijn?`7tLAdnl{QtAU6V{c6fA1C++b1^c5$kU)fmO zq_?uc#depLC#9{hfxV`+-(^K!3(V)L5VfbY^@Erb=k+T=MILwdw8CQ@B4&dNRq^)X+pQ#mMw4^(UuUR$VK@IU1jGE>{1;3 zy{bClf=tp=DwB7`bXswITo437UF7+sNhLN#DYidr7guD{6W2V@?44a`HgX z0enAo55-~;7Y7G%c4TCT;CCdfMMU{_U$||o*fi&Fclxn(KLuaszHAk}&mf$za+-wU_&5%bsg>p;uCLmKCJ%rv9=%?$w~D#3H81YaR>{ua#6 zK6*N-J%2OKaf3)K5~obqD-s}M&rPhsGz40cd(e9G#UC%gILD28?MybQ7wY#Cd{2Ft zJ(p}c2UhQiNFXY~^P&zZpo(K4k6*$fcbGRKp a1sDK3SSC8rrcdtx0000sNhLN#DYidr7guD{6W2V@?44a`HgX z0enAo55-~;7Y7G%c4TCT;CCdfMMU{_U$||o*fi&Fclxn(KLuaszHAk}&mf$za+-wU_&5%bsg>p;uCLmKCJ%rv9=%?$w~D#3H81YaR>{ua#6 zK6*N-J%2OKaf3)K5~obqD-s}M&rPhsGz40cd(e9G#UC%gILD28?MybQ7wY#Cd{2Ft zJ(p}c2UhQiNFXY~^P&zZpo(K4k6*$fcbGRKp a1sDK3SSC8rrcdtx0000sC>G)9Y^eZ`RqzMy=YR z@pxDT90IdjmL>h(jVNv=&!Ai`V`_XHx8~;`5cV?>OA_VM)R)HTNCbYrA3_KTs@Ll< z3JRv_qPd*#Cl*K9l@unl7vfSvmHkv=M^eTtV;)S z3nO^ExPj}V5v0;Zd@b$3robt$x0#OA$UfP&(pI3>4-X-zRwlSMos+WPuWh3AZh}a% zD6ap6mm1tQPm@4&2n+(-J|9RabX}JO+R`L4*)38~fuMjOP@9COWp@*p)NPXp22;!O zfEzV1xg?3?8z9-DRXdY-s26s339hccYFO5cbHcOZThn*r9fD2?mi1Qw4SqHpJea*O zw)1E`lX)&PQ^0M#LSFf-oAg)-q2Len4~u4ld9L(jkX-q<95#JhBz)d3p|-aN_4oD% z{OY@kKyR=7U+|bjOTkGbjHJe^MzxAcu_$8!+C&ekRewh&lMsLjxM}PvHBhAo#Z%zB zjA1Ni4Uk9m-QE43p`iu*hq;Ea&lqmU9{~mc&DTY!bgh+500000NkvXXu0mjf78ysd literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/py.png b/app/assets/images/boxroom/fileicons/py.png new file mode 100755 index 0000000000000000000000000000000000000000..d398622dfdf12f588d69d9c32914d09e5b2ef1de GIT binary patch literal 568 zcmV-80>}M{P)uFAh=%3-b?vd(vNl%ZDFOH>BS0e$Wa4_9`yP;LH+$iritCmyIb2z&q;RDKR0Yrzf6kPft9a$*jJVN@ZT%4Fun^ zwVj<)5sgl=NjHpy2!|8WvZnrhg{#%OX0dpQtHY>#2`~Ue%pi+G!JMQ30000gJ)vu&G$&B-0 zj1qCii3=C5#HIfb5(tEB#f=+rLAuc3YhJiq0{=pv*W+5{MQE*fUB1w!%o9R@Q zx7tqV=)!5ZeXDMtbL;e}DyEczVHghp?8kAD=YtL@Fe2&(x`$S&$J6o8B-X(4rE|C2J*3Q) zl(Z6|nI=hyFhnq&;oZq6oSvR(ERV6qnu2n<{E_mFU=m_7okJ`Hm?4qN8TcgwgmOV@ z*b@7%f=;J%U3V9X!E!8Qw>{)9XK*dUadS6?d_D^=>qiP=?7D(VrK0s6rgJVht*-xh zJexyu1=Hf-rj5eEr6hK(0dKe4SE|)2fA!+wi7pHV1JUpI=YQ`XV@zWUg#8lD<8#o6 zzZai2j_(P{FrF?Tgjx|bPD+8sle@2J+SfHY2lrPV9336)X(evT0k=#zu7$K`ufp>k z_?c9sHpZ@*$ZNIQcb%a)&I1L`3^pv)8CC|wdQotEp--W-C!j5mCxCzYuVC@)dt`v|# zUbETUpqwEpQbgP>Y+`#a2iK)l4HKVxP?;F(t#*O7<{6D|QQW@ZZ9JxUOWa4x o25Bl}6Q2Ct8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/rb.png b/app/assets/images/boxroom/fileicons/rb.png new file mode 100755 index 0000000000000000000000000000000000000000..7db6ba0f2f285904deb88670e800e62d812f24db GIT binary patch literal 669 zcmV;O0%HA%P)Ps2Aroo`Zjz~X>3t5D(T^(M%c~{tA%_|}}z%goRvj6eJxchCz^B8g#f!Kg& zcOLG;dUn~fi*gR3iOX>w&+|MEQjEPCN+wRhwBh=Rg-{segl(8`EfZ$B497HJ{;m>g zsFz9*cS!hp?@?;VaZXD_#`N6CIWaamI;c|1zQ`e{7K5ql2!^__@?Zu&!$8l$1JK^Q zhtIOGtSAda#B_6nOcl8EY<6~$f2e%;3@I4!TIR&@dYaf!$A_-%h-qlir7u;l!_c*ZoM;o~vb6%|ro zCyX@(FA9Ym-FAyKBX1MZ2nmTds*|L9XzvD2Qn4H7FI_z1>+C`$lScC6r?-@ReGPb> zrl`^RS+j15Oy9s;v?>_|2l@j9rGiBlF4Rr2W+NzWT-Z&;mKG5TE~=%Pq%n!H zrjyCn+_|1RGilO*2cF5z$vJP{bM7!HCFUNiW8~5`^n~<2*N6=9mN3y5Yx96l9r~J` zNF;!87>k@^jWLuc^^&+jgnR3J_)laq+t}IJN2T%ynr4SJ4T~hC9My;qojR@_rYTP~ z`&`+PY__sMkPF{jGMV56hKAx`%y(j$sU6HbZubc=*=QK-@bFtFHB2lJi^TQ$`O8kP z+JWOhSZ$QbzEchu(-84hzz0w(ZWJOriSsGdNGuGLy*Xh3BwOKt(9TFh0g$ z)|EgvKM=c!Tu#C;Tv635U5oBDN@>s9!rGMd;9SZWjTs*RIt1A-oS$eR65$S^5T|i? zcL&u#EU{e3MPh?7RF6!eO7jLE z`ng6$0?oRKKu~vzHEtuJTQmdrmh6pi^t*WX%g2r5-R-ShDWAP9gm{#Db00rm-*bvc zXx6e?n{?fl6fMbQTlksPbzlpz+}e4y-1Pg=3%-BFQi7#MqnCE z{5&Y_*Y-Ann+xun$U1jIE{TALIy*lM+{%2NzH&9Dz7+F$G`?@&$v$6tLcQhQYg7Vi z*r-RSGorEker3z3E^*F}*}7YdLMY{D}OC*kDqm4uQBfsLLM+hJYc3D|3B}B}ie6k_vX#a1@E@DgewSww{VZ;-|L69_SjA)GM!HK!nh6mlfCWj-5Uf)G;3vo zn2RS#B76J$@0kns%Q1#zn^|B98jaL$uCFB<45Z)hORv{c|7~Yy7wgw0e&E^M0J}QA-5QGH(JUUX@<#Jhu!=b#c)s&>s zXy|>hSd{NclFk*3eFY$>0l-rDIF4g^kjvdsqFa=^C;jKQZ1X@D1TAgQiN}TZ=;SDh1q6CkCwC#3#4s=TBd1gL2WBJi8Jb5TOI5Hy2jZ1*d3q`j zmzM5K!O3OT7f^uqip)mHM!OOQ=jURM4-Vw{#)cH)*zoer`uc4T*m<1k!`m4t36c24 zO_rwe`|wa>1#Fft(=_jO6YoRK7QxdDes5!pMDTn|46)2wfGuC0on1UyTl*ycv22!I e5W{x-5nup#`Ak&HNLQ5eVZYyJh@B!b?7 zBnc8tcgnRf#4&JE8k&P31Q)?K;2Q`Osc6ocnxn$KSz5v+C9dB`eix1sIym^ihkNdG z&UrZJ+;byA5O|x!j<;cKzd(fWd0t|KU7TSL7s%lUkGRJT4m7trM}a$3kXPGsO|+0j zN%=$dh2yO~-$EQ|e5$HN0U!9ri^k;D7k9UAjW)_S!L2hWj6c0coxZT`#qg{`*LEg& zLc$%i^uEw%6RVokN5dJEP&>M!K+y`+xQ$0Vr%0+}2%o%tjZ@{{)o1OP=5#Fag;zao zHTyWmgK}@UQogHPsAqdlX<5mo!?URCaL+Mz^0wMvOx9hr3wn`+fB^u9 Wk3C*ONLQ!;0000#^5Hd*;Wx1}4t-ZZRN+b-AlDP2!czz97vgCybiGc8lOTuDz z*-8biRtpbjX7FHnxytZs3LGhcPe6Qwhb&=gVxRDc9`TE~b$W_svzgBEve9_L?WvsY zV5q8eFgibv+x5Ci{OmZm*x!e3ns9iQwhM&Xu-(QR3JkOls(@vs^IKN}W-8!m#!xVw%9Ffi^Fc`~}Gsj&P-F ujwoH6optk5Q|tJT6?tA)IAX_d0R{k;PXn6%!tHqg0000t8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/tgz.png b/app/assets/images/boxroom/fileicons/tgz.png new file mode 100755 index 0000000000000000000000000000000000000000..6d6333afc059e4866b2a6191662ab1c599d23292 GIT binary patch literal 646 zcmV;10(t$3P)t8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/txt.png b/app/assets/images/boxroom/fileicons/txt.png new file mode 100755 index 0000000000000000000000000000000000000000..ed841a02a7d1bd376af2e50c7bf0032938f7fef7 GIT binary patch literal 592 zcmV-W07YdLMY{D}OC*kDqm4uQBfsLLM+hJYc3D|3B}B}ie6k_vX#a1@E@DgewSww{VZ;-|L69_SjA)GM!HK!nh6mlfCWj-5Uf)G;3vo zn2RS#B76J$@0kns%Q1#zn^|B98jaL$uCFB<45Z)hORv{c|7~Yy7wgw0e&E^M0J}QA-5QGH(JUUX@<#Jhu!=b#c)s&>s zXy|>hSd{NclFk*3eFY$>0l-rDIF4g^kjvdsqFa=^C;jKQZ1X@D1TAgQiN}TZ=;SDh1q6CkCwC#3#4s=TBd1gL2WBJi8Jb5TOI5Hy2jZ1*d3q`j zmzM5K!O3OT7f^uqip)mHM!OOQ=jURM4-Vw{#)cH)*zoer`uc4T*m<1k!`m4t36c24 zO_rwe`|wa>1#Fft(=_jO6YoRK7QxdDes5!pMDTn|46)2wfGuC0on1UyTl*ycv22!I e5W{x-5nup#`Ak&HNLgJ)vu&G$&B-0 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 QyZ`_I07*qoM6N<$f{HLDZ2$lO literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/fileicons/xvid.png b/app/assets/images/boxroom/fileicons/xvid.png new file mode 100755 index 0000000000000000000000000000000000000000..c8bd25903d8b637cc9268a4d148e7a7c19323e5e GIT binary patch literal 654 zcmV;90&)F`P)gJ)vu&G$&B-0 zj1qCii3=C5#HIfb5(tEB#f=+rLAuc3YhJiq0{=pv*W+5{MQE*fUB1w!%o9R@Q zx7tqV=)!5ZeXDMtbL;e}DyEczVHghp?8kAD=YtL@Fe2&(x`$S&$J6o8B-X(4rE|C2J*3Q) zl(Z6|nI=hyFhnq&;oZq6oSvR(ERV6qnu2n<{E_mFU=m_7okJ`Hm?4qN8TcgwgmOV@ z*b@7%f=;J%U3V9X!E!8Qw>{)9XK*dUadS6?d_D^=>qiP=?7D(VrK0s6rgJVht*-xh zJexyu1=Hf-rj5eEr6hK(0dKe4SE|)2fA!+wi7pHV1JUpI=YQ`XV@zWUg#8lD<8#o6 zzZai2j_(P{FrF?Tgjx|bPD+8sle@2J+SfHY2lrPV9336)X(evT0k=#zu7$K`ufp>k z_?c9sHpZ@*$ZNIQcb%a)&I1L`3^pv)8CC|wdQotEp--W-C!j5mCxCzYuVC@)dt`v|# zUbETUpqwEpQbgP>Y+`#a2iK)l4HKVxP?;F(t#*O7<{6D|QQW@ZZ9JxUOWa4x o25Bl}6Q2Ct8dlN(uy!IdDAQ~IVACN-5YVedo&+Ue&XwUA!f>?-dmFjsWsHoEo9RXvhUZ}r zKx>WDqa!%{!a?KMPN}r@hZcK;Pt__`mX^?LHt~9K5uWE^$93QGUWD(=c|J1(#cB~O zQb*A12{M@s(&@AawA*cTIvsp0m$xYI6>*;#ODZ681dF8=JrlMqi!2b8hgPeF_q)5u zPfdL|IX+(aMvfQ`?{37HtV9Mw0*+G1=kro`DwV=#&--a|9Dg8SS@#5LwVI3%90%}y z_*5bXajZcJv=E_R0)uORIX5Rsj}i$HVJEx^=Ng{FkD(GM3A7V8vAVe_qv;8U{ZBv* zN{9&<2c&`**{n>~>-C|=+BEwA5s)58AL!%j;h~UM*4Gh6cZEf45+_cq2hq6``VLqm zo6Dh4D4;?DrY{UBfpQIRxCAECZk)Kkwua76TYU#K=IGqn z!MoK}X>8mD=Q4-6>ipmUt*a|^Z*LuQ>%>8$Ar8uw3e4#kan5B9b2*ZPCr_uR%Q_a+ zIPJQwE*6WLelATvnf~qj`YsGL=Q4-6Eclo(mYkTFRmtQCVsUqNWPZuh2p gaq)#-fBY3-0Fu)?c2Bz)mjD0&07*qoM6N<$f{v~nnE(I) literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/folder.png b/app/assets/images/boxroom/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c7a9766e2bad401f8edd03745844feccdaa091 GIT binary patch literal 28239 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/group.png b/app/assets/images/boxroom/group.png new file mode 100755 index 0000000000000000000000000000000000000000..a6aae0404b7313fec54b1c279092a83e25ccae18 GIT binary patch literal 870 zcmV-s1DX7ZP)z8AVPO#zQWo{c3Jan?K~X_re+q=6pZ)7+fBI2GP>~cBS`z2F+n4O9Z}$2}pv8inwC0c6C}x%bhWWuAL3e6@2ld?`Ig$$l)LG&jCYI z#PKr(S0EB8%rRSpO?&njwRp@A2O~5J%jyalPcqsW$5RB6nGnR@)2CrD8lke@wV3Cy zh2_Vz0)Z~6X8yT`*4F**x;o1{k4K((=Z(b^h)Oyglz1G1C?c+Hi$^0{er;Z$-T8Ot z&py))y{9^?E~gbkpKaEzL*B4aM09L;81{+^XqLqqN)~E_thesv4qKEA+`8#)+P|bxs9208Y<(i`t--~lA>VF z#1Bh_vkK4qZqirXH6L?B$wxCKCQKFQf+w{HK?#R^b!NyYW~xgn-A(L5h45@3^)fvA zO*+xz)Yr6Br4$#Y+p2AaTiOer<^&SqJi(4E{lb53x{N`62OgP@K&2$WeYmkcmL|4W zuLt~ecXvg>YKM6$k-*uGqqsfgLFuYulgnIQYoNL{-vX23k~yHw{iee^gYqnCY;Hj$ z8WZo2w7wtrNs~!-CbBF;vl`S?MyfWKxoK2sBYbz?3UK0ngCx`(>vZp185t_JOESh4 z3xfHTp{o!3JxR_NYZ>+q3~ummqiIzSMK#cLHV328Cxkz6=S=7N^6sW8>sm{Gp5*gQ z(zoWJWsS(z1bD4Kfv>HBD3|Lc3|gOk^n4W7(Fi8j~T&Gp$#cK$6bZqsVPAen@bsEQsxyf5hN w=tUZt1dU8Awm|+2g$4hIto?EM$6o>r001#WaFpb2r~m)}07*qoM6N<$f&n$2*#H0l literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/group_add.png b/app/assets/images/boxroom/group_add.png new file mode 100644 index 0000000000000000000000000000000000000000..de1ef7b6beabfdfe2576970c2980cb41d5480a89 GIT binary patch literal 845 zcmV-T1G4;yP)X&Ce&V=Cm_6eeXFa+Xe~5i;A@_7M|1BKB}ef4moG3E6hNd9i?($36pr)^ zI_gLhrPZ#quQ5Zs8Ij6Nt!B9ir2AHsMcUH1-(D|$a5dk$-tp~6Kg~4mUPM{# zYMQ2TY4ZuJ>O6oM-2ifAxvU{x{JH1btLy0*kiCx`J-U6h4C5nZj=xI^hhmw(;7Y$uOw)=mnF$m@61q2i;vWoY|jl0&e+UB&WdN0HH@&(a_I9h>ZEQ7Gq zlt48<-uD8vPhCq6YO*|6A|c76$~Lb5MwhU?vrC~*_qDL&!(n+FSd>GGUUVc{h1B8S XvUWbv%V7`=00000NkvXXu0mjfNEUrd literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/group_grey.png b/app/assets/images/boxroom/group_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..644dc3d9ccc89af7f4dcbe5ca0afaf6dec2ad99f GIT binary patch literal 745 zcmVVi&|g_ zqk^IAX1kfNtAVh){SUj@MHkwIgMu;G%`Sooju5*LH(Rkv!tzI7YMQy{t=aGGh4Y>F zocEmPd(QK|T%*yT|1FQlqv1F%2)m8iBrF&Tg*KW!!sg~*zE~^F=n#&KjL2Bb^3D1S3_!C>EEd1Sp&s^2BoYYRLE$zf&F}_qP6_KY(r9wiXkPs7o z(udynR{)TB5C{a+27`fyhKAG=6BBt97gtwTNvG2h^N3lWmVMYd)xhWTO=32q$z&pS zGt9TOwcYIL>EXiR@b^R_LCi}e5`w8t+Ru$puh#_*ha(A!_QS(NVrIA7S2{a8vxP!I zsn_eNTrM|UF4qdr^Pidk-1&DfGc)sUc6RnfD;9vN3kwU;(a})@05nJ)>j0J;(P;D| zz*Ybp8`xz zPxnJ@_xAQSXEvKDm&=J#sZ>0Z$^5QXtCY{@iTS@jlBiUwyL)?k4sL90OaZZ`AWZN0 z`1li`YS^8FB7t@;5{bm155Kv&`31N0s8*{0Gb$xyPGw1bsUpbx5r%?M2=98e1 z+-^4;fx5f93G-LM2(kLLTJ1gd%GkP07b1&s1HI58j(zg1_hiRe zES5KFwfdnzASk1VZ)s^M?eFh@lgVWFL7h>R0bIJZwe@v*dHL-X03_I>P+mT`Cf)&q b?B)C)`{*{UKo$&_00000NkvXXu0mjfa|2|D literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/information.png b/app/assets/images/boxroom/information.png new file mode 100755 index 0000000000000000000000000000000000000000..5d353a191098dee1e1e8f4bdf99c7f4bccb0f292 GIT binary patch literal 2267 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/logo.png b/app/assets/images/boxroom/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5ba0687ef35574f0901359c2a7be909d36e51fda GIT binary patch literal 4714 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/move.png b/app/assets/images/boxroom/move.png new file mode 100755 index 0000000000000000000000000000000000000000..d59a9bdab6aeffe6db2fb72af4cc571d6d23c3f2 GIT binary patch literal 661 zcmV;G0&4wQ4s$2WkU^^Cg~xX zKo2=6rMdJXl+sfyz1dO_JoF#%Z%}NYii#8uB3ODX?5&p`wHPQwC|;V25NJ<|`Q2=~ zNt$$%yg2i^o2*sDf!TTNy!qyvZ=UvWV*_lOrahn#!@n&fr972`0fxgN^4TnOKCn@Y z@21n+HYI!PQer?kH=oPpuzPUun0t|*@V}YGMF>W7T$5bX>nXRpU3A)Qv|24Rn@tt> zHaEBF-hHaQ<5JQGRP30>(1N~xpKnaM-$$Q->-9R`8wOIdvmf3W#xu&iPyliaAh=j4 z82kZ}N~P2|!C(*{b2)Rwpzmei0r(pTGf_txcRT_mu8-g~FUUzgj~40Dvu)A4yo7Dr z5Deh>)d)hs9mRD9x#9PtTC3sF@-i&boVaMW?^%rojWA{)Mhpl9M7K%ZYNm-|rGm=Y z8M?HY0-e7a#+&5o>J!pLcmb3KN{1^+5hlT=CMPkINI-X(x%O>;|7BunDXsI)uQOn& z7^H4lC{?TYUMS#asRWDO!}`h!reiVOURZce%|8D=0yCghxgb-a5W>XZE-{ew|Lx9> zDh|)hy`sCHCIW#k;=EV{gOHE5Fp92_4*gg(ilt6bp6(NXlAR^w4~)YV6kU8h1_*Wshcce#dZ vxodztYL&~4o5|#B{D)EZ2G{VT{3XBuENdK+QzK+X00000NkvXXu0mjfb8j); literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/permissions.png b/app/assets/images/boxroom/permissions.png new file mode 100755 index 0000000000000000000000000000000000000000..7e47c799a828cdfcc380fe7ef5f517578eebfdbc GIT binary patch literal 636 zcmV-?0)zdDP)QN8gOK%<&1wp*%p&$rqLq(COpa&JI2DHYe zF~(#QH_7g1d=s+T#EJn2K6vwX-#5Q`GsC2m_@5!>&TCx>jWZZJ_Awwy)8S4!Ps^n1 zO+5Eo-3y9dM*^~hnK(Uqp3Nw&%bx>(JE1!mUf$UIVTRhCsFhr4h-D;g5?imUMx$N-9=4^-cXFG=@+| zVtu5uE{QRQY`$gI$`uajHcZ+;u>$BJx8c}TZadOU#2KQLr&hHGo4=q124`FkJDWyX#6GoJ2!F51%Ge0EBL;g#2D80Q+@6}ge(vxO2hDmNO^4KQ zP%18BK!>%?0F=U}!2Hc?Gi#ngCY^@h96|`%4u|i1D3u&&1Zjk*SO+4}(BF^IN+#j< z5De&5MS#@=?;o!+pnV5Dko^)+jSQM7mh!)DmY>LuCCGl3 zA!Trbu9V0YB(6MSII#;GtnqR9O&1;!ctqe4!Qh|=oeB-(x0Z{D-F*w>%CgFpO}sC$=wPF^~A67v=vI)p|95vV8;(V-=z9}_8vwN$b&WnB|R z+@EIB{hOWH?fu4`GRYSnzHjDx-sk=0s+ya@U>e79emdOP-QMWBzMyLw^7%Z(%BCjd za=Gn*BJgkBMo7HNZ@pv5f1_#mN^Ywk5p1%BTW(FdQw8J=4 zz%VAlP7=A$+G>;}NfKTJgR=4b@ktOGrMV3}b7j$Y%Y>(I#$$XwzVZ{nXP=R!j{?Z& zujBtjEOci)#ur%{@~VP;8ykoe7jGp^4*6{*z?@Q{9HAh`)gZZ!?PamLfJ2K$rdPsl!E?8)Af_q|+HBss0UWq^4?E zBvD0{p{goMT`nB-cu;?)3B!*bqPq43s%xrnvf*yW%RATBMTf&-M7P;&=mm2T88 zo<85^E-Np0MAp`@vb>B>!JskX3a=Ng110G%~ literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/spinner.gif b/app/assets/images/boxroom/spinner.gif new file mode 100755 index 0000000000000000000000000000000000000000..bdcb518826bbbf41d7c2519841ae93f83bf269aa GIT binary patch literal 1609 zcmZ?wbhEHb6krfwxT?%>>eQ(}fBy9J^xV65Z|2OI-@kufvSdkld3kqt_s*R=D=RCv zY}s=1dZUcP*J{rdGohYqb?z53X(V;eSX z*tTuk-o1MlEn2jG{rba)4<9{x^u>!8Cr+IB|NlP&4S?c*Za>$MU}whwS0g^nUk#C56lsTcvPQUjyF)=hTc$kE){7 z;3~h6MhI|Z8xtBTx$+|-gpg^Jvqyke^gTcyO5{G?PXAR8pCucQE0Qj%?} z|N*N_31y=g{z^KqGH`dE9O4m2Ew6p}7VPvEWRH0j3nOBln zp_^B%3^TzcwK%ybv!En1KTiQI4yi0i)elN7&Mz%WP6aAghG_#1^~@_SNz6-5h1r`4 zv=^$^6t7+@=c3falKi5O{QMkP3JA!^FUc>?$S+WE4mMQ?&&*57FE0i~4#cl6sYRJ( zsVQzn`MC<<(6!3MV!H{@zxaI((VId}i0K0zqYp|NNJ#}I1WbP*COqK-Iq>U%h}$>=IY|? zIcxT8M}$>O3~UTM3@kvaFbjh<01C4&%>V!Z literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/tick.png b/app/assets/images/boxroom/tick.png new file mode 100755 index 0000000000000000000000000000000000000000..a7d7a96be3f2282a62e3c0733bac89c7f6de7b4a GIT binary patch literal 582 zcmV-M0=fN(P)tYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^az8K(fJ0K@xl?-_o)`1bna-SH57SeZUButL)d$cI_) zkpl5Q!w#U+YtHUCagF&eebP3jhEB literal 0 HcmV?d00001 diff --git a/app/assets/images/boxroom/user.png b/app/assets/images/boxroom/user.png new file mode 100755 index 0000000000000000000000000000000000000000..274d6b920d4dcaa7802f0b2dea08398464c8519c GIT binary patch literal 712 zcmV;(0yq7MP) zO!FFw6l+v!{0Ti&36zpjyco0x5iE!X1igr-UcI#9#aj;^LoYoS#d_#bF-V~iX^QsG zHmUj5k~S~Nd(OPXv}sNbe7mzV-^|X=?g}6o@esPv5Tg-xvxo?(p}OgvQo&5MioJiF z_df|+fbh_R7U^~2nY*Xwaql^wM=2{ve2wSx$<*|ZBwiioiixe3cyGHMq4RE!C%L<8 z&gb)R^_}hfzM2XVcaHq)N67|*KtnTzG6kGk+gLZZb#>uZB!X+h!(bcxj`Kycv894Q z_78A#VgfE%)(t0JF0hTb0a`|zj$jd663A)=dT^ywI<%6FxN=JmET)zO9XzBqwiUa> zYGyr?$|yF|p|7b2C#ZsBu=4yiI=@poH~@q7qrRgM2NeMZE`_j7yO4g@TZTW8iM@z@ z>eP2hXJX|MMR>Vx!WWg+?y`O&ur6LM(!(LeLf|T?=F`;Npw@lCcFFJ86BJiBZP$y- z!66{204o%aeL|HV-6Nq^0R{Ug@Cjuelc%QcOM7aYGjIly#e(*gN?XtPy{qYk1)Bz3 zB&3Mrc6AtF~rqnwi*-LPaHoAecRg1fo4<4?$pQ6cr&oM1e%Tbc3tVf$G!JE-#Pc5a}NdBj1&b%h%=D_xPaH1==m!Gc!}4I zEx{TALcnC=U>*{zwaDr0INQ*0ytuTqBp3|l1^oUn=G|KYK6)`7!J8dWpPrfN^e-<9 zi;Ihb$zpL5ZYM2!Y)|~vI4KNIA$w;_HCN)W>D+IR#MO_D+3N}^z^{%ow$?#yPtfyS zGG@6-?GTn_og;fOB_*rY5$wSUZpP|qk}wt#1oTB$Xp#i0p^y%;Cmiu@*RvW*Xkb@H za`^f3ymWx$1wrq4z%OIYgSB1cJmeBVuO*Cs;p4%#FeEgCmn(xbbpcYosAoym2TxpZRMPvwG)+EI$P1Q+gz&O@8t#F)T* z9C#VDaPyOn!*@_sr_E?K13{$fitTYb=J=VqOD9^&A;eb0CKdYvbku5OTlW`OicVGJ z9wPk-i23koVM+JCQ2SxcdmC~@t93 + fadeout '#notice' + fadeout '#alert' + +jQuery -> + $('.back_link').on 'click', (e) -> + e.preventDefault() + show_element '#files_and_folders' + + $('.permissions_link').on 'click', (e) -> + e.preventDefault() + show_element '#permissions' + + $('.clipboard_link').on 'click', (e) -> + e.preventDefault() + show_element '#clipboard' + + $('.emails_to_share_with').on 'change', (e) -> + update_counter e.target + + $('.emails_to_share_with').on 'keyup', (e) -> + update_counter e.target + +fadeout = (el) -> + $(el).delay(3000).fadeOut('slow') + +show_element = (el) -> + el = '#files_and_folders' if $(el).is(':visible') + + elements = ['#files_and_folders', '#permissions', '#clipboard'] + elements.splice elements.indexOf(el), 1 + hide_elements elements + + $(el).slideDown('slow') + $("#{el}_link").removeClass('folder_menu').addClass('highlight') + +hide_elements = (elements) -> + for element in elements + $(element).slideUp('slow') if $(element).is(':visible') + $("#{element}_link").removeClass('highlight').addClass('folder_menu') + +update_counter = (el) -> + $('#counter').html el.value.length + $('#counter').css 'color', if el.value.length > 255 then '#F00' else '#000' diff --git a/app/assets/javascripts/boxroom/files.js.coffee b/app/assets/javascripts/boxroom/files.js.coffee new file mode 100644 index 0000000..236d074 --- /dev/null +++ b/app/assets/javascripts/boxroom/files.js.coffee @@ -0,0 +1,28 @@ +jQuery -> + $('#new_user_file').fileupload + dataType: 'script' + add: (e, data) -> + $('#user_file_attachment').prop('disabled', true) + file = data.files[0] + folder = $('#target_folder_id').val() + $.getJSON "/file_exists?name=#{encodeURIComponent(file.name)}&folder=#{encodeURIComponent(folder)}", (exists) -> + data.context = $(tmpl("template-upload", file).trim()) + $('#progress').append(data.context) + if exists + data.context.find('.spinner').hide() + data.context.find('.failed').show() + data.context.find('.percentage').hide() + data.context.find('.exists_message').show() + $('#user_file_attachment').prop('disabled', false) + else + data.submit() + progress: (e, data) -> + if data.context + progress = parseInt(data.loaded / data.total * 100) + data.context.find('.percentage').html("#{progress}%") + if data.loaded == data.total + data.context.find('.spinner').hide() + data.context.find('.tick').show() + stop: (e) -> + folder = $('#target_folder_id').val() + window.location.href = "/folders/#{folder}" diff --git a/app/assets/stylesheets/boxroom/application.scss b/app/assets/stylesheets/boxroom/application.scss new file mode 100644 index 0000000..2d49a62 --- /dev/null +++ b/app/assets/stylesheets/boxroom/application.scss @@ -0,0 +1,88 @@ +/* +*= require_self +*= require_tree . +*/ + +html, body { height: 100%; } +body { padding: 0px; margin: 0px; background-color: rgb(225,225,225); } +body, input { font-family: Arial; font-size: 13pt; } +input { padding: 3px; border: 1px solid rgb(210,210,210); } +input[type='button'], input[type='submit'] { background-color: #EEE; padding: 5px; } +input[type='file'] { border: 0; } +textarea { border: 1px solid rgb(210,210,210); width: 700px; padding: 10px; font-family: Arial; font-size: 10pt; } +img { border: 0; } +h1 { font-size: 20pt; text-transform: uppercase; } +h3 { font-size: 13pt; margin-bottom: 0; } +a { color: #BA303A; text-decoration: none; } +a:hover { text-decoration: underline; } +table { border-collapse: collapse; } +th { text-align: left; font-size: 11pt; } +td img { padding: 0; } + +#container { min-height: 100%; position: relative; } +#header { background-color: rgb(0,39,77); color: #FFF; border-bottom: 2px solid rgb(8,58,127); min-height: 70px; } +#menu { background-color: rgb(0,24,49); border-bottom: 4px solid #BA303A; } +#content { position: relative; width: 850px; margin: 10px auto 0 auto; padding: 15px 25px 15px 25px; background-color: #FFF; border-radius: 8px; border-right: 1px solid #CCC; border-bottom: 1px solid #AAA; } +#footer { position: absolute; bottom: 0; width: 100%; background-color: rgb(0,24,49); color: #FFF; border-top: 4px solid #BA303A; } +#footer_spacer { height: 70px; } + +#progress p span.status { margin-left: 3px; } +#progress p img { vertical-align: middle; margin-bottom: 4px; } + +.menu { margin: 0; padding: 10px 15px 7px 15px; color: #FFF; } +.menu a, .footer a { color: #FFF; } +.footer { margin: 0; padding: 15px; text-align: center; } +.footer a { text-decoration: underline; } +.table_header td { font-weight: bold; } +.even td, .odd td, th { padding: 12px 7px 7px 7px; } +.even { background-color: #EEE; } +.odd { background-color: #FFF; } +.text_input { width: 280px; } +.user_welcome { float: right; margin-right: 15px; } +.user_welcome a { color: #FFF; text-decoration: underline; } +.user_groups { margin-right: 200px; display: block; } +.user_groups label { margin-right: 15px; } +.user_name, .user_expiration { min-width: 180px; } +.user_email { min-width: 370px; } +.group_name, .clipboard_item { min-width: 483px; } +.file_name { min-width: 250px; max-width: 400px; overflow: hidden; } +.file_name a, .shared_file a { color: #000; text-decoration: underline; } +.file_size { min-width: 100px; } +.disabled { color: #999; } +.breadcrumb { margin-bottom: 5px; padding: 4px; font-size: 11pt; background-color: #F6F6F6; border: 1px solid #DDD; border-bottom-color: #AAA; border-right-color: #CCC; display: inline-block; } +.nowrap { white-space: nowrap; } +.permission_column { width: 75px; text-align: center; } +.permissions_button, .back { display: inline-block; margin-top: 15px; } +.clipboard_empty { margin-top: 35px; } +.clipboard_info_image { float: left; margin: 3px 15px 15px 0; } +.button_to, .button_to div { display: inline; } +.emails_to_share_with { height: 39px; width: 755px; } +.share_message { height: 80px; width: 755px; } +.shared_file { display: inline-block; min-width: 400px; padding: 15px; background-color: #EEE; } +.shared_file img { margin-right: 8px; } +.share_link_emails { font-size: 11pt; } +.comma_seperated, .optional { position: relative; top: -1px; left: 3px; font-size: 10pt; } +.char_counter { float: right; margin: 5px 75px 0 0; font-size: 10pt; } +.translation .missing, .red { color: #F00; } + +.error, .notice { position: absolute; top: 0; left: 0; width: 100%; text-align: center; } +.error { background-color: rgba(255,0,0,0.8); } +.notice { background-color: rgba(0,165,0,0.7); } + +.error p, .notice p { display: inline-block; font-size: 16pt; color: #FFF; margin: 0; padding: 10px 0 10px 45px; background-repeat: no-repeat; background-position: 0 5px; } +.error p { background-image: url(exclamation.png); } +.notice p { background-image: url(information.png); } + +span.field_with_errors input, span.field_with_errors textarea { background-color: #FDD; } +#errorExplanation { width: 500px; border: 2px solid #c00; padding: 7px 7px 15px 7px; margin: 10px 0 10px 0; background-color: #f0f0f0; } +#errorExplanation p { padding-left: 8px; } +#errorExplanation h2 { text-align: left; font-size: 14pt; font-weight: bold; padding: 5px 5px 5px 15px; margin: -7px; margin-bottom: 7px; background-color: #c00; color: #fff; } +#errorExplanation ul { margin: 0; } +#errorExplanation ul li { list-style: square; } + +.folder_menu, .user_menu, .group_menu { padding: 7px 14px 7px 14px; } +.folder_menu a, .user_menu a, .group_menu a { outline: none; } + +.highlight { display: inline-block; margin: -7px 0 -7px 0; padding-left: 14px; background-color: #BA303A; border-radius: 15px; } +.highlight span { display: inline-block; height: 27px; padding: 7px 14px 0 0; } +.highlight a { text-decoration: none; color: #FFF; outline: none; } diff --git a/app/controllers/boxroom/admins_controller.rb b/app/controllers/boxroom/admins_controller.rb new file mode 100644 index 0000000..a8cb67a --- /dev/null +++ b/app/controllers/boxroom/admins_controller.rb @@ -0,0 +1,28 @@ +module Boxroom + class AdminsController < ApplicationController + skip_before_action :require_admin_in_system, :require_login + before_action :require_no_admin + + def new + @user = User.new + end + + def create + @user = User.new(permitted_params.user) + @user.password_required = true + @user.is_admin = true + + if @user.save + redirect_to new_session_url, :notice => t(:admin_user_created_successfully) + else + render :action => 'new' + end + end + + private + + def require_no_admin + redirect_to new_session_url unless User.no_admin_yet? + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/application_controller.rb b/app/controllers/boxroom/application_controller.rb new file mode 100644 index 0000000..3a96328 --- /dev/null +++ b/app/controllers/boxroom/application_controller.rb @@ -0,0 +1,78 @@ +module Boxroom + class ApplicationController < ActionController::Base + protect_from_forgery + + before_action :require_admin_in_system + before_action :require_login + + helper_method :clipboard, :current_user, :signed_in?, :permitted_params + + protected + + def clipboard + session[:clipboard] ||= Clipboard.new + end + + def current_user + @current_user ||= User.find_by_id(session[:user_id]) + end + + def signed_in? + !!current_user + end + + def permitted_params + @permitted_params ||= PermittedParams.new(params, current_user) + end + + def require_admin_in_system + redirect_to new_admin_url if User.no_admin_yet? + end + + def require_admin + redirect_to :root unless current_user.member_of_admins? + end + + def require_login + if current_user.nil? + user = User.find_by_remember_token(cookies[:auth_token]) unless cookies[:auth_token].blank? + + if user.nil? + reset_session + session[:user_id] = nil + session[:return_to] = request.fullpath + redirect_to new_session_url + else + user.refresh_remember_token + session[:user_id] = user.id + cookies[:auth_token] = user.remember_token + end + end + end + + def require_existing_target_folder + @target_folder = get_folder_or_redirect(params[:folder_id]) + end + + def require_create_permission + unless current_user.can_create(@target_folder) + redirect_to @target_folder, :alert => t(:no_permissions_for_this_type, :method => t(:create), :type => t(:this_folder)) + end + end + + %w{read update delete}.each do |method| + define_method "require_#{method}_permission" do + unless (method == 'read' && @folder.is_root?) || current_user.send("can_#{method}", @folder) + redirect_folder = @folder.parent.nil? ? Folder.root : @folder.parent + redirect_to redirect_folder, :alert => t(:no_permissions_for_this_type, :method => t(:create), :type => t(:this_folder)) + end + end + end + + def get_folder_or_redirect(id) + Folder.find(id) + rescue ActiveRecord::RecordNotFound + redirect_to Folder.root, :alert => t(:already_deleted, :type => t(:this_folder)) + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/clipboard_controller.rb b/app/controllers/boxroom/clipboard_controller.rb new file mode 100644 index 0000000..03ae974 --- /dev/null +++ b/app/controllers/boxroom/clipboard_controller.rb @@ -0,0 +1,77 @@ +module Boxroom + class ClipboardController < ApplicationController + before_action :require_existing_item, :except => :reset + before_action :require_existing_target_folder, :only => [:copy, :move] + before_action :require_target_is_not_child, :only => :move + before_action :require_create_permission, :only => [:copy, :move] + before_action :require_read_permission, :only => [:create, :copy, :move] + before_action :require_delete_permission, :only => :move + + # @item is set in require_existing_item + def create + clipboard.add(@item) + redirect_to folder_url(params[:folder_id]), :notice => t(:added_to_clipboard) + end + + # @item is set in require_existing_item + def destroy + clipboard.remove(@item) + redirect_to folder_url(params[:folder_id]) + end + + def reset + clipboard.reset + redirect_to folder_url(params[:folder_id]) + end + + def copy + paste :copy + end + + def move + paste :move + end + + private + + # @item is set in require_existing_item + # @target_folder is set in require_existing_target_folder + def paste(action) + @item.send(action, @target_folder) + clipboard.remove(@item) + redirect_to folder_url(params[:folder_id]) + rescue ActiveRecord::RecordInvalid + redirect_to folder_url(params[:folder_id]), :alert => t("could_not_#{action}", :type => t(params[:type])) + end + + def require_existing_item + if params[:type] == 'folder' + @item = @folder = Folder.find(params[:id]) + else + @item = UserFile.find(params[:id]) + @folder = @item.folder + end + rescue ActiveRecord::RecordNotFound + redirect_to folder_url(params[:folder_id]), :alert => t(:already_deleted, :type => t("this_#{params[:type]}")) + end + + def require_target_is_not_child + if params[:type] == 'folder' + if @folder == @target_folder || @folder.parent_of?(@target_folder) + redirect_to folder_url(params[:folder_id]), :alert => t(:cannot_move_to_own_subfolder) + end + end + end + + # Overrides require_#{method}_permission in ApplicationController. + # Check if @folder can be read or deleted and redirects to the + # current folder (identified by params[:folder_id]) if not. + %w{read delete}.each do |method| + define_method "require_#{method}_permission" do + unless current_user.send("can_#{method}", @folder) + redirect_to folder_url(params[:folder_id]), :alert => t(:no_permissions_for_this_type, :method => t(method), :type => t("this_#{params[:type]}")) + end + end + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/files_controller.rb b/app/controllers/boxroom/files_controller.rb new file mode 100644 index 0000000..fafb513 --- /dev/null +++ b/app/controllers/boxroom/files_controller.rb @@ -0,0 +1,64 @@ +module Boxroom + class FilesController < ApplicationController + before_action :require_existing_file, :only => [:show, :edit, :update, :destroy] + before_action :require_existing_target_folder, :only => [:new, :create] + + before_action :require_create_permission, :only => [:new, :create] + before_action :require_read_permission, :only => :show + before_action :require_update_permission, :only => [:edit, :update] + before_action :require_delete_permission, :only => :destroy + + # @file and @folder are set in require_existing_file + def show + send_file @file.attachment.path, :filename => @file.attachment_file_name + end + + # @target_folder is set in require_existing_target_folder + def new + @file = @target_folder.user_files.build + end + + # @target_folder is set in require_existing_target_folder + def create + @file = @target_folder.user_files.create(permitted_params.user_file) + render :nothing => true + end + + # @file and @folder are set in require_existing_file + def edit + end + + # @file and @folder are set in require_existing_file + def update + if @file.update_attributes(permitted_params.user_file) + redirect_to edit_file_url(@file), :notice => t(:your_changes_were_saved) + else + render :action => 'edit' + end + end + + # @file and @folder are set in require_existing_file + def destroy + @file.destroy + redirect_to @folder + end + + def exists + @folder = Folder.find(params[:folder]) + + if current_user.can_read(@folder) || current_user.can_write(@folder) + @file = @folder.user_files.build(:attachment_file_name => params[:name].gsub(RESTRICTED_CHARACTERS, '_')) + render :json => !@file.valid? + end + end + + private + + def require_existing_file + @file = UserFile.find(params[:id]) + @folder = @file.folder + rescue ActiveRecord::RecordNotFound + redirect_to Folder.root, :alert => t(:already_deleted, :type => t(:this_file)) + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/folders_controller.rb b/app/controllers/boxroom/folders_controller.rb new file mode 100644 index 0000000..7587a3e --- /dev/null +++ b/app/controllers/boxroom/folders_controller.rb @@ -0,0 +1,89 @@ +module Boxroom + class FoldersController < ApplicationController + before_action :require_existing_folder, :only => [:show, :edit, :update, :destroy] + before_action :require_existing_target_folder, :only => [:new, :create] + before_action :require_folder_isnt_root_folder, :only => [:edit, :update, :destroy] + + before_action :require_create_permission, :only => [:new, :create] + before_action :require_read_permission, :only => :show + before_action :require_update_permission, :only => [:edit, :update] + before_action :require_delete_permission, :only => :destroy + + def index + redirect_to Folder.root + end + + # Note: @folder is set in require_existing_folder + def show + end + + # Note: @target_folder is set in require_existing_target_folder + def new + @folder = @target_folder.children.build + end + + # Note: @target_folder is set in require_existing_target_folder + def create + @folder = @target_folder.children.build(permitted_params.folder) + + if @folder.save + redirect_to @target_folder + else + render :action => 'new' + end + end + + # Note: @folder is set in require_existing_folder + def edit + end + + # Note: @folder is set in require_existing_folder + def update + if @folder.update_attributes(permitted_params.folder) + redirect_to edit_folder_url(@folder), :notice => t(:your_changes_were_saved) + else + render :action => 'edit' + end + end + + # Note: @folder is set in require_existing_folder + def destroy + target_folder = @folder.parent + @folder.destroy + redirect_to target_folder + end + + private + + # get_folder_or_redirect is defined in ApplicationController + def require_existing_folder + @folder = get_folder_or_redirect(params[:id]) + end + + def require_folder_isnt_root_folder + if @folder.is_root? + redirect_to Folder.root, :alert => t(:cannot_delete_root_folder) + end + end + + # Overrides require_delete_permission in ApplicationController + def require_delete_permission + unless @folder.is_root? || current_user.can_delete(@folder) + redirect_to @folder.parent, :alert => t(:no_permissions_for_this_type, :method => t(:delete), :type => t(:this_folder)) + else + require_delete_permissions_for(@folder.children) + end + end + + def require_delete_permissions_for(folders) + folders.each do |folder| + unless current_user.can_delete(folder) + redirect_to @folder.parent, :alert => t(:no_delete_permissions_for_subfolder) + else + # Recursive... + require_delete_permissions_for(folder.children) + end + end + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/groups_controller.rb b/app/controllers/boxroom/groups_controller.rb new file mode 100644 index 0000000..de30dda --- /dev/null +++ b/app/controllers/boxroom/groups_controller.rb @@ -0,0 +1,58 @@ +module Boxroom + class GroupsController < ApplicationController + before_action :require_admin + before_action :require_existing_group, :only => [:edit, :update, :destroy] + before_action :require_group_isnt_admins_group, :only => [:edit, :update, :destroy] + + def index + @groups = Group.order(:name) + end + + def new + @group = Group.new + end + + def create + @group = Group.new(permitted_params.group) + + if @group.save + redirect_to groups_url + else + render :action => 'new' + end + end + + # Note: @group is set in require_existing_group + def edit + end + + # Note: @group is set in require_existing_group + def update + if @group.update_attributes(permitted_params.group) + redirect_to edit_group_url(@group), :notice => t(:your_changes_were_saved) + else + render :action => 'edit' + end + end + + # Note: @group is set in require_existing_group + def destroy + @group.destroy + redirect_to groups_url + end + + private + + def require_existing_group + @group = Group.find(params[:id]) + rescue ActiveRecord::RecordNotFound + redirect_to groups_url, :alert => t(:group_already_deleted) + end + + def require_group_isnt_admins_group + if @group.admins_group? + redirect_to groups_url, :alert => t(:admins_group_cannot_be_deleted) + end + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/permissions_controller.rb b/app/controllers/boxroom/permissions_controller.rb new file mode 100644 index 0000000..720f10e --- /dev/null +++ b/app/controllers/boxroom/permissions_controller.rb @@ -0,0 +1,17 @@ +module Boxroom + class PermissionsController < ApplicationController + before_action :require_admin + + def update_multiple + if params[:permissions] + permissions = Permission.update(params[:permissions].keys, params[:permissions].values) + folder = permissions.first.folder + folder.copy_permissions_to_children(permissions) if params[:recursive] && folder.has_children? + end + + redirect_to :back + rescue ActiveRecord::RecordNotFound # Folder was deleted, so permissions are gone too + redirect_to Folder.root, :alert => t(:already_deleted, :type => t(:this_folder)) + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/reset_password_controller.rb b/app/controllers/boxroom/reset_password_controller.rb new file mode 100644 index 0000000..e916077 --- /dev/null +++ b/app/controllers/boxroom/reset_password_controller.rb @@ -0,0 +1,43 @@ +module Boxroom + class ResetPasswordController < ApplicationController + before_action :require_valid_token, :only => [:edit, :update] + skip_before_action :require_login + + def new + end + + def create + user = User.find_by_email(params[:email]) + + unless user.nil? + user.refresh_reset_password_token + UserMailer.reset_password_email(user).deliver_now + end + + redirect_to new_reset_password_url, :notice => t(:instruction_email_sent, :email => params[:email]) + end + + # Note: @user is set in require_valid_token + def edit + end + + # Note: @user is set in require_valid_token + def update + if @user.update_attributes(permitted_params.user.merge({:password_required => true})) + redirect_to new_session_url, :notice => t(:password_reset_successfully) + else + render :action => 'edit' + end + end + + private + + def require_valid_token + @user = User.find_by_reset_password_token(params[:id]) + + if @user.nil? || @user.reset_password_token_expires_at < Time.now + redirect_to new_reset_password_url, :alert => t(:reset_url_expired) + end + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/sessions_controller.rb b/app/controllers/boxroom/sessions_controller.rb new file mode 100644 index 0000000..12cbbe9 --- /dev/null +++ b/app/controllers/boxroom/sessions_controller.rb @@ -0,0 +1,46 @@ +module Boxroom + class SessionsController < ApplicationController + skip_before_action :require_login + + def new + end + + def create + user = User.authenticate(params[:username], params[:password]) + + unless user.nil? + if params[:remember_me] == 'true' + user.refresh_remember_token + cookies[:auth_token] = {:value => user.remember_token, :expires => 2.weeks.from_now} + end + + session[:user_id] = user.id + redirect_url = session.delete(:return_to) || folders_url + redirect_to redirect_url, :only_path => true + else + log_failed_sign_in_attempt(Time.now, params[:username], request.remote_ip) + redirect_to new_session_url, :alert => t(:credentials_incorrect) + end + end + + def destroy + current_user.forget_me + cookies.delete :auth_token + reset_session + session[:user_id] = nil + redirect_to new_session_url + end + + private + + def log_failed_sign_in_attempt(date, username, ip) + Rails.logger.error( + "\nFAILED SIGN IN ATTEMPT:\n" + + "=======================\n" + + " Date: #{date}\n" + + " Username: #{username}\n" + + " IP address: #{ip}\n\n" + ) + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/share_links_controller.rb b/app/controllers/boxroom/share_links_controller.rb new file mode 100644 index 0000000..71696f4 --- /dev/null +++ b/app/controllers/boxroom/share_links_controller.rb @@ -0,0 +1,65 @@ +module Boxroom + class ShareLinksController < ApplicationController + before_action :require_admin, :only => [:index, :destroy] + before_action :require_existing_file, :except => [:index, :destroy] + before_action :require_existing_share_link, :only => :destroy + before_action :require_read_permission, :only => [:new, :create] + skip_before_action :require_login, :only => :show + + rescue_from ActiveRecord::RecordNotFound, NoMethodError, RuntimeError, :with => :redirect_to_root_or_signin_and_show_alert + + def index + @share_links = ShareLink.active_share_links + end + + # Note: @file is set in require_existing_file + def show + send_file @file.attachment.path, :filename => @file.attachment_file_name unless @file.nil? + end + + # Note: @file is set in require_existing_file + def new + @share_link = @file.share_links.build + end + + # Note: @file and @folder are set in require_existing_file + def create + @share_link = @file.share_links.build(permitted_params.share_link) + @share_link.user = current_user + + if @share_link.save + UserMailer.share_link_email(@share_link).deliver_now + redirect_to @folder, :notice => t(:shared_successfully) + else + render :action => 'new' + end + end + + # Note: @share_link is set in require_existing_share_link + def destroy + @share_link.destroy + redirect_to share_links_url + end + + private + + def require_existing_file + @file = params[:file_id].blank? ? ShareLink.file_for_token(params[:id]) : UserFile.find(params[:file_id]) + @folder = @file.folder + end + + def require_existing_share_link + @share_link = ShareLink.find(params[:id]) + rescue ActiveRecord::RecordNotFound + redirect_to share_links_url, :alert => t(:already_deleted, :type => t(:this_share_link)) + end + + def redirect_to_root_or_signin_and_show_alert + if signed_in? + redirect_to Folder.root, :alert => t(:already_deleted, :type => t(:this_file)) + else + redirect_to signin_url, :alert => t(:already_deleted, :type => t(:this_file)) + end + end + end +end diff --git a/app/controllers/boxroom/signup_controller.rb b/app/controllers/boxroom/signup_controller.rb new file mode 100644 index 0000000..72dc191 --- /dev/null +++ b/app/controllers/boxroom/signup_controller.rb @@ -0,0 +1,29 @@ +module Boxroom + class SignupController < ApplicationController + before_action :require_valid_token, :only => [:edit, :update] + skip_before_action :require_login + + # Note: @user is set in require_valid_token + def edit + end + + # Note: @user is set in require_valid_token + def update + if @user.update_attributes(permitted_params.user.merge({:password_required => true})) + redirect_to new_session_url, :notice => t(:signed_up_successfully) + else + render :action => 'edit' + end + end + + private + + def require_valid_token + @user = User.find_by_signup_token(params[:id]) + + if @user.nil? || @user.signup_token_expires_at < Time.now + redirect_to new_session_url, :alert => t(:sign_url_expired) + end + end + end +end \ No newline at end of file diff --git a/app/controllers/boxroom/users_controller.rb b/app/controllers/boxroom/users_controller.rb new file mode 100644 index 0000000..eab1faa --- /dev/null +++ b/app/controllers/boxroom/users_controller.rb @@ -0,0 +1,73 @@ +module Boxroom + class UsersController < ApplicationController + before_action :require_admin, :except => [:edit, :update] + before_action :require_existing_user, :only => [:edit, :update, :destroy, :extend] + before_action :require_deleted_user_isnt_admin, :only => :destroy + + def index + @users = User.where.not(:name => nil).order('name') + @new_users = User.where(:name => nil).order('email') + end + + def new + @user = User.new + end + + def create + @user = User.new(permitted_params.user) + + if @user.save + UserMailer.signup_email(@user).deliver_now + redirect_to users_url + else + render :action => 'new' + end + end + + # Note: @user is set in require_existing_user + def edit + end + + # Note: @user is set in require_existing_user + def update + if @user.update_attributes(permitted_params.user.merge({:password_required => false})) + redirect_to edit_user_url(@user), :notice => t(:your_changes_were_saved) + else + render :action => 'edit' + end + end + + # Note: @user is set in require_existing_user + def extend + @user.signup_token_expires_at = @user.signup_token_expires_at + 2.weeks + @user.save(:validate => false) + redirect_to users_url + end + + # Note: @user is set in require_existing_user + def destroy + @user.destroy + redirect_to users_url + end + + private + + def require_existing_user + if current_user.member_of_admins? && params[:id] != current_user.id.to_s + @title = t(:edit_user) + @user = User.find(params[:id]) + else + @title = t(:account_settings) + @user = current_user + end + rescue ActiveRecord::RecordNotFound + redirect_to users_url, :alert => t(:user_already_deleted) + end + + def require_deleted_user_isnt_admin + if @user.is_admin + redirect_to users_url, :alert => t(:admin_user_cannot_be_deleted) + end + end + end +end \ No newline at end of file diff --git a/app/helpers/boxroom/application_helper.rb b/app/helpers/boxroom/application_helper.rb new file mode 100644 index 0000000..1eb2a05 --- /dev/null +++ b/app/helpers/boxroom/application_helper.rb @@ -0,0 +1,4 @@ +module Boxroom + module ApplicationHelper + end +end diff --git a/app/helpers/boxroom/folders_helper.rb b/app/helpers/boxroom/folders_helper.rb new file mode 100644 index 0000000..8c22558 --- /dev/null +++ b/app/helpers/boxroom/folders_helper.rb @@ -0,0 +1,17 @@ +module Boxroom + module FoldersHelper + def breadcrumbs(folder, breadcrumbs = '') + breadcrumbs = "#{link_to(folder.parent.name, folder.parent)} » #{breadcrumbs}" + breadcrumbs = breadcrumbs(folder.parent, breadcrumbs) unless folder.parent == Folder.root + breadcrumbs.html_safe + end + + def file_icon(extension) + if extension && FileTest.exists?(Rails.root.join('app', 'assets', 'images', 'fileicons', "#{extension.downcase}.png")) + "fileicons/#{extension.downcase}.png" + else + 'file.png' + end + end + end +end \ No newline at end of file diff --git a/app/jobs/boxroom/application_job.rb b/app/jobs/boxroom/application_job.rb new file mode 100644 index 0000000..4e5aa9c --- /dev/null +++ b/app/jobs/boxroom/application_job.rb @@ -0,0 +1,4 @@ +module Boxroom + class ApplicationJob < ActiveJob::Base + end +end diff --git a/app/mailers/boxroom/application_mailer.rb b/app/mailers/boxroom/application_mailer.rb new file mode 100644 index 0000000..aa23025 --- /dev/null +++ b/app/mailers/boxroom/application_mailer.rb @@ -0,0 +1,6 @@ +module Boxroom + class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' + end +end diff --git a/app/mailers/boxroom/user_mailer.rb b/app/mailers/boxroom/user_mailer.rb new file mode 100644 index 0000000..1f701ac --- /dev/null +++ b/app/mailers/boxroom/user_mailer.rb @@ -0,0 +1,18 @@ +module Boxroom + class UserMailer < ActionMailer::Base + def signup_email(user) + @user = user + mail(:to => user.email, :subject => t(:signup_email_subject)) + end + + def reset_password_email(user) + @user = user + mail(:to => user.email, :subject => t(:reset_password_email_subject)) + end + + def share_link_email(share_link) + @share_link = share_link + mail(:to => share_link.user.email, :reply_to => share_link.user.email, :bcc => share_link.emails, :subject => t(:share_link_email_subject, :email => share_link.user.email)) + end + end +end \ No newline at end of file diff --git a/app/models/boxroom/application_record.rb b/app/models/boxroom/application_record.rb new file mode 100644 index 0000000..6b43fef --- /dev/null +++ b/app/models/boxroom/application_record.rb @@ -0,0 +1,5 @@ +module Boxroom + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end +end diff --git a/app/models/boxroom/clipboard.rb b/app/models/boxroom/clipboard.rb new file mode 100644 index 0000000..d61fa0d --- /dev/null +++ b/app/models/boxroom/clipboard.rb @@ -0,0 +1,45 @@ +module Boxroom + class Clipboard + def initialize + setup + end + + def folders + Folder.where(:id => @folders) + end + + def files + UserFile.where(:id => @files) + end + + def add(item) + if item.class == Folder + @folders << item.id unless @folders.include?(item.id) + else + @files << item.id unless @files.include?(item.id) + end + end + + def remove(item) + if item.class == Folder + @folders.delete(item.id) + else + @files.delete(item.id) + end + end + + def empty? + (@folders.empty? || folders.empty?) && (@files.empty? || files.empty?) + end + + def reset + setup + end + + private + + def setup + @folders, @files = [], [] + end + end +end diff --git a/app/models/boxroom/folder.rb b/app/models/boxroom/folder.rb new file mode 100644 index 0000000..fc32473 --- /dev/null +++ b/app/models/boxroom/folder.rb @@ -0,0 +1,113 @@ +module Boxroom + class Folder < ActiveRecord::Base + acts_as_tree :order => 'name' + + has_many :user_files, -> {order :attachment_file_name}, :dependent => :destroy + has_many :permissions, :dependent => :destroy + + attr_accessor :is_copied_folder + + validates_uniqueness_of :name, :scope => :parent_id + validates_presence_of :name + + before_save :check_for_parent + after_create :create_permissions, :unless => :is_copied_folder + before_destroy :dont_destroy_root_folder + + def copy(target_folder, originally_copied_folder = nil) + new_folder = self.dup + new_folder.is_copied_folder = true + new_folder.parent = target_folder + new_folder.save! + + originally_copied_folder = new_folder if originally_copied_folder.nil? + + # Copy original folder's permissions + self.permissions.each do |permission| + new_permission = permission.dup + new_permission.folder = new_folder + new_permission.save! + end + + self.user_files.each do |file| + file.copy(new_folder) + end + + # Copy sub-folders recursively + self.children.each do |folder| + folder.copy(new_folder, originally_copied_folder) unless folder == originally_copied_folder + end + + new_folder + end + + def move(target_folder) + unless target_folder == self || self.parent_of?(target_folder) + self.parent = target_folder + save! + else + raise 'You cannot move a folder to its own sub-folder.' + end + end + + def copy_permissions_to_children(permissions_to_copy) + permissions_to_copy.each do |permission| + attributes = permission.attributes.except('id', 'folder_id', 'group_id') + Permission.where(:folder_id => children, :group_id => permission.group_id).update_all(attributes) + end + + # Copy permissions recursively + children.each do |child| + child.copy_permissions_to_children(permissions_to_copy) if child.has_children? + end + end + + def parent_of?(folder) + self.children.each do |child| + if child == folder + return true + else + return child.parent_of?(folder) + end + end + false + end + + def is_root? + parent.nil? && !new_record? + end + + def has_children? + children.count > 0 + end + + def self.root + @root_folder ||= find_by_name_and_parent_id('Root folder', nil) + end + + private + + def check_for_parent + raise 'Folders must have a parent.' if parent.nil? && name != 'Root folder' + end + + def create_permissions + unless is_root? + parent.permissions.each do |permission| + Permission.create! do |p| + p.group = permission.group + p.folder = self + p.can_create = permission.can_create + p.can_read = permission.can_read + p.can_update = permission.can_update + p.can_delete = permission.can_delete + end + end + end + end + + def dont_destroy_root_folder + raise "Can't delete Root folder" if is_root? + end + end +end \ No newline at end of file diff --git a/app/models/boxroom/group.rb b/app/models/boxroom/group.rb new file mode 100644 index 0000000..b3ce6c5 --- /dev/null +++ b/app/models/boxroom/group.rb @@ -0,0 +1,57 @@ +module Boxroom + class Group < ActiveRecord::Base + has_many :permissions, :dependent => :destroy + has_and_belongs_to_many :users + + validates_uniqueness_of :name + validates_presence_of :name + + after_create :create_admin_permissions, :if => :admins_group? + after_create :create_permissions, :unless => :admins_group? + before_destroy :dont_destroy_admins + + def admins_group? + name == 'Admins' + end + + def self.admins_group + where(:name => 'Admins').first + end + + def self.all_except_admins + where.not(:name => 'Admins') + end + + private + + def create_admin_permissions + Folder.find_each do |folder| + Permission.create! do |p| + p.group = self + p.folder = folder + p.can_create = true + p.can_read = true + p.can_update = true + p.can_delete = true + end + end + end + + def create_permissions + Folder.find_each do |folder| + Permission.create! do |p| + p.group = self + p.folder = folder + p.can_create = false + p.can_read = folder.is_root? # New groups can read the root folder + p.can_update = false + p.can_delete = false + end + end + end + + def dont_destroy_admins + raise "Can't delete admins group" if admins_group? + end + end +end \ No newline at end of file diff --git a/app/models/boxroom/permission.rb b/app/models/boxroom/permission.rb new file mode 100644 index 0000000..b02a89c --- /dev/null +++ b/app/models/boxroom/permission.rb @@ -0,0 +1,6 @@ +module Boxroom + class Permission < ActiveRecord::Base + belongs_to :group + belongs_to :folder + end +end \ No newline at end of file diff --git a/app/models/boxroom/permitted_params.rb b/app/models/boxroom/permitted_params.rb new file mode 100644 index 0000000..0643796 --- /dev/null +++ b/app/models/boxroom/permitted_params.rb @@ -0,0 +1,33 @@ +module Boxroom + class PermittedParams < Struct.new(:params, :current_user) + %w{folder group share_link user user_file}.each do |model_name| + define_method model_name do + params.require(model_name.to_sym).permit(*send("#{model_name}_attributes")) + end + end + + def folder_attributes + [:name] + end + + def group_attributes + [:name] + end + + def share_link_attributes + [:emails, :link_expires_at, :message] + end + + def user_attributes + if current_user && current_user.member_of_admins? + [:name, :email, :password, :password_confirmation, {:group_ids => []}] + else + [:name, :email, :password, :password_confirmation] + end + end + + def user_file_attributes + [:attachment, :attachment_file_name] + end + end +end \ No newline at end of file diff --git a/app/models/boxroom/share_link.rb b/app/models/boxroom/share_link.rb new file mode 100644 index 0000000..86c27d5 --- /dev/null +++ b/app/models/boxroom/share_link.rb @@ -0,0 +1,40 @@ +module Boxroom + class ShareLink < ActiveRecord::Base + belongs_to :user + belongs_to :user_file + + validates_presence_of :emails, :link_expires_at + validates_length_of :emails, :maximum => 255 + validate :format_of_emails + + before_save :generate_token + + def self.active_share_links + where('link_expires_at >= ?', DateTime.now).order(:link_expires_at) + end + + def self.file_for_token(token) + share_link = find_by_link_token(token) + + if share_link.link_expires_at < DateTime.now + raise 'This share link expired.' + else + share_link.user_file + end + end + + private + + def format_of_emails + emails.split(/,\s*/).each do |email| + unless email.strip =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/ + errors.add(:emails, I18n.t(:are_invalid_due_to, :email => email)) + end + end + end + + def generate_token + self.link_token = SecureRandom.hex(10) + end + end +end \ No newline at end of file diff --git a/app/models/boxroom/user.rb b/app/models/boxroom/user.rb new file mode 100644 index 0000000..c98bd3a --- /dev/null +++ b/app/models/boxroom/user.rb @@ -0,0 +1,113 @@ +module Boxroom + class User < ActiveRecord::Base + has_and_belongs_to_many :groups + has_many :share_links + + attr_accessor :password_confirmation, :password_required, :dont_clear_reset_password_token + + validates_confirmation_of :password + validates_length_of :password, :in => 6..20, :allow_blank => true + validates_presence_of :password, :if => :password_required + validates_presence_of :name, :unless => :new_record? + validates_presence_of :email + validates_uniqueness_of :name, :unless => :new_record? && :name_is_blank? + validates_uniqueness_of :email + validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/ + + before_create :set_signup_token + before_save :clear_reset_password_token, :unless => :dont_clear_reset_password_token + before_update :clear_signup_token + after_create :create_root_folder_and_admins_group, :if => :is_admin + before_destroy :dont_destroy_admin + + %w{create read update delete}.each do |method| + define_method "can_#{method}" do |folder| + has_permission = false + + Permission.where(:group_id => groups, :folder_id => folder.id).each do |permission| + has_permission = permission.send("can_#{method}") + break if has_permission + end + + has_permission + end + end + + def password + @password + end + + def password=(new_password) + @password = new_password + + unless @password.blank? + self.password_salt = SecureRandom.base64(32) + self.hashed_password = Digest::SHA256.hexdigest(password_salt + password) + end + end + + def member_of_admins? + groups.admins_group.present? + end + + def refresh_reset_password_token + self.reset_password_token = SecureRandom.hex(10) + self.reset_password_token_expires_at = 1.hour.from_now + self.dont_clear_reset_password_token = true + save(:validate => false) + end + + def refresh_remember_token + self.remember_token = SecureRandom.base64(32) + save(:validate => false) + end + + def forget_me + self.remember_token = nil + save(:validate => false) + end + + def name_is_blank? + self.name.blank? + end + + def self.authenticate(name, password) + return nil if name.blank? || password.blank? + user = find_by_name(name) or return nil + hash = Digest::SHA256.hexdigest(user.password_salt + password) + hash == user.hashed_password ? user : nil + end + + def self.no_admin_yet? + find_by_is_admin(true).blank? + end + + private + + def set_signup_token + self.signup_token = SecureRandom.hex(10) + self.signup_token_expires_at = 2.weeks.from_now + end + + def clear_signup_token + unless self.name.blank? + self.signup_token = nil + self.signup_token_expires_at = nil + end + end + + def clear_reset_password_token + self.reset_password_token = nil + self.reset_password_token_expires_at = nil + end + + def create_root_folder_and_admins_group + Folder.create(:name => 'Root folder') + groups << Group.create(:name => 'Admins') + end + + def dont_destroy_admin + raise "Can't delete original admin user" if is_admin + end + end +end diff --git a/app/models/boxroom/user_file.rb b/app/models/boxroom/user_file.rb new file mode 100644 index 0000000..5673a30 --- /dev/null +++ b/app/models/boxroom/user_file.rb @@ -0,0 +1,35 @@ +module Boxroom + class UserFile < ActiveRecord::Base + has_attached_file :attachment, :path => ':rails_root/uploads/:rails_env/:id/:style/:id', :restricted_characters => RESTRICTED_CHARACTERS + do_not_validate_attachment_file_type :attachment + + belongs_to :folder + has_many :share_links, :dependent => :destroy + + validates_attachment_presence :attachment, :message => I18n.t(:blank, :scope => [:activerecord, :errors, :messages]) + validates_presence_of :folder_id + validates_uniqueness_of :attachment_file_name, :scope => 'folder_id', :message => I18n.t(:exists_already, :scope => [:activerecord, :errors, :messages]) + validates_format_of :attachment_file_name, :with => /\A[^\/\\\?\*:|"<>]+\z/, :message => I18n.t(:invalid_characters, :scope => [:activerecord, :errors, :messages]) + + def copy(target_folder) + new_file = self.dup + new_file.folder = target_folder + new_file.save! + + path = "#{Rails.root}/uploads/#{Rails.env}/#{new_file.id}/original" + FileUtils.mkdir_p path + FileUtils.cp_r self.attachment.path, "#{path}/#{new_file.id}" + + new_file + end + + def move(target_folder) + self.folder = target_folder + save! + end + + def extension + File.extname(attachment_file_name)[1..-1] + end + end +end \ No newline at end of file diff --git a/app/views/boxroom/admins/new.html.erb b/app/views/boxroom/admins/new.html.erb new file mode 100644 index 0000000..5da81cf --- /dev/null +++ b/app/views/boxroom/admins/new.html.erb @@ -0,0 +1,28 @@ +<% content_for :title, t(:create_admin) -%> + +

<%= content_for :title %>

+

+ <%= t :no_administrator_yet %> +

+<%= form_for @user, :url => { :action => 'create' } do |f| %> + <%= f.error_messages %> +

+ <%= f.label :name, t(:username) %>:
+ <%= f.text_field :name, { :class => 'text_input' } %> +

+

+ <%= f.label :email %>:
+ <%= f.text_field :email, { :class => 'text_input' } %> +

+

+ <%= label_tag :password %>:
+ <%= f.password_field :password, { :class => 'text_input' } %> +

+

+ <%= label_tag :confirm_password %>:
+ <%= f.password_field :password_confirmation, { :class => 'text_input' } %> +

+

+ <%= f.submit t(:create_admin_account) %> +

+<% end %> diff --git a/app/views/boxroom/clipboard/_clipboard_empty.de.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.de.html.erb new file mode 100644 index 0000000..9c1f7e9 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.de.html.erb @@ -0,0 +1,2 @@ +

Die Zwischenablage ist leer

+Gehen Sie <%= link_to 'zurück', '#', :class => 'back_link' %> und benutzen Sie die Funktion Zwischenablage, um Ordner oder Dateien in die Zwischenablage zu kopieren. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.en.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.en.html.erb new file mode 100644 index 0000000..7bc93b4 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.en.html.erb @@ -0,0 +1,2 @@ +

The clipboard is empty

+Go <%= link_to 'back', '#', :class => 'back_link' %> and use the clipboard button to add files or folders to the clipboard. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.es.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.es.html.erb new file mode 100644 index 0000000..b55e6e1 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.es.html.erb @@ -0,0 +1,2 @@ +

El portapapeles esta vacío

+Volver <%= link_to 'back', '#', :class => 'back_link' %> para usar el boton de portapapeles y agregar archivos o carpetas al portapapeles. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.fr.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.fr.html.erb new file mode 100644 index 0000000..725bb06 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.fr.html.erb @@ -0,0 +1,2 @@ +

Le presse-papier est vide !

+Revenir <%= link_to 'en arrière', '#', :class => 'back_link' %> et utilisez le bouton "presse-papier" pour y ajouter des fichiers ou dossiers. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.it.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.it.html.erb new file mode 100644 index 0000000..90a8da3 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.it.html.erb @@ -0,0 +1,2 @@ +

Nessun file presente negli appunti

+Vai <%= link_to 'indietro', '#', :class => 'back_link' %> e usa il pulsante appunti per aggiungere file o cartelle agli appunti. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.nl.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.nl.html.erb new file mode 100644 index 0000000..422bda9 --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.nl.html.erb @@ -0,0 +1,2 @@ +

Het klembord is leeg

+Ga <%= link_to 'terug', '#', :class => 'back_link' %> en gebruik de klembord knop om bestanden en mappen toe te voegen aan het klembord. diff --git a/app/views/boxroom/clipboard/_clipboard_empty.zh-CN.html.erb b/app/views/boxroom/clipboard/_clipboard_empty.zh-CN.html.erb new file mode 100644 index 0000000..a6abd5b --- /dev/null +++ b/app/views/boxroom/clipboard/_clipboard_empty.zh-CN.html.erb @@ -0,0 +1,2 @@ +

剪贴板是空的

+<%= link_to '回去', '#', :class => 'back_link' %>用剪贴板按钮将文件或文件夹加到剪贴板。 diff --git a/app/views/boxroom/clipboard/_show.html.erb b/app/views/boxroom/clipboard/_show.html.erb new file mode 100644 index 0000000..51abec8 --- /dev/null +++ b/app/views/boxroom/clipboard/_show.html.erb @@ -0,0 +1,70 @@ +<% if clipboard.empty? -%> +

+ <%= image_tag('boxroom/information.png', :alt => 'Notice', :class => 'clipboard_info_image') %> + <%= render 'boxroom/clipboard/clipboard_empty' %> +

+<% else -%> + + + + + + + <% reset_cycle -%> + <% clipboard.folders.each do |item| -%> + + + + + + <% end -%> + <% clipboard.files.each do |item| -%> + + + + + + <% end -%> +
<%= t :name %>
<%= image_tag('boxroom/folder.png') %><%= item.name %> + <% if current_user.can_create(@folder) -%> + <%= link_to image_tag('boxroom/copy.png', :alt => t(:copy)), + { :controller => :clipboard, :action => :copy, :id => item.id, :type => 'folder', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:copy_folder) + %>  + <% end -%> + <% if current_user.can_create(@folder) && current_user.can_delete(item) -%> + <%= link_to image_tag('boxroom/move.png', :alt => t(:move)), + { :controller => :clipboard, :action => :move, :id => item.id, :type => 'folder', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:move_folder), :data => { :confirm => t(:are_you_sure) } + %>  + <% end -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), + { :controller => :clipboard, :action => :destroy, :id => item.id, :type => 'folder', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :delete, :title => t(:remove_from_clipboard) + %> +
<%= image_tag(file_icon(item.extension)) %><%= item.attachment_file_name %> + <% if current_user.can_create(@folder) -%> + <%= link_to image_tag('boxroom/copy.png', :alt => t(:copy)), + { :controller => :clipboard, :action => :copy, :id => item.id, :type => 'file', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:copy_file) + %>  + <% end -%> + <% if current_user.can_create(@folder) && current_user.can_delete(item.folder) -%> + <%= link_to image_tag('boxroom/move.png', :alt => t(:move)), + { :controller => :clipboard, :action => :move, :id => item.id, :type => 'file', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:move_file), :data => { :confirm => t(:are_you_sure) } + %>  + <% end -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), + { :controller => :clipboard, :action => :destroy, :id => item.id, :type => 'file', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :delete, :title => t(:remove_from_clipboard) + %> +
+

+ <%= button_to t(:clear_clipboard), + { :controller => :clipboard, :action => :reset, :folder_id => @folder, :authenticity_token => form_authenticity_token }, :method => :put + %> +   —   + <%= link_to t(:back), '#', :class => 'back_link' %> +

+<% end -%> diff --git a/app/views/boxroom/files/edit.html.erb b/app/views/boxroom/files/edit.html.erb new file mode 100644 index 0000000..895c6e9 --- /dev/null +++ b/app/views/boxroom/files/edit.html.erb @@ -0,0 +1,14 @@ +<% content_for :title, t(:rename_file) -%> + +

<%= content_for :title %>

+<%= form_for @file, :url => { :action => 'update' } do |f| %> + <%= f.error_messages %> +

+ <%= f.label :name %>:
+ <%= f.text_field :attachment_file_name %> +

+

+ <%= f.submit t(:save) %>   —   + <%= link_to t(:back), @folder %> +

+<% end %> diff --git a/app/views/boxroom/files/new.html.erb b/app/views/boxroom/files/new.html.erb new file mode 100644 index 0000000..befd99c --- /dev/null +++ b/app/views/boxroom/files/new.html.erb @@ -0,0 +1,25 @@ +<% content_for :title, t(:upload_file) -%> + +

<%= content_for :title %>

+<%= form_for [@target_folder, @file], :url => { :action => 'create' } do |f| %> +

+ <%= hidden_field_tag :target_folder_id, @target_folder.id %> + <%= file_field_tag :attachment, :multiple => true, :name => 'user_file[attachment]' %> +

+
+
+

+ <%= link_to t(:back), @target_folder %> +

+<% end %> + + diff --git a/app/views/boxroom/folders/_form.html.erb b/app/views/boxroom/folders/_form.html.erb new file mode 100644 index 0000000..30b2d2d --- /dev/null +++ b/app/views/boxroom/folders/_form.html.erb @@ -0,0 +1,9 @@ +<%= f.error_messages %> +

+ <%= f.label :name %>:
+ <%= f.text_field :name, { :class => 'text_input' } %> +

+

+ <%= f.submit t(:save) %>   —   + <%= link_to t(:back), @folder.parent %> +

diff --git a/app/views/boxroom/folders/edit.html.erb b/app/views/boxroom/folders/edit.html.erb new file mode 100644 index 0000000..91e9df7 --- /dev/null +++ b/app/views/boxroom/folders/edit.html.erb @@ -0,0 +1,6 @@ +<% content_for :title, t(:rename_folder) -%> + +

<%= content_for :title %>

+<%= form_for @folder do |f| %> + <%= render :partial => 'form', :locals => { :f => f } %> +<% end %> diff --git a/app/views/boxroom/folders/new.html.erb b/app/views/boxroom/folders/new.html.erb new file mode 100644 index 0000000..52693b8 --- /dev/null +++ b/app/views/boxroom/folders/new.html.erb @@ -0,0 +1,6 @@ +<% content_for :title, t(:new_folder) -%> + +

<%= content_for :title %>

+<%= form_for [@target_folder, @folder] do |f| %> + <%= render :partial => 'form', :locals => { :f => f } %> +<% end %> diff --git a/app/views/boxroom/folders/show.html.erb b/app/views/boxroom/folders/show.html.erb new file mode 100644 index 0000000..f803655 --- /dev/null +++ b/app/views/boxroom/folders/show.html.erb @@ -0,0 +1,92 @@ +<% content_for :title, @folder.name -%> + +

<%= content_for :title %>

+<% unless @folder.is_root? -%> +

+ <%= breadcrumbs(@folder) %> + <%= @folder.name %> +

+<% end -%> +

+<% if current_user.can_create(@folder) -%> + <%= image_tag('boxroom/folder_add.png', :alt => t(:create_a_new_folder)) %> <%= link_to t(:create_a_new_folder), new_folder_folder_path(@folder) %> + <%= image_tag('boxroom/file_add.png', :alt => t(:upload_a_file)) %> <%= link_to t(:upload_a_file), new_folder_file_path(@folder) %> +<% end -%> +<% if current_user.member_of_admins? -%> + <%= image_tag('boxroom/permissions.png', :alt => t(:permissions)) %> <%= link_to t(:permissions), '#', :class => 'permissions_link' %> +<% end -%> + <%= image_tag('boxroom/clipboard.png', :alt => t(:clipboard)) %> <%= link_to t(:clipboard), '#', :class => 'clipboard_link' %> +

+
+ + + + + + + + + <% unless @folder.is_root? -%> + + + + + + + + <% end -%> + <% @folder.children.each do |folder| -%> + <% if current_user.can_read(folder) -%> + + + + + + + + <% end -%> + <% end -%> + <% @folder.user_files.each do |file| -%> + <% if current_user.can_read(@folder) -%> + + + + + + + + <% end -%> + <% end -%> +
<%= t :name %><%= t :size %><%= t :date_modified %>
<%= image_tag('boxroom/folder.png') %>↑ <%= link_to t(:up), @folder.parent, :title => @folder.parent.name %>----
<%= image_tag('boxroom/folder.png') %><%= link_to folder.name, folder %>--<%= l folder.updated_at, :format => :short %> + <% if current_user.can_update(folder) -%> + <%= link_to image_tag('boxroom/edit.png', :alt => t(:edit)), edit_folder_path(folder), :title => t(:edit) %>  + <% end -%> + <% if current_user.can_delete(folder) -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), folder, :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:delete_item) %>  + <% end -%> + <%= link_to image_tag('boxroom/clipboard_add.png', :alt => t(:add_to_clipboard)), + { :controller => :clipboard, :action => :create, :id => folder.id, :type => 'folder', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:add_to_clipboard) + %> +
<%= image_tag(file_icon(file.extension)) %><%= link_to file.attachment_file_name, file_path(file), :title => "#{t(:download)} #{file.attachment_file_name}" %><%= number_to_human_size(file.attachment_file_size, :locale => I18n.locale) %><%= l file.updated_at, :format => :short %> + <% if current_user.can_update(file.folder) -%> + <%= link_to image_tag('boxroom/edit.png', :alt => t(:edit)), edit_file_path(file), :title => t(:edit) %>  + <% end -%> + <% if current_user.can_delete(file.folder) -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), file_path(file), :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:delete_item) %>  + <% end -%> + <%= link_to image_tag('boxroom/clipboard_add.png', :alt => t(:add_to_clipboard)), + { :controller => :clipboard, :action => :create, :id => file.id, :type => 'file', :folder_id => @folder, :authenticity_token => form_authenticity_token }, + :method => :post, :title => t(:add_to_clipboard) + %>  + <%= link_to image_tag('boxroom/share.png', :alt => t(:share)), new_file_share_link_path(file), :title => t(:share) %> +
+
+<% if current_user.member_of_admins? -%> + +<% end -%> + diff --git a/app/views/boxroom/groups/_form.html.erb b/app/views/boxroom/groups/_form.html.erb new file mode 100644 index 0000000..877f415 --- /dev/null +++ b/app/views/boxroom/groups/_form.html.erb @@ -0,0 +1,11 @@ +<%= form_for @group do |f| %> + <%= f.error_messages %> +

+ <%= f.label :name %>:
+ <%= f.text_field :name, { :class => 'text_input' } %> +

+

+ <%= f.submit t(:save) %>   —   + <%= link_to t(:back), groups_url %> +

+<% end %> diff --git a/app/views/boxroom/groups/edit.html.erb b/app/views/boxroom/groups/edit.html.erb new file mode 100644 index 0000000..9ac3dd2 --- /dev/null +++ b/app/views/boxroom/groups/edit.html.erb @@ -0,0 +1,4 @@ +<% content_for :title, t(:rename_group) -%> + +

<%= content_for :title %>

+<%= render 'form' %> diff --git a/app/views/boxroom/groups/index.html.erb b/app/views/boxroom/groups/index.html.erb new file mode 100644 index 0000000..cf21f53 --- /dev/null +++ b/app/views/boxroom/groups/index.html.erb @@ -0,0 +1,29 @@ +<% content_for :title, t(:groups) -%> + +

<%= content_for :title %>

+

+ <%= image_tag('boxroom/group_add.png', :alt => t(:create_a_new_group)) %> <%= link_to t(:create_a_new_group), new_group_path %> +

+ + + + + + +<% @groups.each do |group| -%> + + <% if group.admins_group? -%> + + + + <% else -%> + + + + <% end -%> + +<% end -%> +
<%= t :name %>
<%= image_tag('boxroom/group_grey.png') %><%= group.name %><%= image_tag('boxroom/group.png') %><%= group.name %> + <%= link_to image_tag('boxroom/edit.png', :alt => t(:edit)), edit_group_path(group), :title => t(:edit) %>  + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), group_path(group), :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:delete_item) %> +
diff --git a/app/views/boxroom/groups/new.html.erb b/app/views/boxroom/groups/new.html.erb new file mode 100644 index 0000000..72f78dd --- /dev/null +++ b/app/views/boxroom/groups/new.html.erb @@ -0,0 +1,4 @@ +<% content_for :title, t(:new_group) -%> + +

<%= content_for :title %>

+<%= render 'form' %> diff --git a/app/views/boxroom/permissions/_form.html.erb b/app/views/boxroom/permissions/_form.html.erb new file mode 100644 index 0000000..28990ca --- /dev/null +++ b/app/views/boxroom/permissions/_form.html.erb @@ -0,0 +1,41 @@ +<%= form_tag update_multiple_permissions_path do %> + <%= hidden_field_tag '_method', 'put' %> + + + + + + + + + + <% reset_cycle -%> + <% @folder.permissions.each do |permission| -%> + <%= fields_for "permissions[]", permission do |f| %> + + <% if permission.group.admins_group? -%> + + + + + + + <% else -%> + + + + + + + <% end -%> + + <% end -%> + <% end -%> +
<%= t :create_permission %><%= t :read_permission %><%= t :update_permission %><%= t :delete_permission %>
<%= image_tag('boxroom/group_grey.png') %><%= permission.group.name %><%= image_tag('boxroom/group.png') %><%= permission.group.name %><%= f.check_box :can_create %><%= f.check_box :can_read %><%= f.check_box :can_update %><%= f.check_box :can_delete %>
+

+ <%= submit_tag t(:save) %> + <%= check_box_tag :recursive, true %> <%= t :apply_changes_to_subfolders %> +   —   + <%= link_to t(:back), '#', :class => 'back_link' %> +

+<% end -%> diff --git a/app/views/boxroom/reset_password/_message.de.html.erb b/app/views/boxroom/reset_password/_message.de.html.erb new file mode 100644 index 0000000..b6d4bcf --- /dev/null +++ b/app/views/boxroom/reset_password/_message.de.html.erb @@ -0,0 +1,2 @@ +Geben Sie ihre E-Mail Adresse ein um eine Anleitung zur
+Rücksetzung Ihres Kennworts zu erhalten. diff --git a/app/views/boxroom/reset_password/_message.en.html.erb b/app/views/boxroom/reset_password/_message.en.html.erb new file mode 100644 index 0000000..6fb6b83 --- /dev/null +++ b/app/views/boxroom/reset_password/_message.en.html.erb @@ -0,0 +1,2 @@ +Submit your email address and we will send you an email
+with instructions on how to reset your password. diff --git a/app/views/boxroom/reset_password/_message.es.html.erb b/app/views/boxroom/reset_password/_message.es.html.erb new file mode 100644 index 0000000..6c2bf6f --- /dev/null +++ b/app/views/boxroom/reset_password/_message.es.html.erb @@ -0,0 +1,2 @@ +Ingrese su cuenta de correo donde recibirá un correo
+con instrucciones de como reiniciar sus credenciales. diff --git a/app/views/boxroom/reset_password/_message.fr.html.erb b/app/views/boxroom/reset_password/_message.fr.html.erb new file mode 100644 index 0000000..532aa81 --- /dev/null +++ b/app/views/boxroom/reset_password/_message.fr.html.erb @@ -0,0 +1,2 @@ +Saisissez votre adresse email et vous recevrez un message
+contentant les instructions pour réinitialiser votre mot de passe. \ No newline at end of file diff --git a/app/views/boxroom/reset_password/_message.it.html.erb b/app/views/boxroom/reset_password/_message.it.html.erb new file mode 100644 index 0000000..25214a3 --- /dev/null +++ b/app/views/boxroom/reset_password/_message.it.html.erb @@ -0,0 +1,2 @@ +Inserisci il tuo indirizzo email e riceverai una email
+con le istruzioni su come reimpostare la password. diff --git a/app/views/boxroom/reset_password/_message.nl.html.erb b/app/views/boxroom/reset_password/_message.nl.html.erb new file mode 100644 index 0000000..58bb75f --- /dev/null +++ b/app/views/boxroom/reset_password/_message.nl.html.erb @@ -0,0 +1,2 @@ +Vul uw e-mailadres in en we zullen u een e-mail toesturen
+met instructies om uw wachtwoord te resetten. diff --git a/app/views/boxroom/reset_password/_message.zh-CN.html.erb b/app/views/boxroom/reset_password/_message.zh-CN.html.erb new file mode 100644 index 0000000..f62fbf6 --- /dev/null +++ b/app/views/boxroom/reset_password/_message.zh-CN.html.erb @@ -0,0 +1,2 @@ +请提供您的电子邮箱地址,
+我们将给您发邮件告诉您如何重设密码。 diff --git a/app/views/boxroom/reset_password/edit.html.erb b/app/views/boxroom/reset_password/edit.html.erb new file mode 100644 index 0000000..2bf20ec --- /dev/null +++ b/app/views/boxroom/reset_password/edit.html.erb @@ -0,0 +1,18 @@ +<% content_for :title, t(:reset_password) -%> + +

<%= content_for :title %>

+<%= form_for @user, :url => { :action => 'update' } do |f| %> + <%= f.error_messages %> +

+ <%= label_tag :password, t(:password) %>:
+ <%= f.password_field :password, { :class => 'text_input' } %> +

+

+ <%= label_tag :confirm_password, t(:confirm_password) %>:
+ <%= f.password_field :password_confirmation, { :class => 'text_input' } %> +

+

+ <%= f.submit t(:reset_password) %>   —   + <%= link_to t(:back), new_session_path %> +

+<% end %> diff --git a/app/views/boxroom/reset_password/new.html.erb b/app/views/boxroom/reset_password/new.html.erb new file mode 100644 index 0000000..649a069 --- /dev/null +++ b/app/views/boxroom/reset_password/new.html.erb @@ -0,0 +1,16 @@ +<% content_for :title, t(:reset_password) -%> + +

<%= content_for :title %>

+

+ <%= render 'message' %> +

+<%= form_tag(reset_password_index_path) do %> +

+ <%= label_tag :email, t(:email) %>:
+ <%= text_field_tag :email, nil, :class => 'text_input' %> +

+

+ <%= submit_tag t(:send_email) %>   —   + <%= link_to t(:back), new_session_path %> +

+<% end %> diff --git a/app/views/boxroom/sessions/new.html.erb b/app/views/boxroom/sessions/new.html.erb new file mode 100644 index 0000000..41e5c98 --- /dev/null +++ b/app/views/boxroom/sessions/new.html.erb @@ -0,0 +1,21 @@ +<% content_for :title, t(:sign_in) -%> + +

<%= content_for :title %>

+<%= form_tag(sessions_path) do %> +

+ <%= label_tag :username, t(:username) %>:
+ <%= text_field_tag :username, nil, :class => 'text_input' %> +

+

+ <%= label_tag :password, t(:password) %>:
+ <%= password_field_tag :password, nil, :class => 'text_input' %> +

+

+ <%= check_box_tag :remember_me, 'true' %> + <%= label_tag :remember_me, t(:remember_me) %> +

+

+ <%= submit_tag t(:sign_in) %>   —   + <%= link_to t(:reset_password), new_reset_password_path %> +

+<% end %> diff --git a/app/views/boxroom/share_links/index.html.erb b/app/views/boxroom/share_links/index.html.erb new file mode 100644 index 0000000..ea0187a --- /dev/null +++ b/app/views/boxroom/share_links/index.html.erb @@ -0,0 +1,24 @@ +<% content_for :title, t(:shared_files) -%> + +

<%= content_for :title %>

+ + + + + + + + + + +<% @share_links.each do |share_link| -%> + + + + + + + + +<% end -%> +
<%= t('activerecord.models.user_file') %><%= t(:shared_by) %><%= t('activerecord.attributes.share_link.emails') %><%= t('activerecord.attributes.share_link.expires_at') %>
<%= image_tag file_icon(share_link.user_file.extension) %><%= link_to truncate(share_link.user_file.attachment_file_name, :length => 24), share_link.user_file.folder, :title => "#{share_link.user_file.attachment_file_name} (#{share_link.user_file.folder.name})" %><%= share_link.user.name %><%= l share_link.link_expires_at, :format => :very_short %><%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), share_link_path(share_link), :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:unshare) %>
diff --git a/app/views/boxroom/share_links/new.html.erb b/app/views/boxroom/share_links/new.html.erb new file mode 100644 index 0000000..6644f4b --- /dev/null +++ b/app/views/boxroom/share_links/new.html.erb @@ -0,0 +1,48 @@ +<% content_for :title, t(:share_file) -%> + +

<%= content_for :title %>

+<%= form_for [@file, @share_link], :url => { :action => 'create' } do |f| %> + <%= f.error_messages :header_message => t(:share_link_could_not_be_sent) %> +

+ <%= t :you_are_about_to_share_the_following_file %>:
+ + <%= image_tag(file_icon(@file.extension)) %> + <%= link_to @file.attachment_file_name, @folder %> + +

+

+ <%= f.label :emails, t(:emails_to_share_with) %>: + (<%= t :comma_seperated %>)
+ <%= f.text_area :emails, :class => 'emails_to_share_with' %>
+ + <%= t :number_of_charachters %>: + + <%= @share_link.emails.nil? ? 0 : @share_link.emails.length %> + + / 255 + +

+

+ <%= f.label :message, t(:shared_message) %>: (<%= t :optional %>)
+ <%= f.text_area :message, :class => 'share_message' %> +

+

+ <%= f.label t(:link_expires) %>:
+ <%= f.select :link_expires_at, options_for_select([ + [t(:tomorrow), 1.day.from_now.end_of_day], + [t(:three_days_from_now), 3.day.from_now.end_of_day], + [t(:one_week_from_now), 1.week.from_now.end_of_day], + [t(:ten_days_from_now), 10.days.from_now.end_of_day], + [t(:two_weeks_from_now), 2.weeks.from_now.end_of_day], + [t(:three_weeks_from_now), 3.weeks.from_now.end_of_day], + [t(:one_month_from_now), 1.month.from_now.end_of_day], + [t(:two_months_from_now), 2.months.from_now.end_of_day], + [t(:three_months_from_now), 3.months.from_now.end_of_day], + [t(:half_year_from_now), 6.months.from_now.end_of_day] + ], 2.weeks.from_now.end_of_day) %> +

+

+ <%= f.submit t(:share) %>   —   + <%= link_to t(:back), @folder %> +

+<% end %> diff --git a/app/views/boxroom/shared/_footer.html.erb b/app/views/boxroom/shared/_footer.html.erb new file mode 100644 index 0000000..b0b0241 --- /dev/null +++ b/app/views/boxroom/shared/_footer.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/boxroom/shared/_header.html.erb b/app/views/boxroom/shared/_header.html.erb new file mode 100644 index 0000000..16da834 --- /dev/null +++ b/app/views/boxroom/shared/_header.html.erb @@ -0,0 +1,11 @@ + diff --git a/app/views/boxroom/shared/_menu.html.erb b/app/views/boxroom/shared/_menu.html.erb new file mode 100644 index 0000000..dda195e --- /dev/null +++ b/app/views/boxroom/shared/_menu.html.erb @@ -0,0 +1,13 @@ + diff --git a/app/views/boxroom/signup/edit.html.erb b/app/views/boxroom/signup/edit.html.erb new file mode 100644 index 0000000..03408c5 --- /dev/null +++ b/app/views/boxroom/signup/edit.html.erb @@ -0,0 +1,26 @@ +<% content_for :title, t(:sign_up) -%> + +

<%= content_for :title %>

+<%= form_for @user, :url => { :action => 'update' } do |f| %> + <%= f.error_messages %> +

+ <%= f.label :name %>:
+ <%= f.text_field :name, { :class => 'text_input' } %> +

+

+ <%= f.label :email %>:
+ <%= f.text_field :email, { :class => 'text_input' } %> +

+

+ <%= f.label :password %>:
+ <%= f.password_field :password, { :class => 'text_input' } %> +

+

+ <%= label_tag t(:confirm_password) %>:
+ <%= f.password_field :password_confirmation, { :class => 'text_input' } %> +

+

+ <%= f.submit t(:sign_up) %>   —   + <%= link_to t(:back), new_session_path %> +

+<% end %> diff --git a/app/views/boxroom/user_mailer/reset_password_email.de.text.erb b/app/views/boxroom/user_mailer/reset_password_email.de.text.erb new file mode 100644 index 0000000..fea58f9 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.de.text.erb @@ -0,0 +1,18 @@ +Sehr geehrter Nutzer, + +Über die folgende URL können Sie ein neues Kennwort für Ihr Konto erstellen: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +Zur Erinnerung hier nochmals Ihre Anmeldedaten: + + Benutzername: <%= @user.name %> + E-Mail: <%= @user.email %> + +Sollten Sie keine Anfrage zum Zurücksetzen Ihres Kennworts geschickt haben, +ignorieren Sie diese Nachricht. +Ihr altes Kennwort ist weiterhin gültig. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.en.text.erb b/app/views/boxroom/user_mailer/reset_password_email.en.text.erb new file mode 100644 index 0000000..42c7f35 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.en.text.erb @@ -0,0 +1,17 @@ +Dear user, + +Please use the following URL to reset your Boxroom password: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +Just as a reminder, your account info: + + Username: <%= @user.name %> + Email: <%= @user.email %> + +If you did not submit a request to reset your password, please ignore this email. +Your current password is still valid. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.es.text.erb b/app/views/boxroom/user_mailer/reset_password_email.es.text.erb new file mode 100644 index 0000000..150d808 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.es.text.erb @@ -0,0 +1,17 @@ +Estiamdo usuario, + +Use la siguiente URL para reiniciar sus credenciales de Boxroom: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +Información de su cuenta: + + Usuario: <%= @user.name %> + Correo: <%= @user.email %> + +Si usted no solicitó reiniciar sus credenciales, por favor ignore este correo. +Sus credenciales seguirán siendo validas. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.fr.text.erb b/app/views/boxroom/user_mailer/reset_password_email.fr.text.erb new file mode 100644 index 0000000..296d357 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.fr.text.erb @@ -0,0 +1,17 @@ +Cher utilisateur, + +Veuillez cliquer le lien ci-dessous pour réinitialiser votre mot de passe Boxroom: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +A titre d'information, vos informations utilisateur: + + Nom d'utilisateur: <%= @user.name %> + Email: <%= @user.email %> + +Si vous n'avez pas demandé à réinitialiser votre mot de passe, ignorez simplement ce message. +Votre mot de passe actuel reste inchangé. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.it.text.erb b/app/views/boxroom/user_mailer/reset_password_email.it.text.erb new file mode 100644 index 0000000..a22edd5 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.it.text.erb @@ -0,0 +1,17 @@ +Gentile utente, + +La preghiamo di utilizzare il seguente URL per reimpostare la sua password su BoxRoom: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +Le ricordiamo i dettagli dell'account: + + Username: <%= @user.name %> + Email: <%= @user.email %> + +Se non ha inoltrato la richiesta per reimpostare la password, può ignorare questa email. +La password corrente continuerà ad essere valida. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.nl.text.erb b/app/views/boxroom/user_mailer/reset_password_email.nl.text.erb new file mode 100644 index 0000000..2003859 --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.nl.text.erb @@ -0,0 +1,17 @@ +Geachte gebruiker, + +Gebruik onderstaande URL om uw wachtwoord te resetten: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +Ter herinnering, uw account informatie is: + + Gebruikersnaam: <%= @user.name %> + E-mail: <%= @user.email %> + +Als u geen aanvraag heeft gedaan voor het resetten van uw wachtwoord kunt u deze e-mail negeren. +Uw huidige wachtwoord is nog steeds geldig. + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/reset_password_email.zh-CN.text.erb b/app/views/boxroom/user_mailer/reset_password_email.zh-CN.text.erb new file mode 100644 index 0000000..911d82d --- /dev/null +++ b/app/views/boxroom/user_mailer/reset_password_email.zh-CN.text.erb @@ -0,0 +1,16 @@ +亲爱的用户: + +请用下面的网址重设您的Boxroom密码: + + <%= edit_reset_password_url(@user.reset_password_token) %> + +提醒一下,您的户头情况: + + 用户名: <%= @user.name %> + 邮箱: <%= @user.email %> + +如果您不曾提交要求重设密码,请忽略这封信。您当前的密码仍然有效。 + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.de.text.erb b/app/views/boxroom/user_mailer/share_link_email.de.text.erb new file mode 100644 index 0000000..2eca673 --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.de.text.erb @@ -0,0 +1,20 @@ +Sehr geehrter Empfänger, + +<%= @share_link.user.email %> hat Ihnen eine Datei freigegeben: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Benutzen Sie die folgende Adresse, um die Datei herunterzuladen: + + <%= share_link_url(:id => @share_link.link_token) %> + +Diese URL wird am <%= l @share_link.link_expires_at, :format => :long %> ablaufen. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.en.text.erb b/app/views/boxroom/user_mailer/share_link_email.en.text.erb new file mode 100644 index 0000000..1223d9b --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.en.text.erb @@ -0,0 +1,20 @@ +Dear recipient, + +<%= @share_link.user.email %> has shared a file with you: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Use the following URL to download your file: + + <%= share_link_url(:id => @share_link.link_token) %> + +The URL above will expire at <%= l @share_link.link_expires_at, :format => :time_only %> on <%= l @share_link.link_expires_at, :format => :date_only %>. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.es.text.erb b/app/views/boxroom/user_mailer/share_link_email.es.text.erb new file mode 100644 index 0000000..e13eb18 --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.es.text.erb @@ -0,0 +1,20 @@ +Estimado, + +<%= @share_link.user.email %> ha compartido un archivo con usted: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Use la siguiente URL para descargar su archivo: + + <%= share_link_url(:id => @share_link.link_token) %> + +La URL expirará a las <%= l @share_link.link_expires_at, :format => :time_only %> del <%= l @share_link.link_expires_at, :format => :date_only %>. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.fr.text.erb b/app/views/boxroom/user_mailer/share_link_email.fr.text.erb new file mode 100644 index 0000000..9b88fa1 --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.fr.text.erb @@ -0,0 +1,20 @@ +Cher destinataire, + +<%= @share_link.user.email %> a partagé un fichier avec vous: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Utilisez le lien suivant pour télécharger le fichier: + + <%= share_link_url(:id => @share_link.link_token) %> + +Le lien ci-dessus expirera à <%= l @share_link.link_expires_at, :format => :time_only %> le <%= l @share_link.link_expires_at, :format => :date_only %>. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.it.text.erb b/app/views/boxroom/user_mailer/share_link_email.it.text.erb new file mode 100644 index 0000000..0373042 --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.it.text.erb @@ -0,0 +1,20 @@ +Gentile utente, + +<%= @share_link.user.email %> ha condiviso un file con te: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Clicca sul seguente URL per scaricare il file: + + <%= share_link_url(:id => @share_link.link_token) %> + +L'URL indicato sopra scadrà <%= l @share_link.link_expires_at, :format => :long %>. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.nl.text.erb b/app/views/boxroom/user_mailer/share_link_email.nl.text.erb new file mode 100644 index 0000000..942221f --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.nl.text.erb @@ -0,0 +1,20 @@ +Geachte ontvanger, + +<%= @share_link.user.email %> heeft een bestand met u gedeeld: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +Gebruik onderstaande URL om het bestand te downloaden: + + <%= share_link_url(:id => @share_link.link_token) %> + +De vervaldatum van bovenstaande URL is <%= l @share_link.link_expires_at, :format => :long %>. + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/share_link_email.zh-CN.text.erb b/app/views/boxroom/user_mailer/share_link_email.zh-CN.text.erb new file mode 100644 index 0000000..84e8728 --- /dev/null +++ b/app/views/boxroom/user_mailer/share_link_email.zh-CN.text.erb @@ -0,0 +1,20 @@ +亲爱的收件人: + +<%= @share_link.user.email %> 与您共享了一个文件: + + <%= @share_link.user_file.attachment_file_name %> (<%= number_to_human_size(@share_link.user_file.attachment_file_size, :locale => I18n.locale) %>) + +请用下面的链接下载文件: + + <%= share_link_url(:id => @share_link.link_token) %> + +此链接会在<%= l @share_link.link_expires_at, :format => :time_only %>,<%= l @share_link.link_expires_at, :format => :date_only %>失效。 + +<% unless @share_link.message.blank? -%> +=== <%= t(:shared_message) %>: === +<%= strip_tags(@share_link.message) %> +<% end -%> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.de.text.erb b/app/views/boxroom/user_mailer/signup_email.de.text.erb new file mode 100644 index 0000000..2e1bf07 --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.de.text.erb @@ -0,0 +1,9 @@ +Dear recipient, + +This is an invitation to sign up for Boxroom. Please use the following URL to sign up: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.en.text.erb b/app/views/boxroom/user_mailer/signup_email.en.text.erb new file mode 100644 index 0000000..2e1bf07 --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.en.text.erb @@ -0,0 +1,9 @@ +Dear recipient, + +This is an invitation to sign up for Boxroom. Please use the following URL to sign up: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.es.text.erb b/app/views/boxroom/user_mailer/signup_email.es.text.erb new file mode 100644 index 0000000..1f4c701 --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.es.text.erb @@ -0,0 +1,9 @@ +Estimado, + +Esta es una invitacion para activar su cuenta Boxroom. Por favor use la siguiente URL: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.fr.text.erb b/app/views/boxroom/user_mailer/signup_email.fr.text.erb new file mode 100644 index 0000000..eb2bedf --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.fr.text.erb @@ -0,0 +1,9 @@ +Cher destinataire, + +Ceci est un mail d'invitation à Boxroom. Pour vous inscrire, veuillez utiliser le lien ci-dessous: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.it.text.erb b/app/views/boxroom/user_mailer/signup_email.it.text.erb new file mode 100644 index 0000000..2e1bf07 --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.it.text.erb @@ -0,0 +1,9 @@ +Dear recipient, + +This is an invitation to sign up for Boxroom. Please use the following URL to sign up: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.nl.text.erb b/app/views/boxroom/user_mailer/signup_email.nl.text.erb new file mode 100644 index 0000000..3f5f9d9 --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.nl.text.erb @@ -0,0 +1,10 @@ +Geachte ontvanger, + +Dit is een uitnodiging om u aan te melden voor Boxroom. +Gebruik onderstaande URL om uw account aan te maken: + + <%= edit_signup_url(@user.signup_token) %> + +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/user_mailer/signup_email.zh-CN.text.erb b/app/views/boxroom/user_mailer/signup_email.zh-CN.text.erb new file mode 100644 index 0000000..151e25a --- /dev/null +++ b/app/views/boxroom/user_mailer/signup_email.zh-CN.text.erb @@ -0,0 +1,8 @@ +亲爱的收件人, + +此信邀请你注册Boxroom。请用下面的网址注册: + + <%= edit_signup_url(@user.signup_token) %> +-- +Boxroom +http://boxroomapp.com/ diff --git a/app/views/boxroom/users/_form.html.erb b/app/views/boxroom/users/_form.html.erb new file mode 100644 index 0000000..e71281f --- /dev/null +++ b/app/views/boxroom/users/_form.html.erb @@ -0,0 +1,46 @@ +<%= form_for @user do |f| %> + <%= f.error_messages %> + <% unless @user.new_record? -%> +

+ <%= f.label :name %>:
+ <%= f.text_field :name, { :class => 'text_input' } %> +

+ <% end -%> +

+ <%= f.label :email %>:
+ <%= f.text_field :email, { :class => 'text_input' } %> +

+ <% unless @user.new_record? -%> +

+ <%= f.label :password %>:
+ <%= f.password_field :password, { :class => 'text_input' } %> +

+

+ <%= label_tag t(:confirm_password) %>:
+ <%= f.password_field :password_confirmation, { :class => 'text_input' } %> +

+ <% end -%> + <% if signed_in? && current_user.member_of_admins? -%> +

+ <%= t :member_of_these_groups %>: + + <% if @user.is_admin -%> + + <%= hidden_field_tag 'user[group_ids][]', Group.admins_group.id %> + + + <%= f.collection_check_boxes :group_ids, Group.all_except_admins, :id, :name %> + <% else -%> + <%= f.collection_check_boxes :group_ids, Group.all, :id, :name %> + <% end -%> + +

+ <% end -%> +

+ <%= f.submit t(:save) %> + <% if @user != current_user -%> +   —   + <%= link_to t(:back), users_url %> + <% end -%> +

+<% end %> diff --git a/app/views/boxroom/users/edit.html.erb b/app/views/boxroom/users/edit.html.erb new file mode 100644 index 0000000..c9f9b5b --- /dev/null +++ b/app/views/boxroom/users/edit.html.erb @@ -0,0 +1,4 @@ +<% content_for :title, @title -%> + +

<%= content_for :title %>

+<%= render 'form' %> diff --git a/app/views/boxroom/users/index.html.erb b/app/views/boxroom/users/index.html.erb new file mode 100644 index 0000000..0d15d89 --- /dev/null +++ b/app/views/boxroom/users/index.html.erb @@ -0,0 +1,60 @@ +<% content_for :title, t(:users) -%> + +

<%= content_for :title %>

+

+ <%= image_tag('boxroom/user_add.png', :alt => t(:create_a_new_user)) %> <%= link_to t(:create_a_new_user), new_user_path %> +

+ +

<%= t :active_users %>

+ + + + + + + +<% @users.each do |user| -%> + + + + + + +<% end -%> +
<%= t :name %><%= t :email %>
<%= image_tag('boxroom/user.png') %><%= user.name %><%= user.email %> + <%= link_to image_tag('boxroom/edit.png', :alt => t(:edit)), edit_user_path(user), :title => t(:edit) %>  + <% unless user.is_admin -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), user_path(user), :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:delete_item) %> + <% end -%> +
+ +<% unless @new_users.blank? -%> +

<%= t :invited_users %>

+ + + + + + + +<% @new_users.each do |user| -%> + + + + + + +<% end -%> +
<%= t :email %><%= t :expiration_date %>
<%= image_tag('boxroom/user.png') %><%= user.email %> + <% if user.signup_token_expires_at > Time.now -%> + <%= l user.signup_token_expires_at, :format => :very_short %> + <% else -%> + <%= l user.signup_token_expires_at, :format => :very_short %> + <% end -%> + + <%= link_to image_tag('boxroom/extend.png', :alt => t(:extend_expiration_date)), extend_user_path(user), :method => :put, :title => t(:extend_expiration_date) %>  + <% unless user.is_admin -%> + <%= link_to image_tag('boxroom/delete.png', :alt => t(:delete_item)), user_path(user), :method => :delete, :data => { :confirm => t(:are_you_sure) }, :title => t(:delete_item) %> + <% end -%> +
+<% end -%> diff --git a/app/views/boxroom/users/new.html.erb b/app/views/boxroom/users/new.html.erb new file mode 100644 index 0000000..41d442f --- /dev/null +++ b/app/views/boxroom/users/new.html.erb @@ -0,0 +1,4 @@ +<% content_for :title, t(:new_user) -%> + +

<%= content_for :title %>

+<%= render 'form' %> diff --git a/app/views/layouts/boxroom/application.html.erb b/app/views/layouts/boxroom/application.html.erb new file mode 100644 index 0000000..1374a37 --- /dev/null +++ b/app/views/layouts/boxroom/application.html.erb @@ -0,0 +1,39 @@ + + + +<% if content_for? :title -%> + <%= Boxroom.configuration.site_name %> | <%= content_for :title %> +<% else -%> + <%= Boxroom.configuration.site_name %> +<% end -%> + <%= stylesheet_link_tag 'boxroom/application' %> + <%= javascript_include_tag 'boxroom/application' %> + <%= csrf_meta_tag %> + + + +
+ <% if flash[:notice] -%> +
+

+ <%= flash[:notice] %> +

+
+ <% end -%> + <% if flash[:alert] -%> +
+

+ <%= flash[:alert] %> +

+
+ <% end -%> + <%= render 'boxroom/shared/header' %> + <%= render 'boxroom/shared/menu' %> +
+ <%= yield %> +
+ + <%= render 'boxroom/shared/footer' %> +
+ + diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..d6b505d --- /dev/null +++ b/bin/rails @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/boxroom/engine', __FILE__) +APP_PATH = File.expand_path('../../test/dummy/config/application', __FILE__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/boxroom.gemspec b/boxroom.gemspec new file mode 100644 index 0000000..4ce4535 --- /dev/null +++ b/boxroom.gemspec @@ -0,0 +1,26 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "boxroom/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "boxroom" + s.version = Boxroom::VERSION + s.authors = ["Serge Koba"] + s.email = ["s.koba@mobidev.biz"] + s.homepage = "" + s.summary = "File manager engine" + s.description = "File manager engine" + s.license = "MIT" + + s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + + s.add_dependency 'rails', "~> 5.0.2" + s.add_dependency 'dynamic_form' + s.add_dependency 'acts_as_tree' + s.add_dependency 'paperclip' + s.add_dependency 'jquery-fileupload-rails' + + s.add_development_dependency "sqlite3" +end diff --git a/config/locales/de.yml b/config/locales/de.yml new file mode 100644 index 0000000..fb45a14 --- /dev/null +++ b/config/locales/de.yml @@ -0,0 +1,412 @@ +# German translations for Boxroom +# Original Rails translation by Clemens Kofler (clemens@railway.at) +# and Alexander Dreher - http://github.com/alexdreher - Rails 3 update +# +# see: https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/de.yml + +de: + date: + formats: + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + only_day: "%e" + + day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag] + abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa] + month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember] + abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez] + order: + - :day + - :month + - :year + + time: + formats: + default: "%A, %d. %B %Y, %H:%M Uhr" + short: "%d. %B %Y, %H:%M Uhr" + very_short: "%d. %B %Y" + long: "%A, %d. %B %Y, %H:%M Uhr" + time: "%H:%M" + + am: "vormittags" + pm: "nachmittags" + + datetime: + distance_in_words: + half_a_minute: 'eine halbe Minute' + less_than_x_seconds: + one: 'weniger als eine Sekunde' + other: 'weniger als %{count} Sekunden' + x_seconds: + one: 'eine Sekunde' + other: '%{count} Sekunden' + less_than_x_minutes: + one: 'weniger als eine Minute' + other: 'weniger als %{count} Minuten' + x_minutes: + one: 'eine Minute' + other: '%{count} Minuten' + about_x_hours: + one: 'etwa eine Stunde' + other: 'etwa %{count} Stunden' + x_days: + one: 'ein Tag' + other: '%{count} Tage' + about_x_months: + one: 'etwa ein Monat' + other: 'etwa %{count} Monate' + x_months: + one: 'ein Monat' + other: '%{count} Monate' + almost_x_years: + one: 'fast ein Jahr' + other: 'fast %{count} Jahre' + about_x_years: + one: 'etwa ein Jahr' + other: 'etwa %{count} Jahre' + over_x_years: + one: 'mehr als ein Jahr' + other: 'mehr als %{count} Jahre' + prompts: + second: "Sekunden" + minute: "Minuten" + hour: "Stunden" + day: "Tag" + month: "Monat" + year: "Jahr" + + number: + format: + precision: 2 + separator: ',' + delimiter: '.' + significant: false + strip_insignificant_zeros: false + currency: + format: + unit: '€' + format: '%n%u' + separator: "," + delimiter: "" + precision: 2 + significant: false + strip_insignificant_zeros: false + percentage: + format: + delimiter: "" + precision: + format: + delimiter: "" + human: + format: + delimiter: "" + precision: 1 + significant: true + strip_insignificant_zeros: true + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: Tausend + million: Millionen + billion: + one: Milliarde + others: Milliarden + trillion: Billionen + quadrillion: + one: Billiarde + others: Billiarden + + support: + array: + words_connector: ", " + two_words_connector: " und " + last_word_connector: " und " + select: + prompt: "Bitte wählen:" + + activemodel: + errors: + template: + header: + one: "Konnte %{model} nicht speichern: ein Fehler." + other: "Konnte %{model} nicht speichern: %{count} Fehler." + body: "Bitte überprüfen Sie die folgenden Felder:" + helpers: + select: + prompt: "Bitte wählen" + + submit: + create: '%{model} erstellen' + update: '%{model} aktualisieren' + submit: '%{model} speichern' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "ist kein gültiger Wert" + exclusion: "ist nicht verfügbar" + invalid: "ist nicht gültig" + confirmation: "stimmt nicht" + accepted: "muss akzeptiert werden" + empty: "muss ausgefüllt werden" + blank: "muss ausgefüllt werden" + too_long: "ist zu lang (nicht mehr als %{count} Zeichen)" + too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)" + wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)" + not_a_number: "ist keine Zahl" + greater_than: "muss größer als %{count} sein" + greater_than_or_equal_to: "muss größer oder gleich %{count} sein" + equal_to: "muss genau %{count} sein" + less_than: "muss kleiner als %{count} sein" + less_than_or_equal_to: "muss kleiner oder gleich %{count} sein" + odd: "muss ungerade sein" + even: "muss gerade sein" + not_an_integer: "muss ganzzahlig sein" + + activerecord: + errors: + template: + header: + one: "Konnte %{model} nicht speichern: ein Fehler." + other: "Konnte %{model} nicht speichern: %{count} Fehler." + body: "Bitte überprüfen Sie die folgenden Felder:" + + messages: + taken: "ist bereits vergeben" + record_invalid: "Gültigkeitsprüfung ist fehlgeschlagen: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: Zwischenablage + folder: Ordner + group: Gruppe + permission: Berechtigung + share_link: Share link + user: Benutzer + user_file: Datei + + attributes: + folder: + name: Name + group: + name: Name + share_link: + emails: E-Mail Adressen + expires_at: Läuft ab am + user: + email: E-Mail + name: Name + password: Kennwort + user_file: + name: Name + attachment_file_name: Datei + + # APPLICATION SPECIFIC + + # general + back: Zurück + save: Speichern + name: Name + email: E-Mail + submit: Senden + + your_changes_were_saved: Ihre Einstellungen wurden erfolgreich gespeichert. + already_deleted: "Jemand hat %{type} bereits gelöscht. Die Aktion wurde abgebrochen." + + # admins/new + create_admin: Administrator-Zugang erstellen + no_administrator_yet: Es gibt noch keinen Administrator-Zugang zu Boxroom. Hier können Sie einen anlegen. + create_admin_account: Konto erstellen + admin_user_created_successfully: Das Konto wurde erfolgreich erstellt. Sie können sich nun einloggen. + + # clipboard/_show + folder: Ordner + file: Datei + this_folder: dieser Ordner + this_file: diese Datei + copy: Kopieren + copy_folder: Ordner kopieren + copy_file: Datei kopieren + move: Verschieben + move_folder: Ordner verschieben + move_file: Datei verschieben + are_you_sure: Sind Sie sicher? + delete_item: Löschen + remove_from_clipboard: Aus der Zwischenablage entfernen + clear_clipboard: Zwischenablage leeren + + # files/edit + rename_file: Datei umbenennen + + # files/new + select_file: Datei wählen + upload_file: Dateien hochladen + upload: Hochladen + exists_already: bereits vorhanden + + #folders/edit + rename_folder: Ordner umbenennen + + #folders/new + new_folder: Neuen Ordner anlegen + + #folders/show + create_a_new_folder: Neuer Ordner + upload_a_file: Dateien hochladen + permissions: Berechtigungen + clipboard: Zwischenablage + size: Größe + date_modified: Letzte Änderung + up: nach oben + edit: Bearbeiten + add_to_clipboard: Zur Zwischenablage hinzufügen + download: Herunterladen + share: Freigeben + + #groups/edit + rename_group: Gruppe umbenennen + + #groups/index + groups: Gruppen + create_a_new_group: Neue Gruppe anlegen + + #groups/new + new_group: Neue Gruppe anlegen + + #permissions/form + create_permission: Erstellen + read_permission: Lesen + update_permission: Aktualisieren + delete_permission: Löschen + apply_changes_to_subfolders: Änderungen auf Unterordner anwenden + + create: Erstellen + read: Lesen + update: Aktualisieren + delete: Löschen + + #reset_password/edit + password: Kennwort + reset_password: Kennwort zurücksetzen + send_email: Absenden + + #signup/edit + sign_up: Sign up + + #sessions + username: Benutzername + remember_me: Angemeldet bleiben + sign_in: Anmelden + + #share_links/index + shared_by: Freigabe von + unshare: Freigabe aufheben + + #share_links/new + this_share_link: Dieser Freigabe-Link + share_file: Datei freigeben + you_are_about_to_share_the_following_file: Sie möchten die folgende Datei freigeben + emails_to_share_with: 'E-Mail Adressen, für welche die Datei freigegeben werden soll' + comma_seperated: Komma-separiert + number_of_charachters: Anzahl d. Zeichen + link_expires: Ablaufdatum + tomorrow: Morgen + three_days_from_now: In drei Tagen + one_week_from_now: In einer Woche + ten_days_from_now: In zehn Tagen + two_weeks_from_now: In zwei Wochen + three_weeks_from_now: In drei Wochen + one_month_from_now: In einem Monat + two_months_from_now: In zwei Monaten + three_months_from_now: In drei Monaten + half_year_from_now: In einem halben Jahr + download_link_could_not_be_sent: Die Download-URL konnte nicht versendet werden. + are_invalid_due_to: "Die Adresse %{email} ist ungültig." + shared_successfully: Die Datei wurde erfolgreich freigegeben. + shared_message: Nachricht + optional: Nicht erforderlich + + #shared/_header + hello: Hallo + settings: Einstellungen + sign_out: Abmelden + + #shared/_menu + folders: Ordner + users: Benutzer + shared_files: Freigegebene Dateien + + #users/_form + member_of_these_groups: Mitglied in diesen Gruppen + confirm_password: Kennwort bestätigen + + #users/index + create_a_new_user: Neuen Benutzer anlegen + active_users: Active users + invited_users: Invited users + expiration_date: Expiration date + extend_expiration_date: Extend expiration date + + #users/new + new_user: Neuen Benutzer anlegen + + #admins/controller + admin_user_created_sucessfully: Der Administrator-Zugang wurde erfolgreich angelegt. Sie können sich nun anmelden. + + #application_controller + no_permissions_for_this_type: "Sie haben keine Berechtigung um %{type} zu %{method}." + + # clipboard_controller + added_to_clipboard: Erfolgreich zur Zwischenablage hinzugefügt. + could_not_copy: "Kopieren fehlgeschlagen. Ein %{type} dieses Namens existiert bereits." + could_not_move: "Verschieben fehlgeschlagen. Ein %{type} dieses Namens existiert bereits." + cannot_move_to_own_subfolder: Sie können einen Ordner nicht in einen seiner Unterordner verschieben. + + # folders_controller + cannot_delete_root_folder: Das Haupt-Verzeichnis kann nicht gelöscht oder umbenannt werden. + no_delete_permissions_for_subfolder: "Sie haben keine Berechtigung, einen der Unterordner zu löschen." + + # groups_controller + group_already_deleted: Jemand hat diese Gruppe bereits gelöscht. Die Aktion wurde abgebrochen. + admins_group_cannot_be_deleted: Die Administrator-Gruppe kann nicht gelöscht werden. + + # reset_password_controller + instruction_email_sent: E-Mail erfolgreich verschickt. Bitte prüfen Sie Ihren Posteingang. + password_reset_successfully: Ihr Kennwort wurde erfolgreich zurückgesetzt. Sie können sich nun anmelden. + reset_url_expired: Die URL zum Rücksetzen des Kennworts ist leider abgelaufen. Bitte probieren Sie es nochmals. + + # signup_controller + signed_up_successfully: Account created successfully. You can now sign in. + sign_url_expired: The URL for signing up expired. Please contact the administrator. + + # sessions_controller + credentials_incorrect: Benutzername und/oder Kennwort falsch. Bitte probieren Sie es nochmals. + + # users_controller + user_already_deleted: Jemand hat diesen Benutzer bereits gelöscht. Die Aktion wurde abgebrochen. + admin_user_cannot_be_deleted: Der Administrator-Zugang kann nicht gelöscht werden. + edit_user: Benutzer bearbeiten + account_settings: Kontoeinstellungen + + # mailers/user_mailer + signup_email_subject: '[Boxroom] Sign up invitation' + reset_password_email_subject: '[Boxroom] Anleitung zum Ändern Ihres Kennworts' + share_link_email_subject: '[Boxroom] %{email} möchte Ihnen eine Datei freigeben' diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..a2a81e6 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,405 @@ +# US English translations for Ruby on Rails +# +# Use this as the base for the locale file of your language. +# see: https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en-US.yml + +"en": + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %Y %H:%M" + very_short: "%d %b %Y" + long: "%B %d, %Y %H:%M" + date_only: "%B %d, %Y" + time_only: "%H:%M" + am: "am" + pm: "pm" + + support: + array: + words_connector: ", " + two_words_connector: " and " + last_word_connector: ", and " + + select: + prompt: "Please select" + + number: + format: + separator: "." + delimiter: "," + precision: 3 + significant: false + strip_insignificant_zeros: false + + currency: + format: + format: "%u%n" + unit: "$" + separator: "." + delimiter: "," + precision: 2 + significant: false + strip_insignificant_zeros: false + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: Thousand + million: Million + billion: Billion + trillion: Trillion + quadrillion: Quadrillion + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + prompts: + year: "Year" + month: "Month" + day: "Day" + hour: "Hour" + minute: "Minute" + second: "Seconds" + + helpers: + select: + prompt: "Please select" + + submit: + create: 'Create %{model}' + update: 'Update %{model}' + submit: 'Save %{model}' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + exists_already: 'exists already' + too_long: "is too long (maximum is %{count} characters)" + too_short: "is too short (minimum is %{count} characters)" + wrong_length: "is the wrong length (should be %{count} characters)" + not_a_number: "is not a number" + not_an_integer: "must be an integer" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + odd: "must be odd" + even: "must be even" + invalid_characters: 'cannot contain any of these characters: < > : " / \ | ? *' + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + body: "There were problems with the following fields:" + + messages: + taken: "has already been taken" + record_invalid: "Validation failed: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: Clipboard + folder: Folder + group: Group + permission: Permission + share_link: Share link + user: User + user_file: File + + attributes: + folder: + name: Name + group: + name: Name + share_link: + emails: Email addresses + expires_at: Expires + user: + email: Email + name: Name + password: Password + user_file: + name: Name + attachment_file_name: File + + # APPLICATION SPECIFIC + + # general + back: Back + save: Save + name: Name + email: Email + submit: Submit + + your_changes_were_saved: Your changes were saved successfully. + already_deleted: "Sorry, but this %{type} was deleted already." + + # admins/new + create_admin: Create admin + no_administrator_yet: Boxroom does not have an administrator yet. Create one here. + create_admin_account: Create admin account + admin_user_created_successfully: The admin user was created successfully. You can now sign in. + + # clipboard/_show + folder: folder + file: file + this_folder: this folder + this_file: this file + copy: Copy + copy_folder: Copy folder + copy_file: Copy file + move: Move + move_folder: Move folder + move_file: Move file + are_you_sure: Are you sure? + delete_item: Delete + remove_from_clipboard: Remove from clipboard + clear_clipboard: Clear clipboard + + # files/edit + rename_file: Rename file + + # files/new + select_file: Select file + upload_file: Upload files + upload: Upload + exists_already: exists already + + #folders/edit + rename_folder: Rename folder + + #folders/new + new_folder: New folder + + #folders/show + create_a_new_folder: Create a new folder + upload_a_file: Upload files + permissions: Permissions + clipboard: Clipboard + size: Size + date_modified: Date modified + up: up + edit: Edit + add_to_clipboard: Add to clipboard + download: Download + share: Share + + #groups/edit + rename_group: Rename group + + #groups/index + groups: Groups + create_a_new_group: Create a new group + + #groups/new + new_group: New group + + #permissions/form + create_permission: Create + read_permission: Read + update_permission: Update + delete_permission: Delete + apply_changes_to_subfolders: Apply changes to subfolders + + create: create + read: read + update: update + delete: delete + + #reset_password/edit + password: Password + reset_password: Reset password + send_email: Send + + #signup/edit + sign_up: Sign up + + #sessions + username: Username + remember_me: Remember me + sign_in: Sign in + + #share_links/index + shared_by: Shared by + unshare: Unshare + + #share_links/new + this_share_link: this share link + share_file: Share file + you_are_about_to_share_the_following_file: You are about to share the following file + emails_to_share_with: Email addresses of the people you wish to share this file with + comma_seperated: Comma seperated + number_of_charachters: Number of characters + link_expires: Expires + tomorrow: Tomorrow + three_days_from_now: Three days from now + one_week_from_now: A week from now + ten_days_from_now: Ten days from now + two_weeks_from_now: Two weeks from now + three_weeks_from_now: Three weeks from now + one_month_from_now: A month from now + two_months_from_now: Two months from now + three_months_from_now: Three months from now + half_year_from_now: A half year from now + share_link_could_not_be_sent: The download link could not be sent + are_invalid_due_to: "is invalid due to %{email}" + shared_successfully: The file was shared successfully. + shared_message: Message + optional: Optional + + #shared/_header + hello: Hello + settings: Settings + sign_out: Sign out + + #shared/_menu + folders: Folders + users: Users + shared_files: Shared files + + #users/_form + member_of_these_groups: Member of these groups + confirm_password: Confirm password + + #users/index + create_a_new_user: Create a new user + active_users: Active users + invited_users: Invited users + expiration_date: Expiration date + extend_expiration_date: Extend expiration date + + #users/new + new_user: New user + + #admins/controller + admin_user_created_sucessfully: The admin user was created successfully. You can now sign in. + + #application_controller + no_permissions_for_this_type: "You don't have %{method} permissions for %{type}." + + # clipboard_controller + added_to_clipboard: Successfully added to clipboard. + could_not_copy: "Couldn't copy. A %{type} with the same name exists already." + could_not_move: "Couldn't move. A %{type} with the same name exists already." + cannot_move_to_own_subfolder: You cannot move a folder to its own sub-folder. + + # folders_controller + cannot_delete_root_folder: The root folder cannot be deleted or renamed. + no_delete_permissions_for_subfolder: You don't have delete permissions for one of the subfolders. + + # groups_controller + group_already_deleted: Someone else deleted this group. Your action was cancelled. + admins_group_cannot_be_deleted: The admins group cannot be deleted or renamed. + + # reset_password_controller + instruction_email_sent: "An email with instructions was sent if '%{email}' exists in our system." + password_reset_successfully: Your password was reset successfully. You can now sign in. + reset_url_expired: The URL for resetting your password expired. Please try again. + + # signup_controller + signed_up_successfully: Account created successfully. You can now sign in. + sign_url_expired: The URL for signing up expired. Please contact the administrator. + + # sessions_controller + credentials_incorrect: Username and/or password were incorrect. Try again. + + # users_controller + user_already_deleted: Someone else deleted the user. Your action was cancelled. + admin_user_cannot_be_deleted: The admin user cannot be deleted. + edit_user: Edit user + account_settings: Account settings + + # mailers/user_mailer + signup_email_subject: '[Boxroom] Sign up invitation' + reset_password_email_subject: '[Boxroom] Password reset instructions' + share_link_email_subject: '[Boxroom] %{email} shared a file with you' diff --git a/config/locales/es.yml b/config/locales/es.yml new file mode 100644 index 0000000..46640ab --- /dev/null +++ b/config/locales/es.yml @@ -0,0 +1,401 @@ +# ES Spanish translations for Boxroom +# + + +"es": + date: + formats: + default: "%d-%m-%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Domingo, Lunes, Martes, Miércoles, Jueves, Viernes, Sábado] + abbr_day_names: [Dom, Lun, Mar, Mie, Jue, Vie, Sab] + + month_names: [~, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre] + abbr_month_names: [~, Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Sep, Oct, Nov, Dic] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %Y %H:%M" + very_short: "%d %b %Y" + long: "%B %d, %Y %H:%M" + date_only: "%d de %B del %Y" + time_only: "%H:%M" + am: "am" + pm: "pm" + + support: + array: + words_connector: ", " + two_words_connector: " y " + last_word_connector: ", y " + + select: + prompt: "Seleccione" + + number: + format: + separator: "." + delimiter: "," + precision: 3 + significant: false + strip_insignificant_zeros: false + + currency: + format: + format: "%u%n" + unit: "$" + separator: "." + delimiter: "," + precision: 2 + significant: false + strip_insignificant_zeros: false + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: Ciento + million: Millon + billion: Billon + trillion: Trillon + quadrillion: Cuadrillon + + datetime: + distance_in_words: + half_a_minute: "minuto y medio" + less_than_x_seconds: + one: "menos de 1 segundo" + other: "menos de %{count} segundos" + x_seconds: + one: "1 segundo" + other: "%{count} segundos" + less_than_x_minutes: + one: "menos de un minuto" + other: "menos de %{count} minutos" + x_minutes: + one: "1 minuto" + other: "%{count} minutos" + about_x_hours: + one: "alrededor de 1 hora" + other: "alrededor de %{count} horas" + x_days: + one: "1 día" + other: "%{count} días" + about_x_months: + one: "alrededor de 1 mes" + other: "alrededor de %{count} meses" + x_months: + one: "1 mes" + other: "%{count} meses" + about_x_years: + one: "alrededor de 1 año" + other: "alrededor de %{count} años" + over_x_years: + one: "mas de 1 año" + other: "mas de %{count} años" + almost_x_years: + one: "casi 1 año" + other: "casi %{count} años" + prompts: + year: "Año" + month: "Mes" + day: "Día" + hour: "Hora" + minute: "Minuto" + second: "Segundos" + + helpers: + select: + prompt: "Seleccione" + + submit: + create: 'Crear %{model}' + update: 'Actualizar %{model}' + submit: 'Guardar %{model}' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "no está incluido en la lista" + exclusion: "es reservado" + invalid: "es inválido" + confirmation: "no concuerda" + accepted: "debe ser aceptado" + empty: "no puede estar vacío" + blank: "no puede estar vacío" + exists_already: 'ya existe' + too_long: "demaciado largo (maximo %{count} caracteres)" + too_short: "demaciado corto (minimo is %{count} caracteres)" + wrong_length: "longitud incorrecta (debe ser %{count} caracteres)" + not_a_number: "no es un número" + not_an_integer: "debe ser un entero" + greater_than: "debe ser mayor que %{count}" + greater_than_or_equal_to: "debe ser mayor o igual que %{count}" + equal_to: "debe ser igual a %{count}" + less_than: "debe ser menor que %{count}" + less_than_or_equal_to: "debe ser menor o igual que %{count}" + odd: "debe ser impar" + even: "debe ser par" + invalid_characters: 'no puede tener los caracteres: < > : " / \ | ? *' + + activerecord: + errors: + template: + header: + one: "1 error impidió guardar %{model}" + other: "%{count} errores impidió guardar %{model}" + body: "Los siguientes campos tienen un problema:" + + messages: + taken: "ya está en uso" + record_invalid: "Validación: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: Portapaleles + folder: Carpeta + group: Grupo + permission: Permisos + share_link: Compartir + user: Usuario + user_file: Archivo + + attributes: + folder: + name: Nombre + group: + name: Nombre + share_link: + emails: Correo electrónico + expires_at: Expira + user: + email: Correo + name: Usuario (sin espacios) + password: Clave + user_file: + name: Nombre + attachment_file_name: Archivo + + # APPLICATION SPECIFIC + + # general + back: Atrás + save: Guardar + name: Nombre + email: Correo + submit: Enviar + + your_changes_were_saved: Los cambios fueron guardados correctamente. + already_deleted: "%{type} ya fue borrado." + + # admins/new + create_admin: Crear admin + no_administrator_yet: El Sistema de Gestion de Archivos no tiene un administrador. Crear uno. + create_admin_account: Crear admin + admin_user_created_successfully: El administrador fue creado. Iniciar sesión. + + # clipboard/_show + folder: carpeta + file: archivo + this_folder: esta carpeta + this_file: este archivo + copy: Copiar + copy_folder: Copiar carpeta + copy_file: Copiar archivo + move: Mover + move_folder: Mover carpeta + move_file: Mover archivo + are_you_sure: Está seguro? + delete_item: Borrar + remove_from_clipboard: Borrar desde el portapepeles + clear_clipboard: Limpiar portpapeles + + # files/edit + rename_file: Renombrar archivo + + # files/new + select_file: Seleccionar archivp + upload_file: Subir archivos + upload: Subir + exists_already: ya existe + + #folders/edit + rename_folder: Renombrar Carpeta + + #folders/new + new_folder: Nueva Carpeta + + #folders/show + create_a_new_folder: Crear nueva carpeta + upload_a_file: Subir Archivos + permissions: Permisos + clipboard: Portapapeles + size: KB/MB + date_modified: Fecha modificación + up: subir + edit: Editar + add_to_clipboard: Agregar al portapapeles + download: Descargar + share: Compartir + + #groups/edit + rename_group: Renombrar grupo + + #groups/index + groups: Grupos + create_a_new_group: Crear grupo + + #groups/new + new_group: Nuevo grupo + + #permissions/form + create_permission: Crear + read_permission: Leer + update_permission: Actualizar + delete_permission: Borrar + apply_changes_to_subfolders: Aplicar cabios a las subcarpetas + + create: crear + read: leer + update: actualizar + delete: borrar + + #reset_password/edit + password: Clave + reset_password: Cambiar clave + send_email: Enviar + + #signup/edit + sign_up: Iniciar sesión + + #sessions + username: Usuario + remember_me: Recordarme + sign_in: Iniciar sesión + + #share_links/index + unshare: Descompartir + + #share_links/new + this_share_link: este enlace + share_file: Compartir archivo + you_are_about_to_share_the_following_file: Compartiendo el archivo + emails_to_share_with: Correos electrónicos a enviar el enlace + comma_seperated: Separados por comas + number_of_charachters: Número de caracteres + link_expires: Expira + tomorrow: Mañana + three_days_from_now: En 3 días + one_week_from_now: En 1 semana + ten_days_from_now: En 10 días + two_weeks_from_now: En 2 semanas + three_weeks_from_now: En 3 semanas + one_month_from_now: En 1 mes + two_months_from_now: En 2 meses + three_months_from_now: En 3 meses + half_year_from_now: En 6 meses + share_link_could_not_be_sent: El enclace no se pudo enviar + are_invalid_due_to: "%{email} es inválido" + shared_successfully: El archivo fue compartido. + + #shared/_header + hello: Usuario + settings: Configuraciones + sign_out: Desconectarse + + #shared/_menu + folders: Carpetas + users: Usuarios + shared_files: Compartir Archivos + + #users/_form + member_of_these_groups: Miembro de estos grupos + confirm_password: Confirmar clave + + #users/index + create_a_new_user: Crear usuario + active_users: Usuarios Activos + invited_users: Invitados + expiration_date: Fecha de Expiración + extend_expiration_date: Extender fecha de expiración + + #users/new + new_user: Nuevo usuario + + #admins/controller + admin_user_created_sucessfully: El administrador ha sido creado. + + #application_controller + no_permissions_for_this_type: "No tiene permisos en %{method} para %{type}." + + # clipboard_controller + added_to_clipboard: Agregado al portapapeles. + could_not_copy: "No se pudo copiar. Ya existe un %{type} con el mismo nombre." + could_not_move: "No se pudo mover. Ya existe un %{type} con el mismo nombre." + cannot_move_to_own_subfolder: No se puede mover una carpeta en la subcarpeta. + + # folders_controller + cannot_delete_root_folder: La carpeta raíz no se puede borrar o renombrar. + no_delete_permissions_for_subfolder: No tiene permisos para esta subcarpeta. + + # groups_controller + group_already_deleted: Este grupo fue borrado. + admins_group_cannot_be_deleted: El grupo admins no se puede borrar o renombrar. + + # reset_password_controller + instruction_email_sent: "Un correo fue enviado con las instrucciones a '%{email}'." + password_reset_successfully: La clave fue cambiada. + reset_url_expired: El enlace para cambiar la clave ha expirado, contacte al administrador. + + # signup_controller + signed_up_successfully: Cuenta creada. + sign_url_expired: El enlace para iniciar sesión expiró, contacte al administrador. + + # sessions_controller + credentials_incorrect: Usuario y/o clave incorrecta. + + # users_controller + user_already_deleted: Usuario borrado. + admin_user_cannot_be_deleted: El administrador no puede borrarse. + edit_user: Editar usuario + account_settings: Configuración de cuenta + + # mailers/user_mailer + signup_email_subject: 'Invitación al Sistema de Gestión de Archivos' + reset_password_email_subject: 'Gestión de Archivos: cambio de claves' + share_link_email_subject: 'Gestión de Archivos: se le ha compartido un enlace' diff --git a/config/locales/fr.yml b/config/locales/fr.yml new file mode 100644 index 0000000..9c56dc3 --- /dev/null +++ b/config/locales/fr.yml @@ -0,0 +1,401 @@ +# US English translations for Ruby on Rails +# +# Use this as the base for the locale file of your language. +# see: https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en-US.yml + +"fr": + date: + abbr_day_names: + - dim + - lun + - mar + - mer + - jeu + - ven + - sam + abbr_month_names: + - + - jan. + - fév. + - mar. + - avr. + - mai + - juin + - juil. + - août + - sept. + - oct. + - nov. + - déc. + day_names: + - dimanche + - lundi + - mardi + - mercredi + - jeudi + - vendredi + - samedi + formats: + default: ! '%d/%m/%Y' + long: ! '%e %B %Y' + short: ! '%e %b' + month_names: + - + - janvier + - février + - mars + - avril + - mai + - juin + - juillet + - août + - septembre + - octobre + - novembre + - décembre + order: + - :day + - :month + - :year + datetime: + distance_in_words: + about_x_hours: + one: environ %{count} heure + other: environ %{count} heures + about_x_months: + one: environ %{count} mois + other: environ %{count} mois + about_x_years: + one: environ %{count} an + other: environ %{count} ans + almost_x_years: + one: presque %{count} an + other: presque %{count} ans + half_a_minute: une demi-minute + less_than_x_minutes: + one: moins de %{count} minute + other: moins de %{count} minutes + zero: moins d'une minute + less_than_x_seconds: + one: moins de %{count} seconde + other: moins de %{count} secondes + zero: moins d'une seconde + over_x_years: + one: plus de %{count} an + other: plus de %{count} ans + x_days: + one: ! '%{count} jour' + other: ! '%{count} jours' + x_minutes: + one: ! '%{count} minute' + other: ! '%{count} minutes' + x_months: + one: ! '%{count} mois' + other: ! '%{count} mois' + x_seconds: + one: ! '%{count} seconde' + other: ! '%{count} secondes' + prompts: + day: Jour + hour: Heure + minute: Minute + month: Mois + second: Seconde + year: Année + activerecord: + errors: + format: Le %{attribute} %{message} + messages: + accepted: doit être accepté(e) + blank: doit être rempli(e) + confirmation: ne concorde pas + empty: doit être rempli(e) + equal_to: doit être égal à %{count} + even: doit être pair + exclusion: n'est pas disponible + greater_than: doit être supérieur à %{count} + greater_than_or_equal_to: doit être supérieur ou égal à %{count} + inclusion: n'est pas inclus(e) dans la liste + invalid: n'est pas valide + less_than: doit être inférieur à %{count} + less_than_or_equal_to: doit être inférieur ou égal à %{count} + not_a_number: n'est pas un nombre + not_an_integer: doit être un nombre entier + odd: doit être impair + record_invalid: ! 'La validation a échoué : %{errors}' + taken: n'est pas disponible + too_long: + one: est trop long (pas plus de %{count} caractère) + other: est trop long (pas plus de %{count} caractères) + too_short: + one: est trop court (au moins %{count} caractère) + other: est trop court (au moins %{count} caractères) + wrong_length: + one: ne fait pas la bonne longueur (doit comporter %{count} caractère) + other: ne fait pas la bonne longueur (doit comporter %{count} caractères) + template: + body: ! 'Veuillez vérifier les champs suivants : ' + header: + one: ! 'Impossible d''enregistrer ce(tte) %{model} : %{count} erreur' + other: ! 'Impossible d''enregistrer ce(tte) %{model} : %{count} erreurs' + helpers: + select: + prompt: Veuillez sélectionner + submit: + create: Créer un(e) %{model} + submit: Enregistrer ce(tte) %{model} + update: Modifier ce(tte) %{model} + number: + currency: + format: + delimiter: ! ' ' + format: ! '%n %u' + precision: 2 + separator: ! ',' + significant: false + strip_insignificant_zeros: false + unit: € + format: + delimiter: ! ' ' + precision: 3 + separator: ! ',' + significant: false + strip_insignificant_zeros: false + human: + decimal_units: + format: ! '%n %u' + units: + billion: milliard + million: million + quadrillion: million de milliards + thousand: millier + trillion: billion + unit: '' + format: + delimiter: '' + precision: 2 + significant: true + strip_insignificant_zeros: true + storage_units: + format: ! '%n %u' + units: + byte: + one: octet + other: octets + gb: Go + kb: ko + mb: Mo + tb: To + percentage: + format: + delimiter: '' + precision: + format: + delimiter: '' + support: + array: + last_word_connector: ! ' et ' + two_words_connector: ! ' et ' + words_connector: ! ', ' + time: + am: am + formats: + default: ! '%d %B %Y %H:%M:%S' + long: ! '%A %d %B %Y %H:%M' + short: ! '%d %b %H:%M' + very_short: "%d %b %Y" + date_only: "%B %d, %Y" + time_only: "%H:%M" + pm: pm + + # APPLICATION SPECIFIC + + # general + back: Retour + save: Sauvegarder + name: Nom + email: Email + submit: Valider + + your_changes_were_saved: Vos modifications ont été sauvegardées. + already_deleted: "Désolé mais ce %{type} a déjà été supprimé." + + # admins/new + create_admin: Créer un admin + no_administrator_yet: Boxroom n'a pas encore d'admin. Créez-en un ici. + create_admin_account: Créer un compte admin + admin_user_created_successfully: "L'admin est maintenant créé, vous pouvez vous connecter" + + # clipboard/_show + folder: dossier + file: fichier + this_folder: ce dossier + this_file: ce fichier + copy: Copier + copy_folder: Copier dossier + copy_file: Copier fichier + move: Déplacer + move_folder: Déplacer dossier + move_file: Déplacer fichier + are_you_sure: Etes-vous sûr? + delete_item: supprimer + remove_from_clipboard: Supprimer du presse-papier + clear_clipboard: Vider le presse-papier + + # files/edit + rename_file: Renommer fichier + + # files/new + select_file: Choisir un fichier + upload_file: Déposer des fichiers + upload: Déposer + exists_already: existe déjà + + #folders/edit + rename_folder: Renommer dossier + + #folders/new + new_folder: Nouveau dossier + + #folders/show + create_a_new_folder: Créer un nouveau dossier + upload_a_file: Déposer un fichier + permissions: Permissions + clipboard: Presse-papier + size: Taille + date_modified: Date de modification + up: haut + edit: Modifier + add_to_clipboard: Ajouter au presse-papier + download: Télécharger + share: Partager + + #groups/edit + rename_group: Renommer groupe + + #groups/index + groups: Groupes + create_a_new_group: Créer un nouveau groupe + + #groups/new + new_group: Nouveau groupe + + #permissions/form + create_permission: Créer + read_permission: Lire + update_permission: Modifier + delete_permission: Supprimer + apply_changes_to_subfolders: Appliquer les changements aux sous-dossiers + + create: créer + read: lire + update: Modifier + delete: Supprimer + + #reset_password/edit + password: Mot de passe + reset_password: Réinitialiser le mot de passe + send_email: Envoyer + + #signup/edit + sign_up: Inscription + + #sessions + username: Nom d'utilisateur + remember_me: Se souvenir de moi + sign_in: Identification + + #share_links/index + shared_by: Partage par + unshare: Annuler le partage + + #share_links/new + this_share_link: ce lien de partage + share_file: Partager le fichier + you_are_about_to_share_the_following_file: Vous êtes sur le point de partager le fichier suivant + emails_to_share_with: Adresses email des personnes avec qui vous souhaitez partager + comma_seperated: Séparés par une virgule + number_of_charachters: Nombre de caractères + link_expires: Expiration + tomorrow: Demain + three_days_from_now: Dans 3 jours + one_week_from_now: Dans une semaine + ten_days_from_now: Dans 10 jours + two_weeks_from_now: Dans 2 semaines + three_weeks_from_now: Dans 3 semaines + one_month_from_now: Dans un mois + two_months_from_now: Dans 2 mois + three_months_from_now: Dans 3 mois + half_year_from_now: Dans 6 mois + share_link_could_not_be_sent: Le lien n'a pu être envoyé + are_invalid_due_to: "est invalide à cause de %{email}" + shared_successfully: Le fichier a bien été partagé. + shared_message: Message + optional: Optionnel + + #shared/_header + hello: Bonjour + settings: Réglages + sign_out: Déconnexion + + #shared/_menu + folders: Dossiers + users: Fichiers + shared_files: Fichiers partagés + + #users/_form + member_of_these_groups: Membre de ces groupes + confirm_password: Confirmer le mot de passe + + #users/index + create_a_new_user: Créer un nouvel utilisateur + active_users: Utilisateurs actifs + invited_users: Utilisateurs invités + expiration_date: Date d'expiration + extend_expiration_date: Prolonger la durée de validité + + #users/new + new_user: Nouvel utilisateur + + #admins/controller + admin_user_created_sucessfully: "L'admin est maintenant créé, vous pouvez vous connecter" + + #application_controller + no_permissions_for_this_type: "Vous n'avez pas la permission de %{method} %{type}." + + # clipboard_controller + added_to_clipboard: Ajouté au presse-papier. + could_not_copy: "Copie impossible. Un %{type} du même nom existe déjà." + could_not_move: "Déplacement impossible. A %{type} du même nom existe déjà." + cannot_move_to_own_subfolder: Vous ne pouvez pas déplacer un dossier dans un de ses sous-dossiers. + + # folders_controller + cannot_delete_root_folder: Le dossier racine ne peut pas être supprimé. + no_delete_permissions_for_subfolder: Vous n'avez pas les permissions pour un ou plusieurs sous-dossiers. + + # groups_controller + group_already_deleted: Quelqu'un d'autre a déjà supprimé ce groupe. Action annulée + admins_group_cannot_be_deleted: Le groupe admins ne peut être supprimé ni renommé. + + # reset_password_controller + instruction_email_sent: "Se '%{email}' est présent dans le système, vous allez recevoir un message avec les instructions." + password_reset_successfully: Mot de passe réinitialisé, vous pouvez maintenant vous connecter. + reset_url_expired: L'URL de réinitialisation a expiré, veuillez réessayer à nouveau. + + # signup_controller + signed_up_successfully: "Votre compte est maintenant créé, vous pouvez vous connecter" + sign_url_expired: "L'URL de confirmation a expiré, veuillez contacter l'administrateur." + + # sessions_controller + credentials_incorrect: Nom d'utilisateur ou mot de passe incorrect. Veuillez réessayer. + + # users_controller + user_already_deleted: Quelqu'un d'autre a déjà supprimé l'utilisateur. + admin_user_cannot_be_deleted: L'admin ne peut être supprimé + edit_user: Modifier utilisateur + account_settings: Profil + + # mailers/user_mailer + signup_email_subject: '[Boxroom] Invitiation à créer un compte' + reset_password_email_subject: '[Boxroom] Instructions de réinitialisation mot de passe' + share_link_email_subject: "[Boxroom] %{email} a partagé un fichier avec vous" diff --git a/config/locales/it.yml b/config/locales/it.yml new file mode 100644 index 0000000..cd14739 --- /dev/null +++ b/config/locales/it.yml @@ -0,0 +1,412 @@ +# IT Italian translations for Ruby on Rails +# Author Jessica Marcon +# Use this as the base for the locale file of your language. +# see: https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en-US.yml + + +"it": + date: + formats: + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B %Y" + + day_names: [Domenica, Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato] + abbr_day_names: [Dom, Lun, Mar, Mer, Gio, Ven, Sab] + + month_names: [~, Gennaio, Febbraio, Marzo, Aprile, Maggio, Giugno, Luglio, Agosto, Settembre, Ottobre, Novembre, Dicembre] + abbr_month_names: [~, Gen, Feb, Mar, Apr, Mag, Giu, Lug, Ago, Set, Ott, Nov, Dic] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a %d %b %Y, %H:%M %:z" + short: "%d %b %Y %H:%M" + very_short: "%d %b %Y" + long: "%d %B %Y, %H:%M" + am: "am" + pm: "pm" + + support: + array: + words_connector: ", " + two_words_connector: " e " + last_word_connector: ", e " + + select: + prompt: "Seleziona" + + number: + format: + precision: 2 + separator: "," + delimiter: "." + significant: false + strip_insignificant_zeros: false + + currency: + format: + unit: "€" + format: "%u%n" + separator: "," + delimiter: "." + precision: 2 + significant: false + strip_insignificant_zeros: false + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: "Mille" + million: + one: "Milione" + others: "Milioni" + billion: + one: "Miliardo" + others: "Miliardi" + trillion: + one: "Trilione" + others: "Trilioni" + quadrillion: + one: "Quadrilione" + others: "Quadrilioni" + + datetime: + distance_in_words: + half_a_minute: "mezzo minuto" + less_than_x_seconds: + one: "meno di 1 secondo" + other: "meno di %{count} secondi" + x_seconds: + one: "1 secondo" + other: "%{count} secondi" + less_than_x_minutes: + one: "meno di un minuto" + other: "meno di %{count} minuti" + x_minutes: + one: "1 minuto" + other: "%{count} minuti" + about_x_hours: + one: "circa un'ora" + other: "circa %{count} ore" + x_days: + one: "1 giorno" + other: "%{count} giorni" + about_x_months: + one: "circa 1 mese" + other: "circa %{count} mesi" + x_months: + one: "1 mese" + other: "%{count} mese" + about_x_years: + one: "circa 1 anno" + other: "circa %{count} anni" + over_x_years: + one: "oltre 1 anno" + other: "oltre %{count} anni" + almost_x_years: + one: "quasi 1 anno" + other: "quasi %{count} anni" + prompts: + year: "Anno" + month: "Mese" + day: "Giorno" + hour: "Ora" + minute: "Minuto" + second: "Secondi" + + helpers: + select: + prompt: "Seleziona" + + submit: + create: 'Crea %{model}' + update: 'Carica %{model}' + submit: 'Salva %{model}' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "non è incluso nella lista" + exclusion: "è riservato" + invalid: "non è valido" + confirmation: "non corrisponde" + accepted: "dev'essere accettato" + empty: "non può essere vuoto" + blank: "non può essere vuoto" + exists_already: 'esiste già' + too_long: "è troppo lungo (massimo %{count} caratteri)" + too_short: "è troppo corto (minimo %{count} caratteri)" + wrong_length: "la lunghezza è sbagliata (dovrebbe essere di %{count} caratteri)" + not_a_number: "non è un numero" + not_an_integer: "deve essere un numero intero" + greater_than: "deve essere maggiore di %{count}" + greater_than_or_equal_to: "deve essere maggiore o uguale a %{count}" + equal_to: "deve essere uguale a %{count}" + less_than: "deve essere inferiore a %{count}" + less_than_or_equal_to: "deve essere inferiore o uguale a %{count}" + odd: "deve essere dispari" + even: "deve essere ancora" + invalid_characters: 'non può contenere questo tipo di carattere: < > : " / \ | ? *' + + activerecord: + errors: + template: + header: + one: "1 errore ha impedito a %{model} di essere salvato" + other: "%{count} errori hanno impedito a %{model} di essere salvato" + body: "Sono stati rilevati dei problemi con questi campi:" + + messages: + taken: "è già in uso" + record_invalid: "Validation failed: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: Appunti + folder: Cartella + group: Gruppo + permission: Permessi + share_link: Condividi link + user: Utente + user_file: File + + attributes: + folder: + name: Nome + group: + name: Nome + share_link: + emails: Indirizzo Email + expires_at: Scade + user: + email: Email + name: Nome + password: Password + user_file: + name: Nome + attachment_file_name: File + + # APPLICATION SPECIFIC + + # general + back: Indietro + save: Salva + name: Nome + email: Email + submit: Invia + + your_changes_were_saved: Le tue modifiche sono state salvate con successo. + already_deleted: "Qualcuno ha cancellato %{type}. Azione non valida." + + # admins/new + create_admin: Crea amministratore + no_administrator_yet: "Boxroom non ha ancora un amministratore: aggiungilo ora." + create_admin_account: Crea account amministratore + admin_user_created_successfully: "L'utente amministratore è stato aggiunto con successo! Ora puoi accedere." + + # clipboard/_show + folder: cartella + file: file + this_folder: questa cartella + this_file: questo file + copy: Copia + copy_folder: Copia cartella + copy_file: Copia file + move: Sposta + move_folder: Sposta cartella + move_file: Sposta file + are_you_sure: Sei sicuro? + delete_item: Elimina + remove_from_clipboard: Elimina dagli appunti + clear_clipboard: Cancella appunti + + # files/edit + rename_file: Rinomina file + + # files/new + select_file: Seleziona file + upload_file: Carica file + upload: Carica + exists_already: esiste già + + #folders/edit + rename_folder: Rinomina cartella + + #folders/new + new_folder: Nuova cartella + + #folders/show + create_a_new_folder: Crea una nuova cartella + upload_a_file: Carica file + permissions: Permessi + clipboard: Appunti + size: Dimensione + date_modified: Data modifica + up: up + edit: Modifica + add_to_clipboard: Copia negli appunti + download: Scarica + share: Condividi + + #groups/edit + rename_group: Rinomina gruppo + + #groups/index + groups: Gruppi + create_a_new_group: Aggiungi un nuovo gruppo + + #groups/new + new_group: Nuovo gruppo + + #permissions/form + create_permission: Crea + read_permission: Lettura + update_permission: Modifica + delete_permission: Elimina + apply_changes_to_subfolders: Applica modifiche alle sottocartelle + + create: crea + read: lettura + update: modifica + delete: elimina + + #reset_password/edit + password: Password + reset_password: Modifica password + send_email: Invia + + #signup/edit + sign_up: Sign up + + #sessions + username: Username + remember_me: Ricordami + sign_in: Accedi + + #share_links/index + shared_by: Condiviso da + unshare: Annulla la condivisione + + #share_links/new + this_share_link: this share link + share_file: Condividi file + you_are_about_to_share_the_following_file: Stai per condividere il seguente file + emails_to_share_with: Inserisci gli indirizzi email con i quali vuoi condividere il file + comma_seperated: Separati da una virgola + number_of_charachters: Numero di caratteri + link_expires: Scadenza + tomorrow: Domani + three_days_from_now: Tre giorni da adesso + one_week_from_now: Una settimana da adesso + ten_days_from_now: Dieci giorni da adesso + two_weeks_from_now: Due settimane da adesso + three_weeks_from_now: Tre settimane da adesso + one_month_from_now: Un mese da adesso + two_months_from_now: Due mesi da adesso + three_months_from_now: Tre mesi da adesso + half_year_from_now: Sei mesi da adesso + share_link_could_not_be_sent: Il link per il download non è stato inviato + are_invalid_due_to: " %{email} non è valido" + shared_successfully: Il file è stato condiviso con successo. + shared_message: Messaggio + optional: Opzionale + + #shared/_header + hello: Salve + settings: Impostazioni + sign_out: Esci + + #shared/_menu + folders: Cartelle + users: Utenti + shared_files: Condivisione file + + #users/_form + member_of_these_groups: Membro di questi gruppi + confirm_password: Conferma password + + #users/index + create_a_new_user: Aggiungi un nuovo utente + active_users: Active users + invited_users: Invited users + expiration_date: Expiration date + extend_expiration_date: Extend expiration date + + #users/new + new_user: Nuovo utente + + #admins/controller + admin_user_created_sucessfully: "L'utente amministratore è stato aggiunto con successo! Ora puoi accedere." + + #application_controller + no_permissions_for_this_type: "Non hai %{method} i permessi per %{type}." + + # clipboard_controller + added_to_clipboard: Aggiunto con successo negli appunti. + could_not_copy: "Non puoi copiare. Il %{type} con lo stesso nome esiste già." + could_not_move: "Non puoi spostare. Il %{type} con lo stesso nome esiste già." + cannot_move_to_own_subfolder: Non puoi spostare la cartella nella propria sottocartella. + + # folders_controller + cannot_delete_root_folder: La cartella principale non può essere cancellata o rinominata. + no_delete_permissions_for_subfolder: "Non hai il permesso di cancellare una sottocartella." + + # groups_controller + group_already_deleted: Qualcuno ha cancellato questo gruppo. Azione non valida. + admins_group_cannot_be_deleted: Il gruppo amministratore non può essere cancellato o rinominato. + + # reset_password_controller + instruction_email_sent: "L'email con le istruzioni è stata inviata. Controlla la tua email." + password_reset_successfully: La password è stata modificata con successo. Ora puoi accedere. + reset_url_expired: Il link per cambiare la password è scaduto. Riprova. + + # signup_controller + signed_up_successfully: Account created successfully. You can now sign in. + sign_url_expired: The URL for signing up expired. Please contact the administrator. + + # sessions_controller + credentials_incorrect: "Username e/o password errati. Riprova." + + # users_controller + user_already_deleted: "Qualcuno ha cancellato l'utente. Azione non valida." + admin_user_cannot_be_deleted: "L'utente amministratore non può essere cancellato." + edit_user: Inserisci utente + account_settings: Impostazioni account + + # mailers/user_mailer + signup_email_subject: '[Boxroom] Sign up invitation' + share_link_email_subject: '[Boxroom] %{email} ha condiviso un file con te' + reset_password_email_subject: '[Boxroom] Istruzioni per modificare la password' diff --git a/config/locales/nl.yml b/config/locales/nl.yml new file mode 100644 index 0000000..2a6093d --- /dev/null +++ b/config/locales/nl.yml @@ -0,0 +1,406 @@ +# Dutch translations for Ruby on Rails, based on US English template +# Original version by Ariejan de Vroom +# - Sponsored by Kabisa ICT - http://kabisa.nl +# Rails 3 update by Floris Huetink (github: florish) + +nl: + date: + formats: + default: "%d/%m/%Y" + short: "%e %b" + long: "%e %B %Y" + only_day: "%e" + + day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag] + abbr_day_names: [zon, maa, din, woe, don, vri, zat] + + month_names: [~, januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november, december] + abbr_month_names: [~, jan, feb, mar, apr, mei, jun, jul, aug, sep, okt, nov, dec] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a %d %b %Y %H:%M:%S %Z" + short: "%d %b %Y %H:%M" + very_short: "%d %b %Y" + long: "%d %B %Y %H:%M" + time: "%H:%M" + only_second: "%S" + am: "'s ochtends" + pm: "'s middags" + + support: + array: + words_connector: ", " + two_words_connector: " en " + last_word_connector: " en " + + select: + prompt: "Selecteer" + + number: + format: + separator: "," + delimiter: "." + precision: 2 + significant: false + strip_insignificant_zeros: false + + currency: + format: + format: "%u%n" + unit: "€" + separator: "," + delimiter: "." + precision: 2 + significant: false + strip_insignificant_zeros: false + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: "duizend" + million: "miljoen" + billion: "miljard" + trillion: "biljoen" + quadrillion: "biljard" + + datetime: + distance_in_words: + half_a_minute: "een halve minuut" + less_than_x_seconds: + one: "minder dan \xC3\xA9\xC3\xA9n seconde" + other: "minder dan %{count} seconden" + x_seconds: + one: "1 seconde" + other: "%{count} seconden" + less_than_x_minutes: + one: "minder dan \xC3\xA9\xC3\xA9n minuut" + other: "minder dan %{count} minuten" + x_minutes: + one: "1 minuut" + other: "%{count} minuten" + about_x_hours: + one: "ongeveer \xC3\xA9\xC3\xA9n uur" + other: "ongeveer %{count} uur" + x_days: + one: "1 dag" + other: "%{count} dagen" + about_x_months: + one: "ongeveer \xC3\xA9\xC3\xA9n maand" + other: "ongeveer %{count} maanden" + x_months: + one: "1 maand" + other: "%{count} maanden" + about_x_years: + one: "ongeveer \xC3\xA9\xC3\xA9n jaar" + other: "ongeveer %{count} jaar" + over_x_years: + one: "meer dan \xC3\xA9\xC3\xA9n jaar" + other: "meer dan %{count} jaar" + almost_x_years: + one: "bijna \xC3\xA9\xC3\xA9n jaar" + other: "bijna %{count} jaar" + prompts: + year: "jaar" + month: "maand" + day: "dag" + hour: "uur" + minute: "minuut" + second: "seconde" + + helpers: + select: + prompt: "Selecteer" + + submit: + create: '%{model} toevoegen' + update: '%{model} opslaan' + submit: '%{model} opslaan' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "is niet in de lijst opgenomen" + exclusion: "is niet beschikbaar" + invalid: "is ongeldig" + confirmation: "klopt niet" + accepted: "moet worden geaccepteerd" + empty: " mag niet leeg zijn" + blank: "mag niet leeg zijn" + exists_already: 'bestaat al' + too_long: "is te lang (maximaal %{count} tekens)" + too_short: "is te kort (minimaal %{count} tekens)" + wrong_length: "heeft onjuiste lengte (moet %{count} tekens lang zijn)" + not_a_number: "is geen getal" + not_an_integer: "moet een geheel getal zijn" + greater_than: "moet groter zijn dan %{count}" + greater_than_or_equal_to: "moet groter dan of gelijk zijn aan %{count}" + equal_to: "moet gelijk zijn aan %{count}" + less_than: "moet minder zijn dan %{count}" + less_than_or_equal_to: "moet minder dan of gelijk zijn aan %{count}" + odd: "moet oneven zijn" + even: "moet even zijn" + invalid_characters: 'mag geen van deze tekens bevatten: < > : " / \ | ? *' + + activerecord: + errors: + template: + header: + one: "1 fout gevonden: %{model} niet opgeslagen" + other: "%{count} fouten gevonden: %{model} niet opgeslagen" + body: "Controleer de volgende velden:" + + messages: + taken: "is al in gebruik" + record_invalid: "Validatie mislukt: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: Klembord + folder: Map + group: Groep + permission: Bevoegdheid + share_link: Gedeelde link + user: Gebruiker + user_file: Bestand + + attributes: + folder: + name: Naam + group: + name: Naam + share_link: + emails: E-mailadressen + expires_at: Vervaldatum + user: + email: E-mail + name: Naam + password: Wachtwoord + user_file: + name: Naam + attachment_file_name: Bestand + + # APPLICATION SPECIFIC + + # general + back: Terug + save: Opslaan + name: Naam + email: E-mail + submit: Opslaan + + your_changes_were_saved: Uw wijzigingen zijn opgeslagen. + already_deleted: "Iemand heeft %{type} reeds verwijderd." + + # admins/new + create_admin: Admin aanmaken + no_administrator_yet: Boxroom heeft nog geen administrator. Deze kunt u nu aanmaken. + create_admin_account: Admin gebruiker aanmaken + admin_user_created_successfully: De admin gebruiker is aangemaakt. U kunt nu inloggen. + + # clipboard/_show + folder: map + file: bestand + this_folder: deze map + this_file: dit bestand + copy: Kopieer + copy_folder: Map kopiëren + copy_file: Bestand kopiëren + move: Verplaatsen + move_folder: Map verplaatsen + move_file: Bestand verplaatsen + are_you_sure: Weet u het zeker? + delete_item: Verwijderen + remove_from_clipboard: Verwijder van klembord + clear_clipboard: Klembord legen + + # files/edit + rename_file: Bestandsnaam wijzigen + + # files/new + select_file: Selecteer bestand + upload_file: Bestanden uploaden + upload: Uploaden + exists_already: bestaat al + + #folders/edit + rename_folder: Mapnaam wijzigen + + #folders/new + new_folder: Nieuwe map + + #folders/show + create_a_new_folder: Nieuwe map aanmaken + upload_a_file: Bestanden uploaden + permissions: Bevoegdheden + clipboard: Klembord + size: Grootte + date_modified: Datum gewijzigd + up: omhoog + edit: Wijzigen + add_to_clipboard: Op klembord plaatsen + download: Download + share: Delen + + #groups/edit + rename_group: Groepnaam wijzigen + + #groups/index + groups: Groepen + create_a_new_group: Nieuwe groep aanmaken + + #groups/new + new_group: Nieuwe groep + + #permissions/form + create_permission: Aanmaken + read_permission: Lezen + update_permission: Wijzigen + delete_permission: Verwijderen + apply_changes_to_subfolders: Toepassen op submappen + + create: aanmaak + read: lees + update: wijzig + delete: verwijder + + #reset_password/edit + password: Wachtwoord + reset_password: Wachtwoord resetten + send_email: Verzenden + + #signup/edit + sign_up: Aanmelden + + #sessions + username: Gebruikersnaam + remember_me: Onthoud mij + sign_in: Inloggen + + #share_links/index + shared_by: Gedeeld door + unshare: Delen ongedaan maken + + #share_links/new + this_share_link: this share link + share_file: Bestand delen + you_are_about_to_share_the_following_file: U staat op het punt het volgende bestand te delen + emails_to_share_with: E-mailadressen van de mensen waarmee u dit bestand wilt delen + comma_seperated: Gescheiden door komma's + number_of_charachters: Aantal tekens + link_expires: Vervaldatum + tomorrow: Morgen + three_days_from_now: Over drie dagen + one_week_from_now: Over een week + ten_days_from_now: Over tien dagen + two_weeks_from_now: Over twee weken + three_weeks_from_now: Over drie weken + one_month_from_now: Over een maand + two_months_from_now: Over twee maanden + three_months_from_now: Over drie maanden + half_year_from_now: Over een half jaar + share_link_could_not_be_sent: De download link kon niet verstuurd worden + are_invalid_due_to: "is ongeldig vanwege %{email}" + shared_successfully: De download link is verstuurd. + shared_message: Bericht + optional: Niet verplicht + + #shared/_header + hello: Hallo + settings: Instellingen + sign_out: Uitloggen + + #shared/_menu + folders: Mappen + users: Gebruikers + shared_files: Gedeelde bestanden + + #users/_form + member_of_these_groups: Lid van deze groepen + confirm_password: Bevestig wachtwoord + + #users/index + create_a_new_user: Nieuwe gebruiker aanmaken + active_users: Actieve gebruikers + invited_users: Uitgenodigde gebruikers + expiration_date: Vervaldatum + extend_expiration_date: Vervaldatum uitstellen + + #users/new + new_user: Nieuwe gebruiker + + #admins/controller + admin_user_created_sucessfully: De admin gebruiker is aangemaakt. U kunt nu inloggen. + + #application_controller + no_permissions_for_this_type: "U heeft geen %{method} bevoegdheden voor %{type}." + + # clipboard_controller + added_to_clipboard: Succesvol toegevoegd aan klembord. + could_not_copy: "Kopiëren mislukt. Een %{type} met dezelfde naam bestaat al." + could_not_move: "Verplaatsen mislukt. Een %{type} met dezelfde naam bestaat al." + cannot_move_to_own_subfolder: U kunt een map niet verplaatsen naar zijn eigen submap. + + # folders_controller + cannot_delete_root_folder: Root folder kan niet verwijderd of gewijzigd worden. + no_delete_permissions_for_subfolder: U heeft geen bevoegdheden om één van de submappen te verwijderen. + + # groups_controller + group_already_deleted: Iemand anders heeft deze groep verwijderd. De actie is afgebroken. + admins_group_cannot_be_deleted: De admin groep kan niet verwijderd of gewijzigd worden. + + # reset_password_controller + instruction_email_sent: "Een e-mail met instructies is verzonden als '%{email}' aanwezig is in ons systeem." + password_reset_successfully: Uw wachtwoord is gereset. U kunt nu inloggen. + reset_url_expired: De URL om uw wachtwoord te resetten is ongeldig. Probeer het nogmaals. + + # signup_controller + signed_up_successfully: Account aangemaakt. U kunt nu inloggen. + sign_url_expired: The URL om aan te melden is ongeldig. Neem contact op met de administrator. + + # sessions_controller + credentials_incorrect: Gebruikersnaam en/of wachtwoord was incorrect. Probeer het nogmaals. + + # users_controller + user_already_deleted: Iemand anders heeft deze gebruiker verwijderd. De actie is afgebroken. + admin_user_cannot_be_deleted: De admin gebruiker kan niet verwijderd worden. + edit_user: Gebruiker wijzigen + account_settings: Mijn instellingen + + # mailers/user_mailer + signup_email_subject: '[Boxroom] Uitnodiging om aan te melden' + reset_password_email_subject: '[Boxroom] Instructies om uw wachtwoord te resetten' + share_link_email_subject: '[Boxroom] %{email} heeft een bestand met u gedeeld' diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml new file mode 100644 index 0000000..fabde23 --- /dev/null +++ b/config/locales/zh-CN.yml @@ -0,0 +1,404 @@ +# Simplified Chinese translations + +# The base of this locale file is https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/zh-CN.yml + +"zh-CN": + date: + formats: + default: "%Y-%m-%d" + short: "%b%d日" + long: "%Y年%b%d日" + + day_names: [星期天, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六] + abbr_day_names: [周日, 周一, 周二, 周三, 周四, 周五, 周六] + + month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %Y年%B%d日 %H:%M:%S %z" + short: "%Y年%B%d日 %H:%M" + very_short: "%Y年%B%d日" + long: "%Y年%B%d日 %H:%M" + date_only: "%Y年%B%d日" + time_only: "%H:%M" + am: 上午 + pm: 下午 + + support: + array: + words_connector: "," + two_words_connector: "和" + last_word_connector: ",和" + + select: + prompt: "请选择" + + number: + format: + separator: "." + delimiter: "," + precision: 3 + significant: false + strip_insignificant_zeros: false + + currency: + format: + format: "%u%n" + unit: "¥" + separator: "." + delimiter: "," + precision: 2 + significant: false + strip_insignificant_zeros: false + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + one: "字节" + other: "字节" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + decimal_units: + format: "%n %u" + units: + unit: "" + thousand: 千 + million: 百万 + billion: 十亿 + trillion: 万亿 + quadrillion: 千万亿 + + datetime: + distance_in_words: + half_a_minute: "半分钟" + less_than_x_seconds: + one: "不到1秒" + other: "不到%{count}秒" + x_seconds: + one: "1秒" + other: "%{count}秒" + less_than_x_minutes: + one: "不到1分钟" + other: "不到%{count}分钟" + x_minutes: + one: "1分钟" + other: "%{count}分钟" + about_x_hours: + one: "大约1小时" + other: "大约%{count}小时" + x_days: + one: "1天" + other: "%{count}天" + about_x_months: + one: "大约1月" + other: "大约%{count}月" + x_months: + one: "1月" + other: "%{count}月" + about_x_years: + one: "大约1年" + other: "大约%{count}年" + over_x_years: + one: "1年多" + other: "%{count}年多" + almost_x_years: + one: "大约1年" + other: "大约%{count}年" + prompts: + year: "年" + month: "月" + day: "日" + hour: "时" + minute: "分" + second: "秒" + + helpers: + select: + prompt: "请选择" + + submit: + create: '新建%{model}' + update: '更新%{model}' + submit: '提交%{model}' + + errors: + format: "%{attribute} %{message}" + + messages: &errors_messages + inclusion: "不包含于列表中" + exclusion: "是保留关键字" + invalid: "是无效的" + confirmation: "不一致" + accepted: "必须同意" + empty: "不能为空" + blank: "不能为空" + exists_already: '已经存在' + too_long: "太长(最多%{count}字节)" + too_short: "太短(至少%{count}字节)" + wrong_length: "长度不对(应该是%{count}字节)" + not_a_number: "不是数字" + not_an_integer: "必须是整数" + greater_than: "必须大于%{count}" + greater_than_or_equal_to: "必须大于或等于%{count}" + equal_to: "必须等于%{count}" + less_than: "必须小于%{count}" + less_than_or_equal_to: "必须小于或等于%{count}" + odd: "必须是单数" + even: "必须是双数" + invalid_characters: '不能包括这些字符: < > : " / \ | ? *' + + activerecord: + errors: + template: + header: + one: "有1个错误导致%{model}无法被保存" + other: "有%{count}个错误发生导致%{model}无法被保存" + body: "如下字段出现错误" + + messages: + taken: "已经被使用" + record_invalid: "验证失败: %{errors}" + <<: *errors_messages + + full_messages: + format: "%{attribute} %{message}" + + models: + clipboard: 剪贴板 + folder: 文件夹 + group: 群组 + permission: 权限 + share_link: 共享链接 + user: 用户 + user_file: 文件 + + attributes: + folder: + name: 名称 + group: + name: 名称 + share_link: + emails: 邮箱 + expires_at: 失效期 + user: + email: 邮箱 + name: 用户名 + password: 密码 + user_file: + name: 文件名 + attachment_file_name: 文件 + + # APPLICATION SPECIFIC + + # general + back: 返回 + save: 保存 + name: 名字 + email: 邮箱 + submit: 提交 + + your_changes_were_saved: "你的修改已保存" + already_deleted: "对不起, 但是%{type}已被删掉了." + + # admins/new + create_admin: 新增管理员 + no_administrator_yet: Boxroom还没有管理员。请在此新增一个。 + create_admin_account: 新增管理员户头 + admin_user_created_successfully: 管理员户头已添加成功。您可以登录了。 + + # clipboard/_show + folder: 文件夹 + file: 文件 + this_folder: 此文件夹 + this_file: 此文件 + copy: 拷贝 + copy_folder: 拷贝文件夹 + copy_file: 拷贝文件 + move: 移动 + move_folder: 移动文件夹 + move_file: 移动文件 + are_you_sure: 您确定? + delete_item: 删除 + remove_from_clipboard: 从剪贴板删除 + clear_clipboard: 清空剪贴板 + + # files/edit + rename_file: 重新命名文件 + + # files/new + select_file: 选择文件 + upload_file: 上传文件 + upload: 上传 + exists_already: 已存在 + + #folders/edit + rename_folder: 重新命名文件夹 + + #folders/new + new_folder: 新文件夹 + + #folders/show + create_a_new_folder: 新增新文件夹 + upload_a_file: 上传文件 + permissions: 权限 + clipboard: 剪贴板 + size: 大小 + date_modified: 修改日期 + up: 上一层 + edit: 编辑 + add_to_clipboard: 加到剪贴板 + download: 下载 + share: 共享 + + #groups/edit + rename_group: 群组重新命名 + + #groups/index + groups: 群组 + create_a_new_group: 新增群组 + + #groups/new + new_group: 新群组 + + #permissions/form + create_permission: 新增 + read_permission: 可读 + update_permission: 可写 + delete_permission: 删除 + apply_changes_to_subfolders: 应用到所有子文件夹 + + create: 新增 + read: 读 + update: 写 + delete: 删除 + + #reset_password/edit + password: 密码 + reset_password: 重设密码 + send_email: 发送 + + #signup/edit + sign_up: 注册 + + #sessions + username: 用户名 + remember_me: 记住我 + sign_in: 登录 + + #share_links/index + shared_by: 共享人 + unshare: 撤销共享 + + #share_links/new + this_share_link: 这个共享链接 + share_file: 共享文件 + you_are_about_to_share_the_following_file: 您就要共享以下文件 + emails_to_share_with: 您想与之共享文件的人的邮箱地址 + comma_seperated: 由逗号分隔的 + number_of_charachters: 字数 + link_expires: 过期 + tomorrow: 明天 + three_days_from_now: 三天后 + one_week_from_now: 一星期后 + ten_days_from_now: 两天后 + two_weeks_from_now: 两星期后 + three_weeks_from_now: 三个星期后 + one_month_from_now: 一个月后 + two_months_from_now: 两个月后 + three_months_from_now: 三个月后 + half_year_from_now: 半年后 + share_link_could_not_be_sent: 不能发送出下载链接 + are_invalid_due_to: "因%{email}而无效" + shared_successfully: 成功共享文件 + shared_message: 消息 + optional: 可选 + + #shared/_header + hello: 您好 + settings: 设置 + sign_out: 退出 + + #shared/_menu + folders: 文件夹 + users: 用户 + shared_files: 共享的文件 + + #users/_form + member_of_these_groups: 属于群组 + confirm_password: 确认密码 + + #users/index + create_a_new_user: 新增用户 + active_users: 已激活的用户 + invited_users: 已邀请的用户 + expiration_date: 失效期 + extend_expiration_date: 推迟失效期 + + #users/new + new_user: 新用户 + + #admins/controller + admin_user_created_sucessfully: 管理员用户已添加好。可以登录了。 + + #application_controller + no_permissions_for_this_type: "您没有对%{type}的%{method}权限." + + # clipboard_controller + added_to_clipboard: 已成功添加到剪贴板。 + could_not_copy: "不能拷贝。同样名字的%{type}已存在。" + could_not_move: "不能移动。同样名字的%{type}已存在。" + cannot_move_to_own_subfolder: 您不能把文件夹放到其子文件夹里。 + + # folders_controller + cannot_delete_root_folder: 根文件夹不能删除或重新命名。 + no_delete_permissions_for_subfolder: 您没有删除其中某个子文件夹的权限。 + + # groups_controller + group_already_deleted: 有人已删掉此群组。您的操作已被撤销。 + admins_group_cannot_be_deleted: 管理员群组不能被删掉。 + + # reset_password_controller + instruction_email_sent: "如果'%{email}'在本系统中,我们会发出邮件告诉您如何重设密码。" + password_reset_successfully: 您的密码已成功重设。您可以登录了。 + reset_url_expired: 这个重设密码的网址已失效。请重试。 + + # signup_controller + signed_up_successfully: 户头注册成功。您可以登录了。 + sign_url_expired: 这个注册户头的网址已失效。请联系管理员。 + + # sessions_controller + credentials_incorrect: 用户名或密码不正确。请重试。 + + # users_controller + user_already_deleted: 有人已删掉此用户。您的操作已被撤销。 + admin_user_cannot_be_deleted: 此管理员不能被删除。 + edit_user: 编辑用户 + account_settings: 用户设置 + + # mailers/user_mailer + signup_email_subject: '[Boxroom] 注册邀请' + reset_password_email_subject: '[Boxroom] 重设密码的步骤' + share_link_email_subject: '[Boxroom] %{email}与您共享了文件' diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..41723ea --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,43 @@ +Boxroom::Engine.routes.draw do + get '/file_exists', :to => 'files#exists' + get '/signin', :to => 'sessions#new', :as => 'signin' + delete '/signout', :to => 'sessions#destroy' + + # Resources + resources :admins, :only => [:new, :create] + resources :sessions, :only => [:new, :create, :destroy] + resources :reset_password, :except => [:index, :show, :destroy] + resources :signup, :only => [:edit, :update] + resources :groups, :except => :show + resources :files, :except => [:index, :new, :create] + resources :share_links, :only => [:index, :show, :destroy] + + resources :users, :except => :show do + put :extend, :on => :member + end + + resources :clipboard, :only => [:create, :destroy] do + post :copy, :on => :member + post :move, :on => :member + put :reset, :on => :member + end + + # Update a collection of permissions + resources :permissions, :only => :update_multiple do + put :update_multiple, :on => :collection + end + + # Nested resources + resources :folders, :shallow => true, :except => [:new, :create] do + resources :folders, :only => [:new, :create] + resources :files, :only => [:new, :create] + end + + resources :files, :shallow => :true, :only => :show do + resources :share_links, :only => [:new, :create] + end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + root :to => "folders#index" +end diff --git a/db/migrate/20100930062939_create_users.rb b/db/migrate/20100930062939_create_users.rb new file mode 100644 index 0000000..aec191a --- /dev/null +++ b/db/migrate/20100930062939_create_users.rb @@ -0,0 +1,20 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :boxroom_users do |t| + t.string :name + t.string :email + t.string :hashed_password + t.string :password_salt + t.string :is_admin + t.string :access_key + t.string :remember_token + t.string :reset_password_token + t.datetime :reset_password_token_expires_at + t.timestamps + end + end + + def self.down + drop_table :boxroom_users + end +end diff --git a/db/migrate/20100930091426_create_folders.rb b/db/migrate/20100930091426_create_folders.rb new file mode 100644 index 0000000..19490b4 --- /dev/null +++ b/db/migrate/20100930091426_create_folders.rb @@ -0,0 +1,14 @@ +class CreateFolders < ActiveRecord::Migration + def self.up + create_table :boxroom_folders do |t| + t.string :name + t.references :user + t.references :parent + t.timestamps + end + end + + def self.down + drop_table :boxroom_folders + end +end diff --git a/db/migrate/20100930091451_create_groups.rb b/db/migrate/20100930091451_create_groups.rb new file mode 100644 index 0000000..3200872 --- /dev/null +++ b/db/migrate/20100930091451_create_groups.rb @@ -0,0 +1,12 @@ +class CreateGroups < ActiveRecord::Migration + def self.up + create_table :boxroom_groups do |t| + t.string :name + t.timestamps + end + end + + def self.down + drop_table :boxroom_groups + end +end diff --git a/db/migrate/20101002122244_create_user_files.rb b/db/migrate/20101002122244_create_user_files.rb new file mode 100644 index 0000000..9740141 --- /dev/null +++ b/db/migrate/20101002122244_create_user_files.rb @@ -0,0 +1,17 @@ +class CreateUserFiles < ActiveRecord::Migration + def self.up + create_table :boxroom_user_files do |t| + t.string :attachment_file_name + t.string :attachment_content_type + t.integer :attachment_file_size + t.datetime :attachment_updated_at + t.references :folder + t.references :user + t.timestamps + end + end + + def self.down + drop_table :boxroom_user_files + end +end diff --git a/db/migrate/20101005071402_create_permissions.rb b/db/migrate/20101005071402_create_permissions.rb new file mode 100644 index 0000000..615d0e5 --- /dev/null +++ b/db/migrate/20101005071402_create_permissions.rb @@ -0,0 +1,16 @@ +class CreatePermissions < ActiveRecord::Migration + def self.up + create_table :boxroom_permissions do |t| + t.references :folder + t.references :group + t.boolean :can_create + t.boolean :can_read + t.boolean :can_update + t.boolean :can_delete + end + end + + def self.down + drop_table :boxroom_permissions + end +end diff --git a/db/migrate/20101005071508_create_groups_users.rb b/db/migrate/20101005071508_create_groups_users.rb new file mode 100644 index 0000000..cc1839f --- /dev/null +++ b/db/migrate/20101005071508_create_groups_users.rb @@ -0,0 +1,12 @@ +class CreateGroupsUsers < ActiveRecord::Migration + def self.up + create_table :boxroom_groups_users, :id => false do |t| + t.references :group + t.references :user + end + end + + def self.down + drop_table :boxroom_groups_users + end +end diff --git a/db/migrate/20110106045148_drop_column_user_id_from_folders.rb b/db/migrate/20110106045148_drop_column_user_id_from_folders.rb new file mode 100644 index 0000000..6c50a73 --- /dev/null +++ b/db/migrate/20110106045148_drop_column_user_id_from_folders.rb @@ -0,0 +1,9 @@ +class DropColumnUserIdFromFolders < ActiveRecord::Migration + def self.up + remove_column :boxroom_folders, :user_id + end + + def self.down + add_column :boxroom_folders, :user_id, :integer + end +end diff --git a/db/migrate/20110106045414_drop_column_user_id_from_user_files.rb b/db/migrate/20110106045414_drop_column_user_id_from_user_files.rb new file mode 100644 index 0000000..ae392f7 --- /dev/null +++ b/db/migrate/20110106045414_drop_column_user_id_from_user_files.rb @@ -0,0 +1,9 @@ +class DropColumnUserIdFromUserFiles < ActiveRecord::Migration + def self.up + remove_column :boxroom_user_files, :user_id + end + + def self.down + add_column :boxroom_user_files, :user_id, :integer + end +end diff --git a/db/migrate/20110529123402_drop_column_access_key_from_users.rb b/db/migrate/20110529123402_drop_column_access_key_from_users.rb new file mode 100644 index 0000000..37b5fcb --- /dev/null +++ b/db/migrate/20110529123402_drop_column_access_key_from_users.rb @@ -0,0 +1,9 @@ +class DropColumnAccessKeyFromUsers < ActiveRecord::Migration + def self.up + remove_column :boxroom_users, :access_key + end + + def self.down + add_column :boxroom_users, :access_key, :string + end +end diff --git a/db/migrate/20110616215033_create_share_links.rb b/db/migrate/20110616215033_create_share_links.rb new file mode 100644 index 0000000..70a7c60 --- /dev/null +++ b/db/migrate/20110616215033_create_share_links.rb @@ -0,0 +1,15 @@ +class CreateShareLinks < ActiveRecord::Migration + def self.up + create_table :boxroom_share_links do |t| + t.string :emails + t.string :link_token + t.datetime :link_expires_at + t.references :user_file + t.timestamps + end + end + + def self.down + drop_table :boxroom_share_links + end +end diff --git a/db/migrate/20120411075110_add_column_signup_token_to_users.rb b/db/migrate/20120411075110_add_column_signup_token_to_users.rb new file mode 100644 index 0000000..b57069e --- /dev/null +++ b/db/migrate/20120411075110_add_column_signup_token_to_users.rb @@ -0,0 +1,8 @@ +class AddColumnSignupTokenToUsers < ActiveRecord::Migration + def change + change_table :boxroom_users do |t| + t.string :signup_token + t.index :signup_token + end + end +end diff --git a/db/migrate/20120411081345_add_column_signup_token_expires_at_to_users.rb b/db/migrate/20120411081345_add_column_signup_token_expires_at_to_users.rb new file mode 100644 index 0000000..278b561 --- /dev/null +++ b/db/migrate/20120411081345_add_column_signup_token_expires_at_to_users.rb @@ -0,0 +1,7 @@ +class AddColumnSignupTokenExpiresAtToUsers < ActiveRecord::Migration + def change + change_table :boxroom_users do |t| + t.datetime :signup_token_expires_at + end + end +end diff --git a/db/migrate/20130307082111_alter_column_type_is_admin_from_users.rb b/db/migrate/20130307082111_alter_column_type_is_admin_from_users.rb new file mode 100644 index 0000000..786190a --- /dev/null +++ b/db/migrate/20130307082111_alter_column_type_is_admin_from_users.rb @@ -0,0 +1,9 @@ +class AlterColumnTypeIsAdminFromUsers < ActiveRecord::Migration + def self.up + change_column :boxroom_users, :is_admin, :boolean + end + + def self.down + change_column :boxroom_users, :is_admin, :string + end +end diff --git a/db/migrate/20130626210927_add_columns_message_user_id_to_share_links.rb b/db/migrate/20130626210927_add_columns_message_user_id_to_share_links.rb new file mode 100644 index 0000000..961fd83 --- /dev/null +++ b/db/migrate/20130626210927_add_columns_message_user_id_to_share_links.rb @@ -0,0 +1,6 @@ +class AddColumnsMessageUserIdToShareLinks < ActiveRecord::Migration + def change + add_column :boxroom_share_links, :message, :text + add_column :boxroom_share_links, :user_id, :integer + end +end diff --git a/db/migrate/20130628082245_populate_user_id_in_share_links.rb b/db/migrate/20130628082245_populate_user_id_in_share_links.rb new file mode 100644 index 0000000..ab9b2b6 --- /dev/null +++ b/db/migrate/20130628082245_populate_user_id_in_share_links.rb @@ -0,0 +1,9 @@ +class PopulateUserIdInShareLinks < ActiveRecord::Migration + def change + active_users = Boxroom::User.where.not(:name => nil) + + if active_users.any? && Boxroom::ShareLink.any? + Boxroom::ShareLink.where(:user_id => nil).update_all(:user_id => active_users.first.id) + end + end +end diff --git a/lib/boxroom.rb b/lib/boxroom.rb new file mode 100644 index 0000000..2defb53 --- /dev/null +++ b/lib/boxroom.rb @@ -0,0 +1,24 @@ +require "boxroom/engine" +require "dynamic_form" +require 'jquery-fileupload-rails' +require 'acts_as_tree' +require 'paperclip' +require 'boxroom/configuration' + +module Boxroom + class << self + attr_writer :configuration + + def configuration + @configuration ||= Configuration.new + end + + def reset + @configuration = Configuration.new + end + + def configure + yield(configuration) + end + end +end diff --git a/lib/boxroom/configuration.rb b/lib/boxroom/configuration.rb new file mode 100644 index 0000000..f78fa9d --- /dev/null +++ b/lib/boxroom/configuration.rb @@ -0,0 +1,10 @@ +module Boxroom + class Configuration + attr_accessor :site_name, :logo + + def initialize + @site_name = 'Boxroom' + @logo = 'boxroom/logo.png' + end + end +end \ No newline at end of file diff --git a/lib/boxroom/engine.rb b/lib/boxroom/engine.rb new file mode 100644 index 0000000..0da685c --- /dev/null +++ b/lib/boxroom/engine.rb @@ -0,0 +1,9 @@ +module Boxroom + class Engine < ::Rails::Engine + isolate_namespace Boxroom + + initializer 'boxroom.assets.precompile' do |app| + app.config.assets.precompile += %w( boxroom/*.png ) + end + end +end diff --git a/lib/boxroom/version.rb b/lib/boxroom/version.rb new file mode 100644 index 0000000..afe6d69 --- /dev/null +++ b/lib/boxroom/version.rb @@ -0,0 +1,3 @@ +module Boxroom + VERSION = '0.1.0' +end diff --git a/lib/tasks/boxroom_tasks.rake b/lib/tasks/boxroom_tasks.rake new file mode 100644 index 0000000..5ae3d0b --- /dev/null +++ b/lib/tasks/boxroom_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :boxroom do +# # Task goes here +# end diff --git a/test/boxroom_test.rb b/test/boxroom_test.rb new file mode 100644 index 0000000..6ebfeba --- /dev/null +++ b/test/boxroom_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Boxroom::Test < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, Boxroom + end +end diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile new file mode 100644 index 0000000..e85f913 --- /dev/null +++ b/test/dummy/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/test/dummy/app/assets/config/manifest.js b/test/dummy/app/assets/config/manifest.js new file mode 100644 index 0000000..2882d66 --- /dev/null +++ b/test/dummy/app/assets/config/manifest.js @@ -0,0 +1,5 @@ + +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css +//= link boxroom_manifest.js diff --git a/test/dummy/app/assets/images/.keep b/test/dummy/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/assets/javascripts/application.js b/test/dummy/app/assets/javascripts/application.js new file mode 100644 index 0000000..e54c646 --- /dev/null +++ b/test/dummy/app/assets/javascripts/application.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/test/dummy/app/assets/javascripts/cable.js b/test/dummy/app/assets/javascripts/cable.js new file mode 100644 index 0000000..739aa5f --- /dev/null +++ b/test/dummy/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/test/dummy/app/assets/javascripts/channels/.keep b/test/dummy/app/assets/javascripts/channels/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css new file mode 100644 index 0000000..0ebd7fe --- /dev/null +++ b/test/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/test/dummy/app/channels/application_cable/channel.rb b/test/dummy/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/test/dummy/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/test/dummy/app/channels/application_cable/connection.rb b/test/dummy/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/test/dummy/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000..1c07694 --- /dev/null +++ b/test/dummy/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception +end diff --git a/test/dummy/app/controllers/concerns/.keep b/test/dummy/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/test/dummy/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/test/dummy/app/jobs/application_job.rb b/test/dummy/app/jobs/application_job.rb new file mode 100644 index 0000000..a009ace --- /dev/null +++ b/test/dummy/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/test/dummy/app/mailers/application_mailer.rb b/test/dummy/app/mailers/application_mailer.rb new file mode 100644 index 0000000..286b223 --- /dev/null +++ b/test/dummy/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/test/dummy/app/models/application_record.rb b/test/dummy/app/models/application_record.rb new file mode 100644 index 0000000..10a4cba --- /dev/null +++ b/test/dummy/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/test/dummy/app/models/concerns/.keep b/test/dummy/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000..a6eb017 --- /dev/null +++ b/test/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + Dummy + <%= csrf_meta_tags %> + + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= javascript_include_tag 'application' %> + + + + <%= yield %> + + diff --git a/test/dummy/app/views/layouts/mailer.html.erb b/test/dummy/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..cbd34d2 --- /dev/null +++ b/test/dummy/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/test/dummy/app/views/layouts/mailer.text.erb b/test/dummy/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/test/dummy/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/test/dummy/bin/bundle b/test/dummy/bin/bundle new file mode 100755 index 0000000..66e9889 --- /dev/null +++ b/test/dummy/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/test/dummy/bin/rails b/test/dummy/bin/rails new file mode 100755 index 0000000..0739660 --- /dev/null +++ b/test/dummy/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/test/dummy/bin/rake b/test/dummy/bin/rake new file mode 100755 index 0000000..1724048 --- /dev/null +++ b/test/dummy/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/test/dummy/bin/setup b/test/dummy/bin/setup new file mode 100755 index 0000000..78c4e86 --- /dev/null +++ b/test/dummy/bin/setup @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/test/dummy/bin/update b/test/dummy/bin/update new file mode 100755 index 0000000..a8e4462 --- /dev/null +++ b/test/dummy/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/test/dummy/bin/yarn b/test/dummy/bin/yarn new file mode 100755 index 0000000..c2bacef --- /dev/null +++ b/test/dummy/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +VENDOR_PATH = File.expand_path('..', __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/test/dummy/config.ru b/test/dummy/config.ru new file mode 100644 index 0000000..f7ba0b5 --- /dev/null +++ b/test/dummy/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb new file mode 100644 index 0000000..a18f21c --- /dev/null +++ b/test/dummy/config/application.rb @@ -0,0 +1,18 @@ +require_relative 'boot' + +require 'rails/all' + +Bundler.require(*Rails.groups) +require "boxroom" + +module Dummy + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.1 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + end +end + diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb new file mode 100644 index 0000000..c9aef85 --- /dev/null +++ b/test/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) + +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/test/dummy/config/cable.yml b/test/dummy/config/cable.yml new file mode 100644 index 0000000..d3dfabd --- /dev/null +++ b/test/dummy/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: dummy_production diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml new file mode 100644 index 0000000..0d02f24 --- /dev/null +++ b/test/dummy/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb new file mode 100644 index 0000000..426333b --- /dev/null +++ b/test/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb new file mode 100644 index 0000000..55d8c9e --- /dev/null +++ b/test/dummy/config/environments/development.rb @@ -0,0 +1,54 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb new file mode 100644 index 0000000..6ea0018 --- /dev/null +++ b/test/dummy/config/environments/production.rb @@ -0,0 +1,91 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "dummy_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb new file mode 100644 index 0000000..8e5cbde --- /dev/null +++ b/test/dummy/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/test/dummy/config/initializers/application_controller_renderer.rb b/test/dummy/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000..89d2efa --- /dev/null +++ b/test/dummy/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb new file mode 100644 index 0000000..4b828e8 --- /dev/null +++ b/test/dummy/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/test/dummy/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/test/dummy/config/initializers/cookies_serializer.rb b/test/dummy/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000..5a6a32d --- /dev/null +++ b/test/dummy/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..4a994e1 --- /dev/null +++ b/test/dummy/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000..ac033bf --- /dev/null +++ b/test/dummy/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb new file mode 100644 index 0000000..dc18996 --- /dev/null +++ b/test/dummy/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000..bbfc396 --- /dev/null +++ b/test/dummy/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/test/dummy/config/locales/en.yml b/test/dummy/config/locales/en.yml new file mode 100644 index 0000000..decc5a8 --- /dev/null +++ b/test/dummy/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/test/dummy/config/puma.rb b/test/dummy/config/puma.rb new file mode 100644 index 0000000..1e19380 --- /dev/null +++ b/test/dummy/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb new file mode 100644 index 0000000..72926de --- /dev/null +++ b/test/dummy/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + mount Boxroom::Engine => "/boxroom" +end diff --git a/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml new file mode 100644 index 0000000..fc63416 --- /dev/null +++ b/test/dummy/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: 78494ed70673753c6f483e51ec3d207f9815f32a815f8a5e22ccb19d93973b28b1cbbf636e0bfe1f42430fc2e09293839e9bae1747ad8bad6ee84a062c08e03a + +test: + secret_key_base: 391840a9588b36699c9f3bf7077da743159cf67bf8424e70d2e6948204408d1a3869f3b80844c799d0372ef9eb57381333f8145db9f99f0fe210ee53ebd71b5d + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/test/dummy/config/spring.rb b/test/dummy/config/spring.rb new file mode 100644 index 0000000..c9119b4 --- /dev/null +++ b/test/dummy/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/test/dummy/lib/assets/.keep b/test/dummy/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/log/.keep b/test/dummy/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/package.json b/test/dummy/package.json new file mode 100644 index 0000000..caa2d7b --- /dev/null +++ b/test/dummy/package.json @@ -0,0 +1,5 @@ +{ + "name": "dummy", + "private": true, + "dependencies": {} +} diff --git a/test/dummy/public/404.html b/test/dummy/public/404.html new file mode 100644 index 0000000..2be3af2 --- /dev/null +++ b/test/dummy/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/422.html b/test/dummy/public/422.html new file mode 100644 index 0000000..c08eac0 --- /dev/null +++ b/test/dummy/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/500.html b/test/dummy/public/500.html new file mode 100644 index 0000000..78a030a --- /dev/null +++ b/test/dummy/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/apple-touch-icon-precomposed.png b/test/dummy/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/public/apple-touch-icon.png b/test/dummy/public/apple-touch-icon.png new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/public/favicon.ico b/test/dummy/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/test/factories.rb b/test/factories.rb new file mode 100644 index 0000000..37224b5 --- /dev/null +++ b/test/factories.rb @@ -0,0 +1,45 @@ +FactoryGirl.define do + factory :folder do + sequence(:name) { |i| "test#{i}" } + parent { Folder.where(:name => 'Root folder').first_or_create } + end +end + +FactoryGirl.define do + factory :group do + sequence(:name) { |i| "test#{i}" } + end +end + +FactoryGirl.define do + factory :share_link do + emails 'email1@domain.com, email2@domain.com' + link_expires_at { 2.weeks.from_now.end_of_day } + end +end + +FactoryGirl.define do + factory :user_file do + attachment { fixture_file } + sequence(:attachment_file_name) { |i| "test#{i}.txt" } + folder { Folder.where(:name => 'Root folder').first_or_create } + end +end + +FactoryGirl.define do + factory :user do + sequence(:name) { |i| "test#{i}" } + sequence(:email) { |i| "test#{i}@test.com" } + password 'secret123' + password_confirmation { |u| u.password } + password_required true + reset_password_token '' + dont_clear_reset_password_token false + remember_token '' + is_admin false + end +end + +def fixture_file + File.open("#{Rails.root}/test/fixtures/textfile.txt") +end diff --git a/test/fixtures/textfile.txt b/test/fixtures/textfile.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test/fixtures/textfile.txt @@ -0,0 +1 @@ +test diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb new file mode 100644 index 0000000..f5d1ec2 --- /dev/null +++ b/test/integration/navigation_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end + diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..5a85c6c --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,29 @@ +require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) +ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] +ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) +require "rails/test_help" + +# Filter out Minitest backtrace while allowing backtrace from other libraries +# to be shown. +Minitest.backtrace_filter = Minitest::BacktraceFilter.new + + +# Load fixtures from the engine +if ActiveSupport::TestCase.respond_to?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path + ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" + ActiveSupport::TestCase.fixtures :all +end + +# ENV["RAILS_ENV"] = "test" +# require File.expand_path('../../config/environment', __FILE__) +# require 'rails/test_help' +# +# class ActiveSupport::TestCase +# include FactoryGirl::Syntax::Methods +# +# def clear_root_folder +# Folder.instance_variable_set('@root_folder', nil) +# end +# end diff --git a/test/unit/clipboard_test.rb b/test/unit/clipboard_test.rb new file mode 100644 index 0000000..f397150 --- /dev/null +++ b/test/unit/clipboard_test.rb @@ -0,0 +1,134 @@ +require 'test_helper' + +class ClipboardTest < ActiveSupport::TestCase + test 'a folder can be added to the clipboard' do + folder = create(:folder) + clipboard = Clipboard.new + assert clipboard.folders.empty? + assert clipboard.empty? + + clipboard.add(folder) + assert_equal clipboard.folders.count, 1 + assert !clipboard.empty? + end + + test 'a file can be added to the clipboard' do + file = create(:user_file) + clipboard = Clipboard.new + assert clipboard.files.empty? + assert clipboard.empty? + + clipboard.add(file) + assert_equal clipboard.files.count, 1 + assert !clipboard.empty? + end + + test 'a folder can be removed from the clipboard' do + folder = create(:folder) + clipboard = Clipboard.new + clipboard.add(folder) + assert_equal clipboard.folders.count, 1 + assert !clipboard.empty? + + clipboard.remove(folder) + assert clipboard.folders.empty? + assert clipboard.empty? + end + + test 'a file can be removed from the clipboard' do + file = create(:user_file) + clipboard = Clipboard.new + clipboard.add(file) + assert_equal clipboard.files.count, 1 + assert !clipboard.empty? + + clipboard.remove(file) + assert clipboard.files.empty? + assert clipboard.empty? + end + + test 'the same folder cannot be added twice' do + folder = create(:folder) + clipboard = Clipboard.new + clipboard.add(folder) + assert_equal clipboard.folders.count, 1 + + another_folder = create(:folder) + clipboard.add(another_folder) + assert_equal clipboard.folders.count, 2 + + clipboard.add(folder) + assert_equal clipboard.folders.count, 2 + end + + test 'the same file cannot be added twice' do + file = create(:user_file) + clipboard = Clipboard.new + clipboard.add(file) + assert_equal clipboard.files.count, 1 + + another_file = create(:user_file) + clipboard.add(another_file) + assert_equal clipboard.files.count, 2 + + clipboard.add(file) + assert_equal clipboard.files.count, 2 + end + + test 'when a folder is updated the referenced folder on the clipboard must also change' do + folder = create(:folder, :name => 'Test') + clipboard = Clipboard.new + clipboard.add(folder) + assert_equal clipboard.folders.first.name, 'Test' + + folder.update_attributes(:name => 'Name changed') + assert_equal clipboard.folders.first.name, 'Name changed' + end + + test 'when a file is updated the referenced file on the clipboard must also change' do + file = create(:user_file) + clipboard = Clipboard.new + clipboard.add(file) + assert_not_equal clipboard.files.first.attachment_file_name, 'Name changed.txt' + + file.update_attributes(:attachment_file_name => 'Name changed.txt') + assert_equal clipboard.files.first.attachment_file_name, 'Name changed.txt' + end + + test 'a deleted folder must also be deleted from the clipboard' do + folder = create(:folder) + clipboard = Clipboard.new + clipboard.add(folder) + assert !clipboard.folders.empty? + + folder.destroy + assert clipboard.empty? + assert clipboard.folders.empty? + end + + test 'a deleted file must also be deleted from the clipboard' do + file = create(:user_file) + clipboard = Clipboard.new + clipboard.add(file) + assert !clipboard.files.empty? + + file.destroy + assert clipboard.empty? + assert clipboard.files.empty? + end + + test 'reset clears all the files and folders from the clipboard' do + clipboard = Clipboard.new + 3.times { clipboard.add(create(:user_file)) } + 3.times { clipboard.add(create(:folder)) } + + assert_equal clipboard.files.size, 3 + assert_equal clipboard.folders.size, 3 + assert !clipboard.empty? + + clipboard.reset + assert clipboard.files.empty? + assert clipboard.folders.empty? + assert clipboard.empty? + end +end diff --git a/test/unit/folder_test.rb b/test/unit/folder_test.rb new file mode 100644 index 0000000..135b2ee --- /dev/null +++ b/test/unit/folder_test.rb @@ -0,0 +1,207 @@ +require 'test_helper' + +class FolderTest < ActiveSupport::TestCase + def setup + clear_root_folder + end + + test 'dependent files get deleted' do + folder1 = create(:folder) + assert_equal Folder.all.count, 2 # Root folder gets created automatically + + 3.times { create(:user_file, :folder => folder1) } + assert_equal folder1.user_files.count, 3 + + folder2 = create(:folder) + assert_equal Folder.all.count, 3 + + 5.times { create(:user_file, :folder => folder2) } + assert_equal folder2.user_files.count, 5 + assert_equal UserFile.all.count, 8 + + folder1.destroy + assert_equal UserFile.all.count, 5 + + folder2.destroy + assert_equal UserFile.all.count, 0 + end + + test 'dependent permissions get deleted' do + root = create(:folder, :name => 'Root folder', :parent => nil) # Root folder + assert_equal Folder.all.count, 1 + + 3.times { create(:group) } + assert Group.all.count > 0 + assert_equal root.permissions.count, Group.all.count + + folder1 = create(:folder) + folder2 = create(:folder) + assert_equal folder1.permissions.count, 3 + assert_equal folder2.permissions.count, 3 + assert_equal Permission.all.count, 9 + + folder1.destroy + assert_equal Permission.all.count, 6 + + folder2.destroy + assert_equal Permission.all.count, 3 + end + + test 'name is unique' do + folder = create(:folder, :name => 'Test') + assert Folder.exists?(:name => 'Test') + + folder2 = Folder.new(:name => 'Test') + folder2.parent = folder + assert folder2.save + + folder3 = Folder.new(:name => 'Test') + folder3.parent = folder + assert !folder3.save + end + + test 'name is not empty' do + folder = Folder.new + assert !folder.save + end + + test 'cannot create a folder without a parent' do + folder = Folder.new(:name => 'Test') + assert_nil folder.parent + assert_raise(RuntimeError) { folder.save } + end + + test 'permissions get created' do + root = create(:folder, :name => 'Root folder', :parent => nil) # Root folder + assert_equal Folder.all.count, 1 + + create(:group) + assert Group.all.count > 0 + assert_equal root.permissions.count, Group.all.count + + root.permissions.each do |permission| + assert !permission.can_create + assert permission.can_read + assert !permission.can_update + assert !permission.can_delete + + # Change the permissions + permission.update_attributes(:can_create => true, :can_update => true) + end + + folder = create(:folder) + assert_equal Folder.all.count, 2 + assert_equal folder.permissions.count, Group.all.count + + # Test if updated permissions get copied correctly + folder.permissions.each do |permission| + assert permission.can_create + assert permission.can_read + assert permission.can_update + assert !permission.can_delete + end + end + + test 'cannot delete root folder' do + folder = create(:folder) + root = Folder.root + + assert_raise(RuntimeError) { root.destroy } + assert folder.destroy + end + + test 'cannot copy a folder to anything other than a folder' do + file = create(:user_file) + folder1 = create(:folder) + folder2 = create(:folder) + + assert_raise(RuntimeError) { folder1.copy(nil) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { folder1.copy('A string...') } + assert_raise(ActiveRecord::AssociationTypeMismatch) { folder1.copy(file) } + assert folder1.copy(folder2) + end + + test 'copy a folder' do + folder1 = create(:folder) + 5.times { create(:user_file, :folder => folder1) } + + folder2 = create(:folder) + 3.times { create(:user_file, :folder => folder2) } + + assert_raise(ActiveRecord::RecordInvalid) { folder1.copy(Folder.root) } + assert_equal UserFile.all.count, 8 + assert_equal folder2.children.count, 0 + assert folder1.copy(folder2) + assert_equal UserFile.all.count, 13 + assert_equal folder2.children.count, 1 + end + + test 'cannot move a folder to anything other than a folder' do + file = create(:user_file) + folder1 = create(:folder) + folder2 = create(:folder) + + assert_raise(RuntimeError) { folder1.move(nil) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { folder1.move('A string...') } + assert_raise(ActiveRecord::AssociationTypeMismatch) { folder1.move(file) } + assert folder1.move(folder2) + end + + test 'move a folder' do + folder1 = create(:folder) + folder2 = create(:folder, :parent => folder1) + folder3 = create(:folder) + + assert folder1.parent_of?(folder2) + assert !folder1.parent_of?(folder3) + + # Should not be able to move a folder to its own sub-folder + assert_raise(RuntimeError) { folder1.move(folder2) } + + assert_equal Folder.all.count, 4 + assert_equal folder1.parent, Folder.root + + folder1.move(folder3) + assert_equal folder1.parent, folder3 + assert_equal Folder.all.count, 4 + + assert folder3.parent_of?(folder1) + assert folder3.parent_of?(folder2) + end + + test 'whether a folder is root or not' do + folder1 = create(:folder) + assert !folder1.is_root? + + folder2 = Folder.new + assert !folder2.is_root? + + root = Folder.root + assert root.is_root? + end + + test 'whether a folder has children or not' do + folder = create(:folder) + assert !folder.has_children? + + root = Folder.root + assert_equal folder.parent, root + assert root.has_children? + + folder2 = create(:folder, :parent => folder) + assert folder.has_children? + + folder.destroy + assert !root.has_children? + end + + test 'that the root folder is really the root folder' do + folder = create(:folder) + assert !folder.is_root? + + root = Folder.root + assert root.is_root? + assert_equal root.name, 'Root folder' + assert_nil root.parent + end +end diff --git a/test/unit/group_test.rb b/test/unit/group_test.rb new file mode 100644 index 0000000..fa786fa --- /dev/null +++ b/test/unit/group_test.rb @@ -0,0 +1,72 @@ +require 'test_helper' + +class GroupTest < ActiveSupport::TestCase + test 'dependent permissions get deleted' do + 3.times { create(:folder) } + assert_equal Folder.all.count, 4 # Root folder gets created automatically + + group1 = create(:group) + group2 = create(:group) + assert_equal group1.permissions.count, 4 + assert_equal group2.permissions.count, 4 + assert_equal Permission.all.count, 8 + + group1.destroy + assert_equal Permission.all.count, 4 + + group2.destroy + assert_equal Permission.all.count, 0 + end + + test 'name is unique' do + create(:group, :name => 'Users') + assert Group.exists?(:name => 'Users') + + group = Group.new(:name => 'Users') + assert !group.save + end + + test 'name is not empty' do + group = Group.new + assert !group.save + end + + test 'admin permissions get created' do + create(:folder) + assert Folder.all.count > 0 + + group = create(:group, :name => 'Admins') + assert_equal group.permissions.count, Folder.all.count + + group.permissions.each do |permission| + assert permission.can_create + assert permission.can_read + assert permission.can_update + assert permission.can_delete + end + end + + test 'permissions get created' do + create(:folder) + assert Folder.all.count > 0 + + group = create(:group) + assert_equal group.permissions.count, Folder.all.count + + group.permissions.each do |permission| + assert !permission.can_create + assert_equal permission.can_read, permission.folder.is_root? + assert !permission.can_update + assert !permission.can_delete + end + end + + test 'cannot delete admins group' do + admins = create(:group, :name => 'Admins') + normal_group = create(:group) + + assert admins.admins_group? + assert_raise(RuntimeError) { admins.destroy } + assert normal_group.destroy + end +end diff --git a/test/unit/share_link_test.rb b/test/unit/share_link_test.rb new file mode 100644 index 0000000..f815f2d --- /dev/null +++ b/test/unit/share_link_test.rb @@ -0,0 +1,78 @@ +require 'test_helper' + +class ShareLinkTest < ActiveSupport::TestCase + test 'emails is not empty' do + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => '') } + end + + test 'link expires at is not empty' do + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :link_expires_at => '') } + end + + test 'emails is not longer than 255 characters' do + share_link = create(:share_link) + + # 255 chars + share_link.emails = 'email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, anotheremail@domain.com, email@domain.com, another@domain.com' + assert share_link.save + + # 256 chars + share_link.emails = 'email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, another-email@domain.com, email@domain.com, anotheremail@domain.com, email@domain.com, anothere@domain.com' + assert !share_link.save + end + + test 'the format of emails is valid' do + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, @.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, @test.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, test@.com, another-email@domain.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, test@test.') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, test@$%^.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, test@test.c') } + assert_raise(ActiveRecord::RecordInvalid) { create(:share_link, :emails => 'mail@domain.com, test@test.$$$') } + end + + test 'a link token gets generated' do + share_link1 = create(:share_link) + assert !share_link1.link_token.blank? + + share_link2 = ShareLink.new + share_link2.user_file = create(:user_file) + share_link2.emails = 'mail@domain.com' + share_link2.link_expires_at = 2.weeks.from_now.end_of_day + assert share_link2.link_token.blank? + + share_link2.save + assert !share_link2.link_token.blank? + end + + test 'active share links' do + expiry_dates = [1.week.ago, 1.week.from_now, 2.weeks.from_now, 3.weeks.from_now, 1.month.from_now] + + expiry_dates.each do |expiry_date| + create(:share_link, :link_expires_at => expiry_date) + end + + assert_equal ShareLink.active_share_links.count, 4 + end + + test 'the correct file is returned for a given link token' do + user_file = create(:user_file) + + share_link1 = create(:share_link) + share_link1.user_file = user_file + share_link1.save + + share_link2 = create(:share_link, :link_expires_at => 1.week.ago.end_of_day) + share_link2.user_file = user_file + share_link2.save + + random_token = SecureRandom.hex(10) + assert_raise(NoMethodError) { ShareLink.file_for_token(random_token) } + + old_link_token = share_link2.link_token + assert_raise(RuntimeError) { ShareLink.file_for_token(old_link_token) } + + valid_link_token = share_link1.link_token + assert_equal ShareLink.file_for_token(valid_link_token), user_file + end +end diff --git a/test/unit/user_file_test.rb b/test/unit/user_file_test.rb new file mode 100644 index 0000000..292c19e --- /dev/null +++ b/test/unit/user_file_test.rb @@ -0,0 +1,137 @@ +require 'test_helper' + +class UserFileTest < ActiveSupport::TestCase + def setup + clear_root_folder + end + + test 'dependent share links get deleted' do + file1 = create(:user_file) + assert_equal UserFile.all.count, 1 + + 3.times { create(:share_link, :user_file => file1) } + assert_equal file1.share_links.count, 3 + + file2 = create(:user_file) + assert_equal UserFile.all.count, 2 + + 5.times { create(:share_link, :user_file => file2) } + assert_equal file2.share_links.count, 5 + assert_equal ShareLink.all.count, 8 + + file1.destroy + assert_equal ShareLink.all.count, 5 + + file2.destroy + assert_equal ShareLink.all.count, 0 + end + + test 'attachment is not empty' do + folder = create(:folder) + file = folder.user_files.build + assert !file.save + + file.attachment = fixture_file + assert file.save + end + + test 'folder is not empty' do + file = UserFile.new(:attachment => fixture_file) + assert !file.save + + folder = create(:folder) + file.folder = folder + assert file.save + end + + test 'attachment file name is unique' do + file = create(:user_file) + file.update_attributes(:attachment_file_name => 'Test.txt') + assert UserFile.exists?(:attachment_file_name => 'Test.txt') + + folder = create(:folder) + file2 = folder.user_files.build(:attachment => fixture_file) + file2.attachment_file_name = 'Test.txt' + assert file2.save + + file3 = Folder.root.user_files.build(:attachment => fixture_file) + file3.attachment_file_name = 'Test.txt' + assert !file3.save + end + + test 'attachment file name cannot contain invalid characters' do + file = create(:user_file) + + %w{< > : " / \ | ? *}.each do |invalid_character| + file.attachment_file_name = "Test#{invalid_character}.txt" + assert !file.save + end + + file.attachment_file_name = 'Test.txt' + assert file.save + end + + test 'cannot copy a file to anything other than a folder' do + file1 = create(:user_file) + file2 = create(:user_file) + folder = create(:folder) + + assert_raise(ActiveRecord::RecordInvalid) { file1.copy(nil) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { file1.copy('A string...') } + assert_raise(ActiveRecord::AssociationTypeMismatch) { file1.copy(file2) } + assert file1.copy(folder) + end + + test 'copy a file' do + folder = create(:folder) + file = create(:user_file) + + assert_raise(ActiveRecord::RecordInvalid) { file.copy(Folder.root) } + assert_equal UserFile.all.count, 1 + assert_equal folder.user_files.count, 0 + + file.copy(folder) + assert_equal UserFile.all.count, 2 + assert_equal folder.user_files.count, 1 + + new_file = UserFile.find_by_attachment_file_name_and_folder_id(file.attachment_file_name, folder.id) + assert File.exists?(new_file.attachment.path) + end + + test 'cannot move a file to anything other than a folder' do + file1 = create(:user_file) + file2 = create(:user_file) + folder = create(:folder) + + assert_raise(ActiveRecord::RecordInvalid) { file1.move(nil) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { file1.move('A string...') } + assert_raise(ActiveRecord::AssociationTypeMismatch) { file1.move(file2) } + assert file1.move(folder) + end + + test 'move a file' do + folder = create(:folder) + folder2 = create(:folder) + file = create(:user_file) + + assert file.copy(folder) + assert_equal UserFile.all.count, 2 + assert_not_equal file.folder, folder + assert_raise(ActiveRecord::RecordInvalid) { file.move(folder) } + + assert file.move(folder2) + assert_equal UserFile.all.count, 2 + assert_equal file.folder, folder2 + end + + test 'file has correct extension' do + file = create(:user_file) + assert_equal file.extension, 'txt' + + file.update_attributes(:attachment_file_name => 'test.pdf') + assert_equal file.extension, 'pdf' + + file.update_attributes(:attachment_file_name => 'test') + assert file.extension.blank? + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..a121f3a --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,237 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + test 'password is valid' do + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :password => '123456', :password_confirmation => '654321') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :password => '123') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :password => '') } + assert create(:user, :password => '123456') + + user = create(:user) + user.password_required = false + user.password = '' + user.password_confirmation = '' + assert user.save + end + + test 'name can be empty for a new user' do + create(:user, :name => '', :email => 'test@test.com') + assert User.exists?(:name => '', :email => 'test@test.com') + end + + test 'signup_token and signup_token_expires_at are set for a new user' do + user = create(:user, :name => '', :email => 'test@test.com') + assert !user.signup_token.empty? + assert user.signup_token_expires_at > 1.hour.from_now + end + + test 'name cannot be empty for an existing user' do + user = create(:user) + user.name = '' + assert_raise(ActiveRecord::RecordInvalid) { user.save! } + end + + test 'signup_token and signup_token_expires_at are nil for an existing user' do + user = create(:user, :name => '', :email => 'test@test.com') + user.name = 'test' + user.save + assert user.signup_token.nil? + assert user.signup_token_expires_at.nil? + end + + test 'email is not empty' do + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => '') } + end + + test 'name is unique' do + create(:user, :name => 'Test') + assert User.exists?(:name => 'Test') + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :name => 'Test') } + end + + test 'email is unique' do + create(:user, :email => 'test@test.com') + assert User.exists?(:email => 'test@test.com') + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@test.com') } + end + + test 'email is valid' do + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => '@.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => '@test.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@test.') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@$%^.com') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@test.c') } + assert_raise(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@test.$$$') } + assert_nothing_raised(ActiveRecord::RecordInvalid) { create(:user, :email => 'test@test.com') } + end + + test 'reset_password_token gets cleared' do + token = SecureRandom.base64(32) + user = create(:user, :reset_password_token => token, :dont_clear_reset_password_token => true) + assert_equal user.reset_password_token, token + + user2 = User.find_by_reset_password_token(token) + user2.save + assert user2.reset_password_token.blank? + end + + test 'root folder and admins group get created' do + admin = create(:user, :is_admin => true) + assert_equal Folder.where(:name => 'Root folder').count, 1 + assert_equal Group.where(:name => 'Admins').count, 1 + assert_equal admin.groups.count, 1 + end + + test 'cannot delete admin user' do + admin = create(:user, :is_admin => true) + normal_user = create(:user) + + assert_raise(RuntimeError) { admin.destroy } + assert normal_user.destroy + end + + test 'user permissions' do + admin = create(:user, :is_admin => true) + user = create(:user) + root = Folder.root + folder = create(:folder) + group = create(:group) + + %w{create read update delete}.each { |method| assert admin.send("can_#{method}", root) } + %w{create read update delete}.each { |method| assert admin.send("can_#{method}", folder) } + %w{create read update delete}.each { |method| assert !user.send("can_#{method}", root) } + %w{create read update delete}.each { |method| assert !user.send("can_#{method}", folder) } + + user.groups << group + assert user.can_read(root) + %w{create update delete}.each { |method| assert !user.send("can_#{method}", root) } + %w{create read update delete}.each { |method| assert !user.send("can_#{method}", folder) } + + folder.permissions.where(:group_id => group).update_all(:can_create => true) + assert user.can_create(folder) + %w{read update delete}.each { |method| assert !user.send("can_#{method}", folder) } + + folder.permissions.where(:group_id => group).update_all(:can_read => true) + %w{create read}.each { |method| assert user.send("can_#{method}", folder) } + %w{update delete}.each { |method| assert !user.send("can_#{method}", folder) } + + folder.permissions.where(:group_id => group).update_all(:can_update => true) + %w{create read update}.each { |method| assert user.send("can_#{method}", folder) } + assert !user.can_delete(folder) + + folder.permissions.where(:group_id => group).update_all(:can_delete => true) + %w{create read update delete}.each { |method| assert user.send("can_#{method}", folder) } + + assert user.can_read(root) + %w{create update delete}.each { |method| assert !user.send("can_#{method}", root) } + %w{create read update delete}.each { |method| assert admin.send("can_#{method}", root) } + %w{create read update delete}.each { |method| assert admin.send("can_#{method}", folder) } + end + + test 'hashed_password and password_salt do not change when leaving the password empty' do + user = create(:user) + assert !user.password_salt.blank? + assert !user.hashed_password.blank? + + salt = user.password_salt + hash = user.hashed_password + + user.password_required = false + user.password = '' + user.password_confirmation = '' + + assert user.save + assert_equal user.password_salt, salt + assert_equal user.hashed_password, hash + end + + test 'hashed_password and password_salt change when updating the password' do + user = create(:user) + salt = user.password_salt + hash = user.hashed_password + + user.password = 'test1234' + user.password_confirmation = 'test1234' + + assert user.save + assert_not_equal user.password_salt, salt + assert_not_equal user.hashed_password, hash + assert User.authenticate(user.name, 'test1234') + end + + test 'whether a user is member of admins or not' do + admin = create(:user, :is_admin => true) + assert admin.member_of_admins? + + user = create(:user) + assert !user.member_of_admins? + + user.groups << Group.find_by_name('Admins') + assert user.member_of_admins? + end + + test 'whether reset_password_token refreshes' do + user = create(:user) + assert user.reset_password_token.blank? + + user.refresh_reset_password_token + assert !user.reset_password_token.blank? + assert_in_delta user.reset_password_token_expires_at, 1.hour.from_now, 1.second + + token = user.reset_password_token + user.refresh_reset_password_token + + assert !user.reset_password_token.blank? + assert_not_equal user.reset_password_token, token + end + + test 'whether remember_token refreshes' do + user = create(:user) + assert user.remember_token.blank? + + user.refresh_remember_token + assert !user.remember_token.blank? + + token = user.remember_token + user.refresh_remember_token + + assert !user.remember_token.blank? + assert_not_equal user.remember_token, token + end + + test 'whether forget_me clears remember_token' do + user = create(:user) + user.refresh_remember_token + assert !user.remember_token.blank? + + user.forget_me + assert user.remember_token.blank? + assert !user.changed? # There are no unsaved changes: `forget_me` saved the record + end + + test 'authentication' do + user = create(:user, :name => 'testname', :password => 'secret') + assert !User.authenticate(nil, nil) + assert !User.authenticate('', '') + assert !User.authenticate('testname', nil) + assert !User.authenticate('testname', '') + assert !User.authenticate(nil, 'secret') + assert !User.authenticate('', 'secret') + assert !User.authenticate('test', 'test') + assert User.authenticate('testname', 'secret') + end + + test 'whether there is an admin user or not' do + assert User.no_admin_yet? + + normal_user = create(:user) + assert User.no_admin_yet? + + # make normal_user admin + normal_user.is_admin = true + normal_user.save + + assert !User.no_admin_yet? + end +end