├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYING ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── fonts │ │ ├── entypo.eot │ │ ├── entypo.svg │ │ ├── entypo.ttf │ │ ├── entypo.woff │ │ ├── fontello.eot │ │ ├── fontello.svg │ │ ├── fontello.ttf │ │ └── fontello.woff │ ├── images │ │ ├── .keep │ │ ├── annotator-glyph-sprite.png │ │ ├── annotator-icon-sprite.png │ │ ├── apple-touch-icon.png │ │ ├── cancelfullscreen.png │ │ ├── close.png │ │ ├── ebook-icon.png │ │ ├── fullscreen.png │ │ ├── loader.gif │ │ ├── menu-icon.png │ │ ├── save.png │ │ ├── saved.png │ │ ├── settings-s.png │ │ ├── settings.png │ │ ├── spritemap.png │ │ ├── spritemap@2x.png │ │ └── star.png │ ├── javascripts │ │ ├── application.js │ │ ├── bootstrap.min.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ ├── ebook-reader.js │ │ ├── ebooks.js │ │ ├── epub.min.js │ │ ├── hooks.min.js │ │ ├── jquery.hammer.js │ │ ├── localforage.min.js │ │ ├── reader.min.js │ │ ├── screenfull.min.js │ │ └── zip.min.js │ └── stylesheets │ │ ├── annotations.css │ │ ├── application.scss │ │ ├── bootstrap.min.css │ │ ├── ebook-reader.scss │ │ ├── ebooks.scss │ │ ├── main.css │ │ ├── normalize.css │ │ └── popup.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── ebooks_controller.rb ├── helpers │ ├── application_helper.rb │ └── ebooks_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── ebook.rb │ └── user.rb └── views │ ├── ebooks │ ├── _ebook.html.erb │ ├── _nobooks.html.erb │ ├── choose.html.erb │ ├── import.html.erb │ ├── index.html.erb │ ├── index.js.erb │ ├── new.html.erb │ └── show.html.erb │ ├── layouts │ ├── _header.html.erb │ ├── application.html.erb │ ├── mailer.html.erb │ ├── mailer.text.erb │ └── reader.html.erb │ └── users │ ├── confirmations │ └── new.html.erb │ ├── mailer │ ├── confirmation_instructions.html.erb │ ├── password_change.html.erb │ ├── reset_password_instructions.html.erb │ └── unlock_instructions.html.erb │ ├── passwords │ ├── edit.html.erb │ └── new.html.erb │ ├── registrations │ ├── edit.html.erb │ └── new.html.erb │ ├── sessions │ └── new.html.erb │ ├── shared │ └── _links.html.erb │ └── unlocks │ └── new.html.erb ├── bin ├── bundle ├── install-debian.sh ├── install-readingcloud.sh ├── install-redhat.sh ├── rails ├── rake ├── setup └── update ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── migrate │ ├── 20170105071407_create_ebooks.rb │ ├── 20170105212429_add_attachment_book_to_ebooks.rb │ ├── 20170105230506_remove_original_file_from_ebooks.rb │ ├── 20170106043812_add_cover_to_ebooks.rb │ ├── 20170107040335_devise_create_users.rb │ └── 20170218013642_add_book_local_path_to_ebooks.rb ├── schema.rb └── seeds.rb ├── docker-start.sh ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── css │ └── popup.css ├── favicon.ico ├── font │ ├── entypo.eot │ ├── entypo.svg │ ├── entypo.ttf │ ├── entypo.woff │ ├── fontello.eot │ ├── fontello.svg │ ├── fontello.ttf │ └── fontello.woff ├── js │ ├── application.js │ ├── cable.js │ ├── channels │ │ └── .keep │ ├── ebooks.coffee │ ├── epub.min.js │ ├── epub.min.map │ ├── hooks.min.js │ ├── hooks.min.map │ ├── hooks │ │ └── extensions │ │ │ └── highlight.js │ ├── libs │ │ ├── jquery.min.js │ │ ├── screenfull.js │ │ ├── screenfull.min.js │ │ └── zip.min.js │ ├── localforage.min.js │ ├── plugins │ │ ├── hypothesis.js │ │ └── search.js │ ├── reader.min.js │ ├── reader.min.map │ └── zip.min.js └── robots.txt ├── test ├── controllers │ ├── .keep │ └── ebooks_controller_test.rb ├── fixtures │ ├── .keep │ ├── ebooks.yml │ ├── files │ │ └── .keep │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── ebook_test.rb │ └── user_test.rb └── test_helper.rb ├── tmp └── .keep └── vendor └── assets ├── javascripts ├── .keep └── jquery.infinitescroll.js └── stylesheets ├── .keep └── dropzone.css.erb /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | *~ 8 | # Ignore bundler config. 9 | /.bundle 10 | 11 | # Ignore the default SQLite database. 12 | /db/*.sqlite3 13 | /db/*.sqlite3-journal 14 | 15 | # ignore the public/system folder 16 | /public/system/* 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore Byebug command history file. 24 | .byebug_history 25 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thom.cherryhomes@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | I'm pretty open to contributions, just do a Pull Request, I'll look @ it, and merge if it's ok, giving feedback regardless. :) 2 | 3 | -Thom 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4.0 2 | MAINTAINER Thomas Cherryhomes 3 | 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | EXPOSE 3000 6 | 7 | RUN apt-get update -qq 8 | RUN apt-get install -y build-essential libsqlite3-dev libssl-dev zlib1g-dev libreadline-dev libyaml-dev nodejs git 9 | 10 | RUN gem install bundler 11 | VOLUME /app 12 | ADD . /app 13 | WORKDIR /app 14 | 15 | RUN bundle install 16 | RUN rake db:setup 17 | RUN rake db:migrate 18 | ENTRYPOINT exec /app/docker-start.sh 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | 9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 10 | gem 'rails', '~> 5.0.1' 11 | # Use sqlite3 as the database for Active Record 12 | gem 'sqlite3' 13 | # Use Puma as the app server 14 | gem 'webrick' 15 | # Use SCSS for stylesheets 16 | gem 'sass-rails', '~> 5.0' 17 | # Use Uglifier as compressor for JavaScript assets 18 | gem 'uglifier', '>= 1.3.0' 19 | # Use CoffeeScript for .coffee assets and views 20 | gem 'coffee-rails', '~> 4.2' 21 | # See https://github.com/rails/execjs#readme for more supported runtimes 22 | # gem 'therubyracer', platforms: :ruby 23 | 24 | # Use jquery as the JavaScript library 25 | gem 'jquery-rails' 26 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 27 | gem 'turbolinks', '~> 5' 28 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 29 | gem 'jbuilder', '~> 2.5' 30 | # Use ActiveModel has_secure_password 31 | gem 'bcrypt', '~> 3.1.7' 32 | 33 | group :development, :test do 34 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 35 | gem 'byebug', platform: :mri 36 | end 37 | 38 | group :development do 39 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 40 | gem 'web-console', '>= 3.3.0' 41 | gem 'listen', '~> 3.0.5' 42 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 43 | gem 'spring' 44 | gem 'spring-watcher-listen', '~> 2.0.0' 45 | end 46 | 47 | # additional libs 48 | gem 'epub-parser' 49 | gem 'dropzonejs-rails' 50 | gem 'paperclip' 51 | gem 'kaminari' 52 | gem 'devise' 53 | gem 'bootstrap-table-rails' 54 | gem 'hammerjs-rails' 55 | 56 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 57 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 58 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.0.1) 5 | actionpack (= 5.0.1) 6 | nio4r (~> 1.2) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.0.1) 9 | actionpack (= 5.0.1) 10 | actionview (= 5.0.1) 11 | activejob (= 5.0.1) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.0.1) 15 | actionview (= 5.0.1) 16 | activesupport (= 5.0.1) 17 | rack (~> 2.0) 18 | rack-test (~> 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.0.1) 22 | activesupport (= 5.0.1) 23 | builder (~> 3.1) 24 | erubis (~> 2.7.0) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | activejob (5.0.1) 28 | activesupport (= 5.0.1) 29 | globalid (>= 0.3.6) 30 | activemodel (5.0.1) 31 | activesupport (= 5.0.1) 32 | activerecord (5.0.1) 33 | activemodel (= 5.0.1) 34 | activesupport (= 5.0.1) 35 | arel (~> 7.0) 36 | activesupport (5.0.1) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (~> 0.7) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | addressable (2.5.0) 42 | public_suffix (~> 2.0, >= 2.0.2) 43 | archive-zip (0.9.0) 44 | io-like (~> 0.3.0) 45 | arel (7.1.4) 46 | bcrypt (3.1.11) 47 | bootstrap-table-rails (1.11.0) 48 | builder (3.2.2) 49 | byebug (9.0.6) 50 | climate_control (0.0.3) 51 | activesupport (>= 3.0) 52 | cocaine (0.5.8) 53 | climate_control (>= 0.0.3, < 1.0) 54 | coffee-rails (4.2.1) 55 | coffee-script (>= 2.2.0) 56 | railties (>= 4.0.0, < 5.2.x) 57 | coffee-script (2.4.1) 58 | coffee-script-source 59 | execjs 60 | coffee-script-source (1.12.2) 61 | concurrent-ruby (1.0.4) 62 | debug_inspector (0.0.2) 63 | devise (4.2.0) 64 | bcrypt (~> 3.0) 65 | orm_adapter (~> 0.1) 66 | railties (>= 4.1.0, < 5.1) 67 | responders 68 | warden (~> 1.2.3) 69 | dropzonejs-rails (0.7.3) 70 | rails (> 3.1) 71 | epub-parser (0.2.7) 72 | addressable (>= 2.3.5) 73 | archive-zip 74 | nokogiri (~> 1.6) 75 | rchardet (>= 1.6.1) 76 | erubis (2.7.0) 77 | execjs (2.7.0) 78 | ffi (1.9.14) 79 | globalid (0.3.7) 80 | activesupport (>= 4.1.0) 81 | hammerjs-rails (2.0.4) 82 | i18n (0.7.0) 83 | io-like (0.3.0) 84 | jbuilder (2.6.1) 85 | activesupport (>= 3.0.0, < 5.1) 86 | multi_json (~> 1.2) 87 | jquery-rails (4.2.2) 88 | rails-dom-testing (>= 1, < 3) 89 | railties (>= 4.2.0) 90 | thor (>= 0.14, < 2.0) 91 | kaminari (0.17.0) 92 | actionpack (>= 3.0.0) 93 | activesupport (>= 3.0.0) 94 | listen (3.0.8) 95 | rb-fsevent (~> 0.9, >= 0.9.4) 96 | rb-inotify (~> 0.9, >= 0.9.7) 97 | loofah (2.0.3) 98 | nokogiri (>= 1.5.9) 99 | mail (2.6.4) 100 | mime-types (>= 1.16, < 4) 101 | method_source (0.8.2) 102 | mime-types (3.1) 103 | mime-types-data (~> 3.2015) 104 | mime-types-data (3.2016.0521) 105 | mimemagic (0.3.2) 106 | mini_portile2 (2.1.0) 107 | minitest (5.10.1) 108 | multi_json (1.12.1) 109 | nio4r (1.2.1) 110 | nokogiri (1.7.0.1) 111 | mini_portile2 (~> 2.1.0) 112 | orm_adapter (0.5.0) 113 | paperclip (5.1.0) 114 | activemodel (>= 4.2.0) 115 | activesupport (>= 4.2.0) 116 | cocaine (~> 0.5.5) 117 | mime-types 118 | mimemagic (~> 0.3.0) 119 | public_suffix (2.0.5) 120 | rack (2.0.1) 121 | rack-test (0.6.3) 122 | rack (>= 1.0) 123 | rails (5.0.1) 124 | actioncable (= 5.0.1) 125 | actionmailer (= 5.0.1) 126 | actionpack (= 5.0.1) 127 | actionview (= 5.0.1) 128 | activejob (= 5.0.1) 129 | activemodel (= 5.0.1) 130 | activerecord (= 5.0.1) 131 | activesupport (= 5.0.1) 132 | bundler (>= 1.3.0, < 2.0) 133 | railties (= 5.0.1) 134 | sprockets-rails (>= 2.0.0) 135 | rails-dom-testing (2.0.2) 136 | activesupport (>= 4.2.0, < 6.0) 137 | nokogiri (~> 1.6) 138 | rails-html-sanitizer (1.0.3) 139 | loofah (~> 2.0) 140 | railties (5.0.1) 141 | actionpack (= 5.0.1) 142 | activesupport (= 5.0.1) 143 | method_source 144 | rake (>= 0.8.7) 145 | thor (>= 0.18.1, < 2.0) 146 | rake (12.0.0) 147 | rb-fsevent (0.9.8) 148 | rb-inotify (0.9.7) 149 | ffi (>= 0.5.0) 150 | rchardet (1.6.1) 151 | responders (2.3.0) 152 | railties (>= 4.2.0, < 5.1) 153 | sass (3.4.23) 154 | sass-rails (5.0.6) 155 | railties (>= 4.0.0, < 6) 156 | sass (~> 3.1) 157 | sprockets (>= 2.8, < 4.0) 158 | sprockets-rails (>= 2.0, < 4.0) 159 | tilt (>= 1.1, < 3) 160 | spring (2.0.0) 161 | activesupport (>= 4.2) 162 | spring-watcher-listen (2.0.1) 163 | listen (>= 2.7, < 4.0) 164 | spring (>= 1.2, < 3.0) 165 | sprockets (3.7.1) 166 | concurrent-ruby (~> 1.0) 167 | rack (> 1, < 3) 168 | sprockets-rails (3.2.0) 169 | actionpack (>= 4.0) 170 | activesupport (>= 4.0) 171 | sprockets (>= 3.0.0) 172 | sqlite3 (1.3.13) 173 | thor (0.19.4) 174 | thread_safe (0.3.5) 175 | tilt (2.0.5) 176 | turbolinks (5.0.1) 177 | turbolinks-source (~> 5) 178 | turbolinks-source (5.0.0) 179 | tzinfo (1.2.2) 180 | thread_safe (~> 0.1) 181 | uglifier (3.0.4) 182 | execjs (>= 0.3.0, < 3) 183 | warden (1.2.6) 184 | rack (>= 1.0) 185 | web-console (3.4.0) 186 | actionview (>= 5.0) 187 | activemodel (>= 5.0) 188 | debug_inspector 189 | railties (>= 5.0) 190 | webrick (1.3.1) 191 | websocket-driver (0.6.4) 192 | websocket-extensions (>= 0.1.0) 193 | websocket-extensions (0.1.2) 194 | 195 | PLATFORMS 196 | ruby 197 | 198 | DEPENDENCIES 199 | bcrypt (~> 3.1.7) 200 | bootstrap-table-rails 201 | byebug 202 | coffee-rails (~> 4.2) 203 | devise 204 | dropzonejs-rails 205 | epub-parser 206 | hammerjs-rails 207 | jbuilder (~> 2.5) 208 | jquery-rails 209 | kaminari 210 | listen (~> 3.0.5) 211 | paperclip 212 | rails (~> 5.0.1) 213 | sass-rails (~> 5.0) 214 | spring 215 | spring-watcher-listen (~> 2.0.0) 216 | sqlite3 217 | turbolinks (~> 5) 218 | tzinfo-data 219 | uglifier (>= 1.3.0) 220 | web-console (>= 3.3.0) 221 | webrick 222 | 223 | BUNDLED WITH 224 | 1.13.7 225 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Simple, Describe what you're adding or fixing as concisely as possible within the template. 2 | 3 | -Thom 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | readingcloud 2 | ------------ 3 | 4 | readingcloud is a simple epub.js based reader for personal use from a web browser. You can upload unencrypted epub ebooks and be able to read them from your web browser. This application is built in Ruby on Rails, and thus needs to be installed as a Ruby on Rails application. 5 | 6 | installing on debian based machines 7 | =================================== 8 | 9 | To install on debian based machines, Please run the bin/install-debian.sh script from inside readingcloud directory. 10 | 11 | Debian by default does not install sudo, or put any users in the sudo group. Please use apt-get install sudo and add yourself to the sudo group, and log out and log back in, if needed. 12 | 13 | installing on redhat based machines 14 | =================================== 15 | 16 | To install on redhat based machines, Please run the bin/install-redhat.sh script from inside readingcloud directory. 17 | 18 | using the software 19 | ================== 20 | 21 | To run the server, you can start it in its simplest mode by typing: 22 | 23 | rails s -b your.ip.address.here -p whatever.port.you.select 24 | 25 | If you do not specify an address to bind, using b, then it will bind to localhost. If you do not specify a port to bind to, it will bind to port 3000, by default. 26 | 27 | user interface 28 | ============== 29 | 30 | When you first approach readingcloud, it will ask you to log in, using an email address, and password. You should click Sign Up, to create one, after which it will automatically log you in. 31 | 32 | At this point, you can use Upload Books, to upload books in epub format. You can either drag and drop your epub files onto the "Drop files here" box, or you can click on the box, to present a dialog box to select files to upload. You can upload multiple files at a time. The system will automatically parse them, grabbing the cover image, title, and other metadata automatically. Once this is done, you can click Books, and see the books that you've uploaded. 33 | 34 | Simply selecting a row from the table, will open the book for reading. You can also type a title or author into the search box, to narrow down the number of books. 35 | 36 | Once open for reading, you can: 37 | 38 | * Navigate the book by either swiping left or right on mobile devices, or selecting the left or right arrow icons on either side of the visible page(s) 39 | * Add a bookmark using the bookmark icon. 40 | * Add an annotation 41 | * Open the top left sidebar (using its icon) so that you can view a list of noted chapters, bookmarks, and annotations. 42 | * Delete the book from the library, using the trashcan icon 43 | * Close the book and return to the library with the close icon. 44 | 45 | supported formats 46 | ================= 47 | 48 | Right now, unencrypted epub files are supported (that report themselves as application/epub+zip MIME type), if the file has some sort of DRM (e.g. Google Play Books, or Adobe Digital Editions), you will need to remove it, before using this software. 49 | 50 | license 51 | ======= 52 | 53 | This software is licensed under version 3 or later of the GNU Public License. Please read COPYING for details. 54 | 55 | author 56 | ====== 57 | 58 | This software was the result of a flurry of integration programming by Thomas Cherryhomes 59 | 60 | embedded software 61 | ================= 62 | 63 | This software utilizes the following open source software modules 64 | 65 | * EPUB.JS for reading. 66 | * EPUB.JS Reader for reading UI. 67 | * Hammer.js for Gestural Control 68 | * Devise for Authentication and Authorization 69 | * Paperclip for file management 70 | * Dropzone.js for drag and drop multiple file uploading 71 | * epub-parser, for EPUB metadata parsing 72 | * bootstrap-table for the intelligent table layout used on the front page 73 | * bootstrap for quickly making a pleasant user interface that works well on mobile and desktop devices 74 | * Fontello font for UI icon elements 75 | * entypo font for UI icon elements 76 | 77 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/fonts/entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/entypo.eot -------------------------------------------------------------------------------- /app/assets/fonts/entypo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/assets/fonts/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/entypo.ttf -------------------------------------------------------------------------------- /app/assets/fonts/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/entypo.woff -------------------------------------------------------------------------------- /app/assets/fonts/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/fontello.eot -------------------------------------------------------------------------------- /app/assets/fonts/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2013 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/assets/fonts/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/fontello.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/fonts/fontello.woff -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/annotator-glyph-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/annotator-glyph-sprite.png -------------------------------------------------------------------------------- /app/assets/images/annotator-icon-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/annotator-icon-sprite.png -------------------------------------------------------------------------------- /app/assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /app/assets/images/cancelfullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/cancelfullscreen.png -------------------------------------------------------------------------------- /app/assets/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/close.png -------------------------------------------------------------------------------- /app/assets/images/ebook-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/ebook-icon.png -------------------------------------------------------------------------------- /app/assets/images/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/fullscreen.png -------------------------------------------------------------------------------- /app/assets/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/loader.gif -------------------------------------------------------------------------------- /app/assets/images/menu-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/menu-icon.png -------------------------------------------------------------------------------- /app/assets/images/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/save.png -------------------------------------------------------------------------------- /app/assets/images/saved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/saved.png -------------------------------------------------------------------------------- /app/assets/images/settings-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/settings-s.png -------------------------------------------------------------------------------- /app/assets/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/settings.png -------------------------------------------------------------------------------- /app/assets/images/spritemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/spritemap.png -------------------------------------------------------------------------------- /app/assets/images/spritemap@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/spritemap@2x.png -------------------------------------------------------------------------------- /app/assets/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/images/star.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | //= require dropzone 18 | //= require jquery.infinitescroll 19 | //= require bootstrap.min 20 | //= require bootstrap-table 21 | 22 | $(document).ready(function($) { 23 | $(".ebook").click(function() { 24 | window.location = $(this).attr("data-href"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/ebook-reader.js: -------------------------------------------------------------------------------- 1 | // This is the stack of javascript to use in the ebook view. 2 | 3 | //= require jquery 4 | //= require jquery_ujs 5 | //= require epub.min 6 | //= require hooks.min 7 | //= require reader.min 8 | //= require zip.min 9 | //= require localforage.min 10 | //= require screenfull.min 11 | //= require hammer 12 | //= require jquery.hammer 13 | 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/ebooks.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // disable auto discover 3 | Dropzone.autoDiscover = false; 4 | 5 | // grap our upload form by its id 6 | $("#new_ebook").dropzone({ 7 | // restrict image size to a maximum 32MB 8 | maxFilesize: 32, 9 | // changed the passed param to one accepted by 10 | // our rails app 11 | paramName: "ebook[book]", 12 | // show remove links on each image upload 13 | addRemoveLinks: true 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/hooks.min.js: -------------------------------------------------------------------------------- 1 | EPUBJS.Hooks.register("beforeChapterDisplay").endnotes=function(a,b){var c=b.contents.querySelectorAll("a[href]"),d=Array.prototype.slice.call(c),e="epub:type",f="noteref",g=EPUBJS.core.folder(location.pathname),h=(g+EPUBJS.cssPath||g,{});EPUBJS.core.addCss(EPUBJS.cssPath+"popup.css",!1,b.render.document.head),d.forEach(function(a){function c(){var c,e,f=b.height,j=b.width,p=225;o||(c=l.cloneNode(!0),o=c.querySelector("p")),h[k]||(h[k]=document.createElement("div"),h[k].setAttribute("class","popup"),pop_content=document.createElement("div"),h[k].appendChild(pop_content),pop_content.appendChild(o),pop_content.setAttribute("class","pop_content"),b.render.document.body.appendChild(h[k]),h[k].addEventListener("mouseover",d,!1),h[k].addEventListener("mouseout",g,!1),b.on("renderer:pageChanged",i,this),b.on("renderer:pageChanged",g,this)),c=h[k],e=a.getBoundingClientRect(),m=e.left,n=e.top,c.classList.add("show"),popRect=c.getBoundingClientRect(),c.style.left=m-popRect.width/2+"px",c.style.top=n+"px",p>f/2.5&&(p=f/2.5,pop_content.style.maxHeight=p+"px"),popRect.height+n>=f-25?(c.style.top=n-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),m-popRect.width<=0?(c.style.left=m+"px",c.classList.add("left")):c.classList.remove("left"),m+popRect.width/2>=j?(c.style.left=m-300+"px",popRect=c.getBoundingClientRect(),c.style.left=m-popRect.width+"px",popRect.height+n>=f-25?(c.style.top=n-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),c.classList.add("right")):c.classList.remove("right")}function d(){h[k].classList.add("on")}function g(){h[k].classList.remove("on")}function i(){setTimeout(function(){h[k].classList.remove("show")},100)}var j,k,l,m,n,o,p=a.getAttribute(e);p==f&&(j=a.getAttribute("href"),k=j.replace("#",""),l=b.render.document.getElementById(k),a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",i,!1))}),a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").mathml=function(a,b){if(b.currentChapter.manifestProperties.indexOf("mathml")!==-1){b.render.iframe.contentWindow.mathmlCallback=a;var c=document.createElement("script");c.type="text/x-mathjax-config",c.innerHTML=' MathJax.Hub.Register.StartupHook("End",function () { window.mathmlCallback(); }); MathJax.Hub.Config({jax: ["input/TeX","input/MathML","output/SVG"],extensions: ["tex2jax.js","mml2jax.js","MathEvents.js"],TeX: {extensions: ["noErrors.js","noUndefined.js","autoload-all.js"]},MathMenu: {showRenderer: false},menuSettings: {zoom: "Click"},messageStyle: "none"}); ',b.doc.body.appendChild(c),EPUBJS.core.addScript("http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML",null,b.doc.head)}else a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").smartimages=function(a,b){var c=b.contents.querySelectorAll("img"),d=Array.prototype.slice.call(c),e=b.height;return"reflowable"!=b.layoutSettings.layout?void a():(d.forEach(function(a){var c=function(){var c,d=a.getBoundingClientRect(),f=d.height,g=d.top,h=a.getAttribute("data-height"),i=h||f,j=Number(getComputedStyle(a,"").fontSize.match(/(\d*(\.\d*)?)px/)[1]),k=j?j/2:0;e=b.contents.clientHeight,g<0&&(g=0),i+g>=e?(ge&&(a.style.maxHeight=e+"px",a.style.width="auto",d=a.getBoundingClientRect(),i=d.height),a.style.display="block",a.style.WebkitColumnBreakBefore="always",a.style.breakBefore="column"),a.setAttribute("data-height",c)):(a.style.removeProperty("max-height"),a.style.removeProperty("margin-top"))},d=function(){b.off("renderer:resized",c),b.off("renderer:chapterUnload",this)};a.addEventListener("load",c,!1),b.on("renderer:resized",c),b.on("renderer:chapterUnload",d),c()}),void(a&&a()))},EPUBJS.Hooks.register("beforeChapterDisplay").transculsions=function(a,b){var c=b.contents.querySelectorAll("[transclusion]"),d=Array.prototype.slice.call(c);d.forEach(function(a){function c(){j=g,k=h,j>chapter.colWidth&&(d=chapter.colWidth/j,j=chapter.colWidth,k*=d),f.width=j,f.height=k}var d,e=a.getAttribute("ref"),f=document.createElement("iframe"),g=a.getAttribute("width"),h=a.getAttribute("height"),i=a.parentNode,j=g,k=h;c(),b.listenUntil("renderer:resized","renderer:chapterUnloaded",c),f.src=e,i.replaceChild(f,a)}),a&&a()}; -------------------------------------------------------------------------------- /app/assets/javascripts/jquery.hammer.js: -------------------------------------------------------------------------------- 1 | (function(factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'hammerjs'], factory); 4 | } else if (typeof exports === 'object') { 5 | factory(require('jquery'), require('hammerjs')); 6 | } else { 7 | factory(jQuery, Hammer); 8 | } 9 | }(function($, Hammer) { 10 | function hammerify(el, options) { 11 | var $el = $(el); 12 | if(!$el.data("hammer")) { 13 | $el.data("hammer", new Hammer($el[0], options)); 14 | } 15 | } 16 | 17 | $.fn.hammer = function(options) { 18 | return this.each(function() { 19 | hammerify(this, options); 20 | }); 21 | }; 22 | 23 | // extend the emit method to also trigger jQuery events 24 | Hammer.Manager.prototype.emit = (function(originalEmit) { 25 | return function(type, data) { 26 | originalEmit.call(this, type, data); 27 | $(this.element).trigger({ 28 | type: type, 29 | gesture: data 30 | }); 31 | }; 32 | })(Hammer.Manager.prototype.emit); 33 | })); 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/reader.min.js: -------------------------------------------------------------------------------- 1 | EPUBJS.reader={},EPUBJS.reader.plugins={},function(a,b){var c=(a.ePubReader||{},a.ePubReader=function(a,b){return new EPUBJS.Reader(a,b)});"function"==typeof define&&define.amd?define(function(){return Reader}):"undefined"!=typeof module&&module.exports&&(module.exports=c)}(window,jQuery),EPUBJS.Reader=function(a,b){var c,d,e,f=this,g=$("#viewer"),h=window.location.search;this.settings=EPUBJS.core.defaults(b||{},{bookPath:a,restore:!1,reload:!1,bookmarks:void 0,annotations:void 0,contained:void 0,bookKey:void 0,styles:void 0,sidebarReflow:!1,generatePagination:!1,history:!0}),h&&(e=h.slice(1).split("&"),e.forEach(function(a){var b=a.split("="),c=b[0],d=b[1]||"";f.settings[c]=decodeURIComponent(d)})),this.setBookKey(this.settings.bookPath),this.settings.restore&&this.isSaved()&&this.applySavedSettings(),this.settings.styles=this.settings.styles||{fontSize:"100%"},this.book=c=new EPUBJS.Book(this.settings),this.settings.previousLocationCfi&&c.gotoCfi(this.settings.previousLocationCfi),this.offline=!1,this.sidebarOpen=!1,this.settings.bookmarks||(this.settings.bookmarks=[]),this.settings.annotations||(this.settings.annotations=[]),this.settings.generatePagination&&c.generatePagination(g.width(),g.height()),c.renderTo("viewer"),f.ReaderController=EPUBJS.reader.ReaderController.call(f,c),f.SettingsController=EPUBJS.reader.SettingsController.call(f,c),f.ControlsController=EPUBJS.reader.ControlsController.call(f,c),f.SidebarController=EPUBJS.reader.SidebarController.call(f,c),f.BookmarksController=EPUBJS.reader.BookmarksController.call(f,c),f.NotesController=EPUBJS.reader.NotesController.call(f,c);for(d in EPUBJS.reader.plugins)EPUBJS.reader.plugins.hasOwnProperty(d)&&(f[d]=EPUBJS.reader.plugins[d].call(f,c));return c.ready.all.then(function(){f.ReaderController.hideLoader()}),c.getMetadata().then(function(a){f.MetaController=EPUBJS.reader.MetaController.call(f,a)}),c.getToc().then(function(a){f.TocController=EPUBJS.reader.TocController.call(f,a)}),window.addEventListener("beforeunload",this.unload.bind(this),!1),window.addEventListener("hashchange",this.hashChanged.bind(this),!1),document.addEventListener("keydown",this.adjustFontSize.bind(this),!1),c.on("renderer:keydown",this.adjustFontSize.bind(this)),c.on("renderer:keydown",f.ReaderController.arrowKeys.bind(this)),c.on("renderer:selected",this.selectedRange.bind(this)),this},EPUBJS.Reader.prototype.adjustFontSize=function(a){var b,c=2,d=187,e=189,f=48,g=a.ctrlKey||a.metaKey;this.settings.styles&&(this.settings.styles.fontSize||(this.settings.styles.fontSize="100%"),b=parseInt(this.settings.styles.fontSize.slice(0,-1)),g&&a.keyCode==d&&(a.preventDefault(),this.book.setStyle("fontSize",b+c+"%")),g&&a.keyCode==e&&(a.preventDefault(),this.book.setStyle("fontSize",b-c+"%")),g&&a.keyCode==f&&(a.preventDefault(),this.book.setStyle("fontSize","100%")))},EPUBJS.Reader.prototype.addBookmark=function(a){var b=this.isBookmarked(a);b>-1||(this.settings.bookmarks.push(a),this.trigger("reader:bookmarked",a))},EPUBJS.Reader.prototype.removeBookmark=function(a){var b=this.isBookmarked(a);b!==-1&&(this.settings.bookmarks.splice(b,1),this.trigger("reader:unbookmarked",b))},EPUBJS.Reader.prototype.isBookmarked=function(a){var b=this.settings.bookmarks;return b.indexOf(a)},EPUBJS.Reader.prototype.clearBookmarks=function(){this.settings.bookmarks=[]},EPUBJS.Reader.prototype.addNote=function(a){this.settings.annotations.push(a)},EPUBJS.Reader.prototype.removeNote=function(a){var b=this.settings.annotations.indexOf(a);b!==-1&&delete this.settings.annotations[b]},EPUBJS.Reader.prototype.clearNotes=function(){this.settings.annotations=[]},EPUBJS.Reader.prototype.setBookKey=function(a){return this.settings.bookKey||(this.settings.bookKey="epubjsreader:"+EPUBJS.VERSION+":"+window.location.host+":"+a),this.settings.bookKey},EPUBJS.Reader.prototype.isSaved=function(a){var b;return!!localStorage&&(b=localStorage.getItem(this.settings.bookKey),null!==b)},EPUBJS.Reader.prototype.removeSavedSettings=function(){return!!localStorage&&void localStorage.removeItem(this.settings.bookKey)},EPUBJS.Reader.prototype.applySavedSettings=function(){var a;if(!localStorage)return!1;try{a=JSON.parse(localStorage.getItem(this.settings.bookKey))}catch(a){return!1}return!!a&&(a.styles&&(this.settings.styles=EPUBJS.core.defaults(this.settings.styles||{},a.styles)),this.settings=EPUBJS.core.defaults(this.settings,a),!0)},EPUBJS.Reader.prototype.saveSettings=function(){return this.book&&(this.settings.previousLocationCfi=this.book.getCurrentLocationCfi()),!!localStorage&&void localStorage.setItem(this.settings.bookKey,JSON.stringify(this.settings))},EPUBJS.Reader.prototype.unload=function(){this.settings.restore&&localStorage&&this.saveSettings()},EPUBJS.Reader.prototype.hashChanged=function(){var a=window.location.hash.slice(1);this.book.goto(a)},EPUBJS.Reader.prototype.selectedRange=function(a){var b=new EPUBJS.EpubCFI,c=b.generateCfiFromRangeAnchor(a,this.book.renderer.currentChapter.cfiBase),d="#"+c;this.settings.history&&window.location.hash!=d&&(history.pushState({},"",d),this.currentLocationCfi=c)},RSVP.EventTarget.mixin(EPUBJS.Reader.prototype),EPUBJS.reader.BookmarksController=function(){var a=this.book,b=$("#bookmarksView"),c=b.find("#bookmarks"),d=document.createDocumentFragment(),e=function(){b.show()},f=function(){b.hide()},g=0,h=function(b){var c=document.createElement("li"),d=document.createElement("a");return c.id="bookmark-"+g,c.classList.add("list_item"),d.textContent=b,d.href=b,d.classList.add("bookmark_link"),d.addEventListener("click",function(b){var c=this.getAttribute("href");a.gotoCfi(c),b.preventDefault()},!1),c.appendChild(d),g++,c};return this.settings.bookmarks.forEach(function(a){var b=h(a);d.appendChild(b)}),c.append(d),this.on("reader:bookmarked",function(a){var b=h(a);c.append(b)}),this.on("reader:unbookmarked",function(a){var b=$("#bookmark-"+a);b.remove()}),{show:e,hide:f}},EPUBJS.reader.ControlsController=function(a){var b=this,c=($("#store"),$("#fullscreen")),d=($("#fullscreenicon"),$("#cancelfullscreenicon"),$("#slider")),e=($("#main"),$("#sidebar"),$("#setting")),f=$("#bookmark"),g=function(){b.offline=!1},h=function(){b.offline=!0},i=!1;return a.on("book:online",g),a.on("book:offline",h),d.on("click",function(){b.sidebarOpen?(b.SidebarController.hide(),d.addClass("icon-menu"),d.removeClass("icon-right")):(b.SidebarController.show(),d.addClass("icon-right"),d.removeClass("icon-menu"))}),"undefined"!=typeof screenfull&&(c.on("click",function(){screenfull.toggle($("#container")[0])}),screenfull.raw&&document.addEventListener(screenfull.raw.fullscreenchange,function(){i=screenfull.isFullscreen,i?c.addClass("icon-resize-small").removeClass("icon-resize-full"):c.addClass("icon-resize-full").removeClass("icon-resize-small")})),e.on("click",function(){b.SettingsController.show()}),f.on("click",function(){var a=b.book.getCurrentLocationCfi(),c=b.isBookmarked(a);c===-1?(b.addBookmark(a),f.addClass("icon-bookmark").removeClass("icon-bookmark-empty")):(b.removeBookmark(a),f.removeClass("icon-bookmark").addClass("icon-bookmark-empty"))}),a.on("renderer:locationChanged",function(a){var c="#"+a,d=b.isBookmarked(a);d===-1?f.removeClass("icon-bookmark").addClass("icon-bookmark-empty"):f.addClass("icon-bookmark").removeClass("icon-bookmark-empty"),b.currentLocationCfi=a,b.settings.history&&window.location.hash!=c&&history.pushState({},"",c)}),a.on("book:pageChanged",function(a){}),{}},EPUBJS.reader.MetaController=function(a){var b=a.bookTitle,c=a.creator,d=$("#book-title"),e=$("#chapter-title"),f=$("#title-seperator");document.title=b+" – "+c,d.html(b),e.html(c),f.show()},EPUBJS.reader.NotesController=function(){var a=this.book,b=this,c=$("#notesView"),d=$("#notes"),e=$("#note-text"),f=$("#note-anchor"),g=b.settings.annotations,h=a.renderer,i=[],j=new EPUBJS.EpubCFI,k=function(){c.show()},l=function(){c.hide()},m=function(c){var d,g,h,i,k,l=a.renderer.doc;if(l.caretPositionFromPoint?(d=l.caretPositionFromPoint(c.clientX,c.clientY),g=d.offsetNode,h=d.offset):l.caretRangeFromPoint&&(d=l.caretRangeFromPoint(c.clientX,c.clientY),g=d.startContainer,h=d.startOffset),3!==g.nodeType)for(var p=0;pm/2.5&&(o=m/2.5,pop_content.style.maxHeight=o+"px"),popRect.height+l>=m-25?(b.style.top=l-popRect.height+"px",b.classList.add("above")):b.classList.remove("above"),k-popRect.width<=0?(b.style.left=k+"px",b.classList.add("left")):b.classList.remove("left"),k+popRect.width/2>=n?(b.style.left=k-300+"px",popRect=b.getBoundingClientRect(),b.style.left=k-popRect.width+"px",popRect.height+l>=m-25?(b.style.top=l-popRect.height+"px",b.classList.add("above")):b.classList.remove("above"),b.classList.add("right")):b.classList.remove("right")},f=function(){i[d].classList.add("on")},g=function(){i[d].classList.remove("on")},j=function(){setTimeout(function(){i[d].classList.remove("show")},100)},l=function(){b.ReaderController.slideOut(),k()};a.addEventListener("mouseover",e,!1),a.addEventListener("mouseout",j,!1),a.addEventListener("click",l,!1)};return f.on("click",function(b){f.text("Cancel"),e.prop("disabled","true"),a.on("renderer:click",m)}),g.forEach(function(a){n(a)}),h.registerHook("beforeChapterDisplay",function(a,b){var c=b.currentChapter;g.forEach(function(a){var b=j.parse(a.anchor);if(b.spinePos===c.spinePos)try{o(a)}catch(b){console.log("anchoring failed",a.anchor)}}),a()},!0),{show:k,hide:l}},EPUBJS.reader.ReaderController=function(a){var b=$("#main"),c=$("#divider"),d=$("#loader"),e=$("#next"),f=$("#prev"),g=this,a=this.book,h=function(){var c=a.getCurrentLocationCfi();g.settings.sidebarReflow?(b.removeClass("single"),b.one("transitionend",function(){a.gotoCfi(c)})):b.removeClass("closed")},i=function(){var c=a.getCurrentLocationCfi();g.settings.sidebarReflow?(b.addClass("single"),b.one("transitionend",function(){a.gotoCfi(c)})):b.addClass("closed")},j=function(){d.show(),m()},k=function(){d.hide()},l=function(){c.addClass("show")},m=function(){c.removeClass("show")},n=!1,o=function(b){37==b.keyCode&&("rtl"===a.metadata.direction?a.nextPage():a.prevPage(),f.addClass("active"),n=!0,setTimeout(function(){n=!1,f.removeClass("active")},100),b.preventDefault()),39==b.keyCode&&("rtl"===a.metadata.direction?a.prevPage():a.nextPage(),e.addClass("active"),n=!0,setTimeout(function(){n=!1,e.removeClass("active")},100),b.preventDefault())};return document.addEventListener("keydown",o,!1),e.on("click",function(b){"rtl"===a.metadata.direction?a.prevPage():a.nextPage(),b.preventDefault()}),f.on("click",function(b){"rtl"===a.metadata.direction?a.nextPage():a.prevPage(),b.preventDefault()}),a.on("renderer:spreads",function(a){a?l():m()}),{slideOut:i,slideIn:h,showLoader:j,hideLoader:k,showDivider:l,hideDivider:m,arrowKeys:o}},EPUBJS.reader.SettingsController=function(){var a=(this.book,this),b=$("#settings-modal"),c=$(".overlay"),d=function(){b.addClass("md-show")},e=function(){b.removeClass("md-show")},f=$("#sidebarReflow");return f.on("click",function(){a.settings.sidebarReflow=!a.settings.sidebarReflow}),b.find(".closer").on("click",function(){e()}),c.on("click",function(){e()}),{show:d,hide:e}},EPUBJS.reader.SidebarController=function(a){var b=this,c=$("#sidebar"),d=$("#panels"),e="Toc",f=function(a){var c=a+"Controller";e!=a&&"undefined"!=typeof b[c]&&(b[e+"Controller"].hide(),b[c].show(),e=a,d.find(".active").removeClass("active"),d.find("#show-"+a).addClass("active"))},g=function(){return e},h=function(){b.sidebarOpen=!0,b.ReaderController.slideOut(),c.addClass("open")},i=function(){b.sidebarOpen=!1,b.ReaderController.slideIn(),c.removeClass("open")};return d.find(".show_view").on("click",function(a){var b=$(this).data("view");f(b),a.preventDefault()}),{show:h,hide:i,getActivePanel:g,changePanelTo:f}},EPUBJS.reader.TocController=function(a){var b=this.book,c=$("#tocView"),d=document.createDocumentFragment(),e=!1,f=function(a,b){var c=document.createElement("ul");return b||(b=1),a.forEach(function(a){var d=document.createElement("li"),e=document.createElement("a");toggle=document.createElement("a");var g;d.id="toc-"+a.id,d.classList.add("list_item"),e.textContent=a.label,e.href=a.href,e.classList.add("toc_link"),d.appendChild(e),a.subitems.length>0&&(b++,g=f(a.subitems,b),toggle.classList.add("toc_toggle"),d.insertBefore(toggle,e),d.appendChild(g)),c.appendChild(d)}),c},g=function(){c.show()},h=function(){c.hide()},i=function(a){var b=a.id,d=c.find("#toc-"+b),f=c.find(".currentChapter");c.find(".openChapter");d.length&&(d!=f&&d.has(e).length>0&&f.removeClass("currentChapter"),d.addClass("currentChapter"),d.parents("li").addClass("openChapter"))};b.on("renderer:chapterDisplayed",i);var j=f(a);return d.appendChild(j),c.append(d),c.find(".toc_link").on("click",function(a){var d=this.getAttribute("href");a.preventDefault(),b.goto(d),c.find(".currentChapter").addClass("openChapter").removeClass("currentChapter"),$(this).parent("li").addClass("currentChapter")}),c.find(".toc_toggle").on("click",function(a){var b=$(this).parent("li"),c=b.hasClass("openChapter");a.preventDefault(),c?b.removeClass("openChapter"):b.addClass("openChapter")}),{show:g,hide:h}}; -------------------------------------------------------------------------------- /app/assets/javascripts/screenfull.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * screenfull 3 | * v1.1.0 - 2013-09-06 4 | * https://github.com/sindresorhus/screenfull.js 5 | * (c) Sindre Sorhus; MIT License 6 | */ 7 | !function(a,b){"use strict";var c="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,d=function(){for(var a,c,d=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenchange","MSFullscreenerror"]],e=0,f=d.length,g={};f>e;e++)if(a=d[e],a&&a[1]in b){for(e=0,c=a.length;c>e;e++)g[d[0][e]]=a[e];return g}return!1}(),e={request:function(a){var e=d.requestFullscreen;a=a||b.documentElement,/5\.1[\.\d]* Safari/.test(navigator.userAgent)?a[e]():a[e](c&&Element.ALLOW_KEYBOARD_INPUT)},exit:function(){b[d.exitFullscreen]()},toggle:function(a){this.isFullscreen?this.exit():this.request(a)},onchange:function(){},onerror:function(){},raw:d};return d?(Object.defineProperties(e,{isFullscreen:{get:function(){return!!b[d.fullscreenElement]}},element:{enumerable:!0,get:function(){return b[d.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return!!b[d.fullscreenEnabled]}}}),b.addEventListener(d.fullscreenchange,function(a){e.onchange.call(e,a)}),b.addEventListener(d.fullscreenerror,function(a){e.onerror.call(e,a)}),a.screenfull=e,void 0):(a.screenfull=!1,void 0)}(window,document); -------------------------------------------------------------------------------- /app/assets/stylesheets/annotations.css: -------------------------------------------------------------------------------- 1 | .annotator-adder { 2 | width: 80px; 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | */ 14 | 15 | @import "dropzone"; 16 | @import "bootstrap.min"; 17 | 18 | //= require bootstrap 19 | //= require jquery 20 | //= require jquery_ujs 21 | //= require turbolinks 22 | //= require_tree . 23 | //= require bootstrap-table 24 | 25 | .form-signin { 26 | max-width: 330px; 27 | padding: 15px; 28 | margin: 0 auto; 29 | } 30 | .form-signin .form-signin-heading, 31 | .form-signin .checkbox { 32 | margin-bottom: 10px; 33 | } 34 | .form-signin .checkbox { 35 | font-weight: normal; 36 | } 37 | .form-signin .form-control { 38 | position: relative; 39 | height: auto; 40 | -webkit-box-sizing: border-box; 41 | -moz-box-sizing: border-box; 42 | box-sizing: border-box; 43 | padding: 10px; 44 | font-size: 16px; 45 | } 46 | .form-signin .form-control:focus { 47 | z-index: 2; 48 | } 49 | .form-signin input[type="email"] { 50 | margin-bottom: -1px; 51 | border-bottom-right-radius: 0; 52 | border-bottom-left-radius: 0; 53 | } 54 | .form-signin input[type="password"] { 55 | margin-bottom: 10px; 56 | border-top-left-radius: 0; 57 | border-top-right-radius: 0; 58 | } 59 | -------------------------------------------------------------------------------- /app/assets/stylesheets/ebook-reader.scss: -------------------------------------------------------------------------------- 1 | /* Reader SCSS */ 2 | 3 | @import "main"; 4 | @import "normalize"; 5 | @import "popup"; 6 | @import "annotations"; 7 | 8 | @font-face { 9 | font-family: 'entypo'; 10 | src: url('../font/entypo.eot'); 11 | src: url('../font/entypo.eot#iefix') format('embedded-opentype'), 12 | url('../font/entypo.woff') format('woff'), 13 | url('../font/entypo.ttf') format('truetype'), 14 | url('../font/entypo.svg#entypo') format('svg'); 15 | font-weight: normal; 16 | font-style: normal; 17 | } 18 | 19 | [class^="eicon-"]:before, [class*=" eicon-"]:before { 20 | font-family: "entypo"; 21 | font-style: normal; 22 | font-weight: normal; 23 | speak: none; 24 | 25 | display: inline-block; 26 | text-decoration: inherit; 27 | width: .002em; 28 | margin-top: -.25em; 29 | margin-right: .2em; 30 | margin-left: -.25em; 31 | text-align: center; 32 | /* opacity: .8; */ 33 | 34 | /* For safety - reset parent styles, that can break glyph codes*/ 35 | font-variant: normal; 36 | text-transform: none; 37 | 38 | /* you can be more comfortable with increased icons size */ 39 | font-size: 220%; 40 | } 41 | 42 | 43 | .eicon-trash:before { content: '\e729'; } /* '' */ 44 | -------------------------------------------------------------------------------- /app/assets/stylesheets/ebooks.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the ebooks controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | *display: inline; 34 | *zoom: 1; 35 | } 36 | 37 | /* 38 | * Prevents modern browsers from displaying `audio` without controls. 39 | * Remove excess height in iOS 5 devices. 40 | */ 41 | 42 | audio:not([controls]) { 43 | display: none; 44 | height: 0; 45 | } 46 | 47 | /* 48 | * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3, 49 | * and Safari 4. 50 | * Known issue: no IE 6 support. 51 | */ 52 | 53 | [hidden] { 54 | display: none; 55 | } 56 | 57 | /* ========================================================================== 58 | Base 59 | ========================================================================== */ 60 | 61 | /* 62 | * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using 63 | * `em` units. 64 | * 2. Prevents iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | font-size: 100%; /* 1 */ 70 | -webkit-text-size-adjust: 100%; /* 2 */ 71 | -ms-text-size-adjust: 100%; /* 2 */ 72 | } 73 | 74 | /* 75 | * Addresses `font-family` inconsistency between `textarea` and other form 76 | * elements. 77 | */ 78 | 79 | html, 80 | button, 81 | input, 82 | select, 83 | textarea { 84 | font-family: sans-serif; 85 | } 86 | 87 | /* 88 | * Addresses margins handled incorrectly in IE 6/7. 89 | */ 90 | 91 | body { 92 | margin: 0; 93 | } 94 | 95 | /* ========================================================================== 96 | Links 97 | ========================================================================== */ 98 | 99 | /* 100 | * Addresses `outline` inconsistency between Chrome and other browsers. 101 | */ 102 | 103 | a:focus { 104 | outline: thin dotted; 105 | } 106 | 107 | /* 108 | * Improves readability when focused and also mouse hovered in all browsers. 109 | */ 110 | 111 | a:active, 112 | a:hover { 113 | outline: 0; 114 | } 115 | 116 | /* ========================================================================== 117 | Typography 118 | ========================================================================== */ 119 | 120 | /* 121 | * Addresses font sizes and margins set differently in IE 6/7. 122 | * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5, 123 | * and Chrome. 124 | */ 125 | 126 | h1 { 127 | font-size: 2em; 128 | margin: 0.67em 0; 129 | } 130 | 131 | h2 { 132 | font-size: 1.5em; 133 | margin: 0.83em 0; 134 | } 135 | 136 | h3 { 137 | font-size: 1.17em; 138 | margin: 1em 0; 139 | } 140 | 141 | h4 { 142 | font-size: 1em; 143 | margin: 1.33em 0; 144 | } 145 | 146 | h5 { 147 | font-size: 0.83em; 148 | margin: 1.67em 0; 149 | } 150 | 151 | h6 { 152 | font-size: 0.75em; 153 | margin: 2.33em 0; 154 | } 155 | 156 | /* 157 | * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome. 158 | */ 159 | 160 | abbr[title] { 161 | border-bottom: 1px dotted; 162 | } 163 | 164 | /* 165 | * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. 166 | */ 167 | 168 | b, 169 | strong { 170 | font-weight: bold; 171 | } 172 | 173 | blockquote { 174 | margin: 1em 40px; 175 | } 176 | 177 | /* 178 | * Addresses styling not present in Safari 5 and Chrome. 179 | */ 180 | 181 | dfn { 182 | font-style: italic; 183 | } 184 | 185 | /* 186 | * Addresses styling not present in IE 6/7/8/9. 187 | */ 188 | 189 | mark { 190 | background: #ff0; 191 | color: #000; 192 | } 193 | 194 | /* 195 | * Addresses margins set differently in IE 6/7. 196 | */ 197 | 198 | p, 199 | pre { 200 | margin: 1em 0; 201 | } 202 | 203 | /* 204 | * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome. 205 | */ 206 | 207 | code, 208 | kbd, 209 | pre, 210 | samp { 211 | font-family: monospace, serif; 212 | _font-family: 'courier new', monospace; 213 | font-size: 1em; 214 | } 215 | 216 | /* 217 | * Improves readability of pre-formatted text in all browsers. 218 | */ 219 | 220 | pre { 221 | white-space: pre; 222 | white-space: pre-wrap; 223 | word-wrap: break-word; 224 | } 225 | 226 | /* 227 | * Addresses CSS quotes not supported in IE 6/7. 228 | */ 229 | 230 | q { 231 | quotes: none; 232 | } 233 | 234 | /* 235 | * Addresses `quotes` property not supported in Safari 4. 236 | */ 237 | 238 | q:before, 239 | q:after { 240 | content: ''; 241 | content: none; 242 | } 243 | 244 | /* 245 | * Addresses inconsistent and variable font size in all browsers. 246 | */ 247 | 248 | small { 249 | font-size: 80%; 250 | } 251 | 252 | /* 253 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 254 | */ 255 | 256 | sub, 257 | sup { 258 | font-size: 75%; 259 | line-height: 0; 260 | position: relative; 261 | vertical-align: baseline; 262 | } 263 | 264 | sup { 265 | top: -0.5em; 266 | } 267 | 268 | sub { 269 | bottom: -0.25em; 270 | } 271 | 272 | /* ========================================================================== 273 | Lists 274 | ========================================================================== */ 275 | 276 | /* 277 | * Addresses margins set differently in IE 6/7. 278 | */ 279 | 280 | dl, 281 | menu, 282 | ol, 283 | ul { 284 | margin: 1em 0; 285 | } 286 | 287 | dd { 288 | margin: 0 0 0 40px; 289 | } 290 | 291 | /* 292 | * Addresses paddings set differently in IE 6/7. 293 | */ 294 | 295 | menu, 296 | ol, 297 | ul { 298 | padding: 0 0 0 40px; 299 | } 300 | 301 | /* 302 | * Corrects list images handled incorrectly in IE 7. 303 | */ 304 | 305 | nav ul, 306 | nav ol { 307 | list-style: none; 308 | list-style-image: none; 309 | } 310 | 311 | /* ========================================================================== 312 | Embedded content 313 | ========================================================================== */ 314 | 315 | /* 316 | * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3. 317 | * 2. Improves image quality when scaled in IE 7. 318 | */ 319 | 320 | img { 321 | border: 0; /* 1 */ 322 | -ms-interpolation-mode: bicubic; /* 2 */ 323 | } 324 | 325 | /* 326 | * Corrects overflow displayed oddly in IE 9. 327 | */ 328 | 329 | svg:not(:root) { 330 | overflow: hidden; 331 | } 332 | 333 | /* ========================================================================== 334 | Figures 335 | ========================================================================== */ 336 | 337 | /* 338 | * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11. 339 | */ 340 | 341 | figure { 342 | margin: 0; 343 | } 344 | 345 | /* ========================================================================== 346 | Forms 347 | ========================================================================== */ 348 | 349 | /* 350 | * Corrects margin displayed oddly in IE 6/7. 351 | */ 352 | 353 | form { 354 | margin: 0; 355 | } 356 | 357 | /* 358 | * Define consistent border, margin, and padding. 359 | */ 360 | 361 | fieldset { 362 | border: 1px solid #c0c0c0; 363 | margin: 0 2px; 364 | padding: 0.35em 0.625em 0.75em; 365 | } 366 | 367 | /* 368 | * 1. Corrects color not being inherited in IE 6/7/8/9. 369 | * 2. Corrects text not wrapping in Firefox 3. 370 | * 3. Corrects alignment displayed oddly in IE 6/7. 371 | */ 372 | 373 | legend { 374 | border: 0; /* 1 */ 375 | padding: 0; 376 | white-space: normal; /* 2 */ 377 | *margin-left: -7px; /* 3 */ 378 | } 379 | 380 | /* 381 | * 1. Corrects font size not being inherited in all browsers. 382 | * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, 383 | * and Chrome. 384 | * 3. Improves appearance and consistency in all browsers. 385 | */ 386 | 387 | button, 388 | input, 389 | select, 390 | textarea { 391 | font-size: 100%; /* 1 */ 392 | margin: 0; /* 2 */ 393 | vertical-align: baseline; /* 3 */ 394 | *vertical-align: middle; /* 3 */ 395 | } 396 | 397 | /* 398 | * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in 399 | * the UA stylesheet. 400 | */ 401 | 402 | button, 403 | input { 404 | line-height: normal; 405 | } 406 | 407 | /* 408 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 409 | * and `video` controls. 410 | * 2. Corrects inability to style clickable `input` types in iOS. 411 | * 3. Improves usability and consistency of cursor style between image-type 412 | * `input` and others. 413 | * 4. Removes inner spacing in IE 7 without affecting normal text inputs. 414 | * Known issue: inner spacing remains in IE 6. 415 | */ 416 | 417 | button, 418 | html input[type="button"], /* 1 */ 419 | input[type="reset"], 420 | input[type="submit"] { 421 | -webkit-appearance: button; /* 2 */ 422 | cursor: pointer; /* 3 */ 423 | *overflow: visible; /* 4 */ 424 | } 425 | 426 | /* 427 | * Re-set default cursor for disabled elements. 428 | */ 429 | 430 | button[disabled], 431 | input[disabled] { 432 | cursor: default; 433 | } 434 | 435 | /* 436 | * 1. Addresses box sizing set to content-box in IE 8/9. 437 | * 2. Removes excess padding in IE 8/9. 438 | * 3. Removes excess padding in IE 7. 439 | * Known issue: excess padding remains in IE 6. 440 | */ 441 | 442 | input[type="checkbox"], 443 | input[type="radio"] { 444 | box-sizing: border-box; /* 1 */ 445 | padding: 0; /* 2 */ 446 | *height: 13px; /* 3 */ 447 | *width: 13px; /* 3 */ 448 | } 449 | 450 | /* 451 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 452 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 453 | * (include `-moz` to future-proof). 454 | */ 455 | /* 456 | input[type="search"] { 457 | -webkit-appearance: textfield; 458 | -moz-box-sizing: content-box; 459 | -webkit-box-sizing: content-box; 460 | box-sizing: content-box; 461 | } 462 | */ 463 | 464 | /* 465 | * Removes inner padding and search cancel button in Safari 5 and Chrome 466 | * on OS X. 467 | */ 468 | 469 | /* input[type="search"]::-webkit-search-cancel-button, 470 | input[type="search"]::-webkit-search-decoration { 471 | -webkit-appearance: none; 472 | } */ 473 | 474 | /* 475 | * Removes inner padding and border in Firefox 3+. 476 | */ 477 | 478 | button::-moz-focus-inner, 479 | input::-moz-focus-inner { 480 | border: 0; 481 | padding: 0; 482 | } 483 | 484 | /* 485 | * 1. Removes default vertical scrollbar in IE 6/7/8/9. 486 | * 2. Improves readability and alignment in all browsers. 487 | */ 488 | 489 | textarea { 490 | overflow: auto; /* 1 */ 491 | vertical-align: top; /* 2 */ 492 | } 493 | 494 | /* ========================================================================== 495 | Tables 496 | ========================================================================== */ 497 | 498 | /* 499 | * Remove most spacing between table cells. 500 | */ 501 | 502 | table { 503 | border-collapse: collapse; 504 | border-spacing: 0; 505 | } 506 | -------------------------------------------------------------------------------- /app/assets/stylesheets/popup.css: -------------------------------------------------------------------------------- 1 | /* http://davidwalsh.name/css-tooltips */ 2 | /* base CSS element */ 3 | .popup { 4 | background: #eee; 5 | border: 1px solid #ccc; 6 | padding: 10px; 7 | border-radius: 8px; 8 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 9 | position: fixed; 10 | max-width: 300px; 11 | font-size: 12px; 12 | 13 | display: none; 14 | margin-left: 2px; 15 | 16 | margin-top: 30px; 17 | } 18 | 19 | .popup.above { 20 | margin-top: -10px; 21 | } 22 | 23 | .popup.left { 24 | margin-left: -20px; 25 | } 26 | 27 | .popup.right { 28 | margin-left: 40px; 29 | } 30 | 31 | .pop_content { 32 | max-height: 225px; 33 | overflow-y: auto; 34 | } 35 | 36 | .pop_content > p { 37 | margin-top: 0; 38 | } 39 | 40 | /* below */ 41 | .popup:before { 42 | position: absolute; 43 | display: inline-block; 44 | border-bottom: 10px solid #eee; 45 | border-right: 10px solid transparent; 46 | border-left: 10px solid transparent; 47 | border-bottom-color: rgba(0, 0, 0, 0.2); 48 | left: 50%; 49 | top: -10px; 50 | margin-left: -6px; 51 | content: ''; 52 | } 53 | 54 | .popup:after { 55 | position: absolute; 56 | display: inline-block; 57 | border-bottom: 9px solid #eee; 58 | border-right: 9px solid transparent; 59 | border-left: 9px solid transparent; 60 | left: 50%; 61 | top: -9px; 62 | margin-left: -5px; 63 | content: ''; 64 | } 65 | 66 | /* above */ 67 | .popup.above:before { 68 | border-bottom: none; 69 | border-top: 10px solid #eee; 70 | border-top-color: rgba(0, 0, 0, 0.2); 71 | top: 100%; 72 | } 73 | 74 | .popup.above:after { 75 | border-bottom: none; 76 | border-top: 9px solid #eee; 77 | top: 100%; 78 | } 79 | 80 | .popup.left:before, 81 | .popup.left:after 82 | { 83 | left: 20px; 84 | } 85 | 86 | .popup.right:before, 87 | .popup.right:after 88 | { 89 | left: auto; 90 | right: 20px; 91 | } 92 | 93 | 94 | .popup.show, .popup.on { 95 | display: block; 96 | } -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/ebooks_controller.rb: -------------------------------------------------------------------------------- 1 | class EbooksController < ApplicationController 2 | 3 | before_filter :authenticate_user! 4 | 5 | def index 6 | @ebooks = Ebook.order(:title).page(params[:page]) 7 | end 8 | 9 | def new 10 | @ebook = Ebook.new 11 | @ebook.cover = "foo" 12 | @ebook.title = "foo" 13 | @ebook.titles = "foo" 14 | @ebook.languages = "foo" 15 | @ebook.contributors = "foo" 16 | @ebook.coverages = "foo" 17 | @ebook.creators = "foo" 18 | @ebook.dates = "foo" 19 | @ebook.descriptions = "foo" 20 | @ebook.formats = "foo" 21 | @ebook.publishers = "foo" 22 | @ebook.relations = "foo" 23 | @ebook.rights = "foo" 24 | @ebook.sources = "foo" 25 | @ebook.subjects = "foo" 26 | @ebook.types = "foo" 27 | @ebook.uniqueid = "foo" 28 | @ebook.epubver = "foo" 29 | end 30 | 31 | def create 32 | @ebook = Ebook.create(ebook_params) 33 | p = EPUB::Parser.parse(@ebook.book.path) 34 | @ebook.cover = "data:"+p.cover_image.media_type+"; base64," + Base64.encode64(p.cover_image.read) unless p.cover_image.nil? 35 | @ebook.title = p.title 36 | @ebook.titles = p.metadata.to_h[:titles][0].to_s unless p.metadata.to_h[:titles].nil? 37 | @ebook.languages = p.metadata.to_h[:languages][0].to_s unless p.metadata.to_h[:languages].nil? 38 | @ebook.contributors = p.metadata.to_h[:contributors][0].to_s unless p.metadata.to_h[:contributors].nil? 39 | @ebook.coverages = p.metadata.to_h[:coverages][0].to_s unless p.metadata.to_h[:coverages].nil? 40 | @ebook.creators = p.metadata.to_h[:creators][0].to_s unless p.metadata.to_h[:creators].nil? 41 | @ebook.dates = p.metadata.to_h[:dates][0].to_s unless p.metadata.to_h[:dates].nil? 42 | @ebook.descriptions = p.metadata.to_h[:descriptions][0].to_s unless p.metadata.to_h[:descriptions].nil? 43 | @ebook.formats = p.metadata.to_h[:formats][0].to_s unless p.metadata.to_h[:formats].nil? 44 | @ebook.publishers = p.metadata.to_h[:publishers][0].to_s unless p.metadata.to_h[:publishers].nil? 45 | @ebook.relations = p.metadata.to_h[:relations][0].to_s unless p.metadata.to_h[:relations].nil? 46 | @ebook.rights = p.metadata.to_h[:rights][0].to_s unless p.metadata.to_h[:rights].nil? 47 | @ebook.sources = p.metadata.to_h[:sources][0].to_s unless p.metadata.to_h[:sources].nil? 48 | @ebook.subjects = p.metadata.to_h[:subjects][0].to_s unless p.metadata.to_h[:subjects].nil? 49 | @ebook.types = p.metadata.to_h[:types][0].to_s unless p.metadata.to_h[:types].nil? 50 | @ebook.uniqueid = p.metadata.to_h[:uniqueid][0].to_s unless p.metadata.to_h[:uniqueid].nil? 51 | @ebook.epubver = p.metadata.to_h[:epubver][0].to_s unless p.metadata.to_h[:epubver].nil? 52 | 53 | if @ebook.save 54 | render json: { message: "success" }, :status => 200 55 | else 56 | render json: { error: @ebook.errors.full_messages.join(',')}, :status => 400 57 | end 58 | end 59 | 60 | def show 61 | @ebook = Ebook.find(params[:id]) 62 | render :layout => "reader" 63 | end 64 | 65 | def destroy 66 | @ebook = Ebook.find(params[:id]) 67 | @ebook.book.destroy 68 | @ebook.destroy 69 | redirect_to ebooks_url 70 | end 71 | 72 | def choose 73 | end 74 | 75 | def import 76 | @ebooks = [] 77 | @path = params["path"] 78 | epubfiles = File.join(params["path"],"**","*.epub") 79 | Dir.glob(epubfiles).each do |file| 80 | ebook = Ebook.new(:book_local_path => file) 81 | p = EPUB::Parser.parse(file) 82 | ebook.cover = "data:"+p.cover_image.media_type+"; base64," + Base64.encode64(p.cover_image.read) unless p.cover_image.nil? 83 | ebook.title = p.title 84 | ebook.titles = p.metadata.to_h[:titles][0].to_s unless p.metadata.to_h[:titles].nil? 85 | ebook.languages = p.metadata.to_h[:languages][0].to_s unless p.metadata.to_h[:languages].nil? 86 | ebook.contributors = p.metadata.to_h[:contributors][0].to_s unless p.metadata.to_h[:contributors].nil? 87 | ebook.coverages = p.metadata.to_h[:coverages][0].to_s unless p.metadata.to_h[:coverages].nil? 88 | ebook.creators = p.metadata.to_h[:creators][0].to_s unless p.metadata.to_h[:creators].nil? 89 | ebook.dates = p.metadata.to_h[:dates][0].to_s unless p.metadata.to_h[:dates].nil? 90 | ebook.descriptions = p.metadata.to_h[:descriptions][0].to_s unless p.metadata.to_h[:descriptions].nil? 91 | ebook.formats = p.metadata.to_h[:formats][0].to_s unless p.metadata.to_h[:formats].nil? 92 | ebook.publishers = p.metadata.to_h[:publishers][0].to_s unless p.metadata.to_h[:publishers].nil? 93 | ebook.relations = p.metadata.to_h[:relations][0].to_s unless p.metadata.to_h[:relations].nil? 94 | ebook.rights = p.metadata.to_h[:rights][0].to_s unless p.metadata.to_h[:rights].nil? 95 | ebook.sources = p.metadata.to_h[:sources][0].to_s unless p.metadata.to_h[:sources].nil? 96 | ebook.subjects = p.metadata.to_h[:subjects][0].to_s unless p.metadata.to_h[:subjects].nil? 97 | ebook.types = p.metadata.to_h[:types][0].to_s unless p.metadata.to_h[:types].nil? 98 | ebook.uniqueid = p.metadata.to_h[:uniqueid][0].to_s unless p.metadata.to_h[:uniqueid].nil? 99 | ebook.epubver = p.metadata.to_h[:epubver][0].to_s unless p.metadata.to_h[:epubver].nil? 100 | 101 | if ebook.save 102 | @ebooks << ebook 103 | end 104 | 105 | end 106 | 107 | end 108 | 109 | private 110 | 111 | def ebook_params 112 | params.require(:ebook).permit(:title,:titles,:languages,:contributors,:coverages,:creators,:dates,:descriptions,:formats,:publishers,:relations,:rights,:sources,:subjects,:types,:uniqueid,:epubver,:book) 113 | end 114 | 115 | end 116 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/ebooks_helper.rb: -------------------------------------------------------------------------------- 1 | module EbooksHelper 2 | 3 | def get_url(url) 4 | url[/^[^\?]*/] 5 | end 6 | 7 | def ebook_cover(cover) 8 | if cover.nil? 9 | image_path("ebook-icon.png") 10 | else 11 | cover 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/ebook.rb: -------------------------------------------------------------------------------- 1 | class Ebook < ApplicationRecord 2 | attr_accessor :book_local_path ## Added extra special field for local filesystem import. 3 | has_attached_file :book 4 | validates_attachment :book, 5 | :presence => true, 6 | :content_type => { :content_type => /\A.*\Z/ }, 7 | :size => { :less_than => 128.megabytes } 8 | 9 | ## Everything below here is special code to deal with importing from the local filesystem ## 10 | 11 | before_validation :import_local_book, :if => :book_local_path_provided? 12 | 13 | validates_presence_of :book_local_path_, :if => :book_local_path_provided?, :message => 'is not accessible' 14 | 15 | private 16 | 17 | def book_local_path_provided? 18 | !self.book_local_path.blank? 19 | end 20 | 21 | def import_local_book 22 | self.book = do_import_local_book 23 | self.book_local_path_ = book_local_path 24 | end 25 | 26 | def do_import_local_book 27 | io = open(book_local_path) 28 | 29 | def io.original_filename; path.split('/').last; end 30 | 31 | io.original_filename.blank? ? nil : io 32 | 33 | rescue # Catch URL errors with validations, instead of exceptions. 34 | 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable and :omniauthable 4 | devise :database_authenticatable, :registerable, 5 | :recoverable, :rememberable, :trackable, :validatable 6 | end 7 | -------------------------------------------------------------------------------- /app/views/ebooks/_ebook.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= ebook.id %> 3 | <%= ebook.title %> 4 | <%= ebook.creators %> 5 | 6 | -------------------------------------------------------------------------------- /app/views/ebooks/_nobooks.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Your Library is Empty

4 |

You can <%= link_to "upload some books.", new_ebook_path %> to fill up your library, or <%= link_to "choose from an existing local folder.", choose_ebooks_path %>

5 | <%= link_to "Upload Books", new_ebook_path, class: "btn btn-primary btn-lg" %> | 6 | <%= link_to "Import From Local Filesystem", choose_ebooks_path, class: "btn btn-default btn-lg" %> 7 |
8 |
9 | -------------------------------------------------------------------------------- /app/views/ebooks/choose.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Import from Filesystem

4 |

You can import books from your local filesystem, by entering a path in the field below.
The path you specify will be searched recursively for books ending in .epub, and automatically added to the library.

5 |
6 | 7 | <%= form_tag(import_ebooks_path, method: 'post') do %> 8 |
9 | <%= label_tag 'path', "Local Filesystem Path", class: 'col-2 col-form-label' %> 10 |
11 | <%= text_field_tag 'path', nil, placeholder: 'Enter a location on your filesystem, such as: /tmp', class: 'form-control' %> 12 |
13 |
14 | <%= submit_tag "Import", class: "btn btn-primary" %> 15 | <% end %> 16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /app/views/ebooks/import.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Import Results

4 |

The following eBooks were found at the path <%= @path %> and were processed

5 |
6 |
7 | 8 | <% if @ebooks.count < 1 %> 9 | <%= render "nobooks" %> 10 | <% else %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <%= render @ebooks %> 19 | 20 |
CoverTitleAuthor(s)
21 | <% end %> 22 | -------------------------------------------------------------------------------- /app/views/ebooks/index.html.erb: -------------------------------------------------------------------------------- 1 | <% if @ebooks.count < 1 %> 2 | <%= render "nobooks" %> 3 | <% else %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <%= render @ebooks %> 12 | 13 |
CoverTitleAuthor(s)
14 | <%= paginate @ebooks %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/ebooks/index.js.erb: -------------------------------------------------------------------------------- 1 | $("#ebooks").append("<%= escape_javascript(render(@ebooks)) %>"); -------------------------------------------------------------------------------- /app/views/ebooks/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@ebook, html: { multipart: true, class: "dropzone"}) do |f| %> 2 | <%= f.hidden_field :title %> 3 | <%= f.hidden_field :titles %> 4 | <%= f.hidden_field :languages %> 5 | <%= f.hidden_field :contributors %> 6 | <%= f.hidden_field :coverages %> 7 | <%= f.hidden_field :creators %> 8 | <%= f.hidden_field :dates %> 9 | <%= f.hidden_field :descriptions %> 10 | <%= f.hidden_field :formats %> 11 | <%= f.hidden_field :publishers %> 12 | <%= f.hidden_field :relations %> 13 | <%= f.hidden_field :rights %> 14 | <%= f.hidden_field :sources %> 15 | <%= f.hidden_field :subjects %> 16 | <%= f.hidden_field :types %> 17 | <%= f.hidden_field :uniqueid %> 18 | <%= f.hidden_field :epubver %> 19 | 20 |
21 | <%= f.file_field :book %>
22 | <%= f.submit "Upload" %> 23 |
24 | <% end %> 25 | -------------------------------------------------------------------------------- /app/views/ebooks/show.html.erb: -------------------------------------------------------------------------------- 1 | 27 |
28 | 29 |
30 |
31 | Menu 32 |
33 |
34 | 35 |   –   36 | 37 |
38 |
39 | Bookmark 40 | Fullscreen 41 | <%= link_to "Delete", url_for(action: :destroy, id: @ebook.id), method: :delete, class: "eicon-trash", data: {confirm: "Are you sure?"} %> 42 | Close 43 |
44 |
45 | 46 |
47 | 48 |
49 | 50 | 51 |
52 |
53 |
54 | 55 | 69 | -------------------------------------------------------------------------------- /app/views/layouts/_header.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 23 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Readingcloud 8 | <%= csrf_meta_tags %> 9 | 10 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 11 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 12 | 13 | 14 | 15 | <%= render 'layouts/header' %> 16 |
17 | <% flash.each do |name, msg| %> 18 | <%= content_tag(:div, msg, class: "alert alert-info") %> 19 | <% end %> 20 | <%= yield %> 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/layouts/reader.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <%= csrf_meta_tags %> 12 | 13 | <%= stylesheet_link_tag 'ebook-reader', media: 'all', 'data-turbolinks-track': 'reload' %> 14 | <%= javascript_include_tag 'ebook-reader', 'data-turbolinks-track': 'reload' %> 15 | 16 | 17 | <%= yield %> 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 9 |
10 | 11 |
12 | <%= f.submit "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "users/shared/links" %> 17 | -------------------------------------------------------------------------------- /app/views/users/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /app/views/users/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /app/views/users/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /app/views/users/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %> 12 | <%= f.password_field :password, autofocus: true, autocomplete: "off" %> 13 |
14 | 15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 18 |
19 | 20 |
21 | <%= f.submit "Change my password" %> 22 |
23 | <% end %> 24 | 25 | <%= render "users/shared/links" %> 26 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Send me reset password instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "users/shared/links" %> 17 | -------------------------------------------------------------------------------- /app/views/users/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "off" %> 18 | <% if @minimum_password_length %> 19 |
20 | <%= @minimum_password_length %> characters minimum 21 | <% end %> 22 |
23 | 24 |
25 | <%= f.label :password_confirmation %>
26 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 27 |
28 | 29 |
30 | <%= f.label :current_password %> (we need your current password to confirm your changes)
31 | <%= f.password_field :current_password, autocomplete: "off" %> 32 |
33 | 34 |
35 | <%= f.submit "Update" %> 36 |
37 | <% end %> 38 | 39 |

Cancel my account

40 | 41 |

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

42 | 43 | <%= link_to "Back", :back %> 44 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), :html => {:class => "form-signin"}) do |f| %> 2 | <%= devise_error_messages! %> 3 | 4 | 5 | 6 | 7 |
8 | <%= f.label :email, class: "sr-only" %>
9 | <%= f.email_field :email, autofocus: true, class: "form-control", placeholder: "Email Address" %> 10 |
11 | 12 |
13 | <%= f.label :password, class: "sr-only" %> 14 | <%= f.password_field :password, autocomplete: "off", class: "form-control", placeholder: "Password" %> 15 |
16 | 17 |
18 | <%= f.label :password_confirmation, class: "sr-only" %> 19 | <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control", placeholder: "Password (again)" %> 20 |
21 | 22 |
23 | <%= f.submit "Sign up", class: "btn btn-lg btn-primary btn-block" %> 24 |
25 | 26 |
27 | <%= render "users/shared/links" %> 28 | 29 | 30 | <% end %> 31 | -------------------------------------------------------------------------------- /app/views/users/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= form_for(resource, as: resource_name, url: session_path(resource_name), :html => {:class => "form-signin"}) do |f| %> 4 | 5 | 6 |
7 | <%= f.label :email, class: "sr-only" %>
8 | <%= f.email_field :email, autofocus: true, class: "form-control", placeholder: "Email Address" %> 9 |
10 | 11 |
12 | <%= f.label :password, class: "sr-only" %>
13 | <%= f.password_field :password, autocomplete: "off", class: "form-control", placeholder: "Password" %> 14 |
15 | 16 | <% if devise_mapping.rememberable? -%> 17 |
18 | <%= f.check_box :remember_me %> 19 | <%= f.label :remember_me %> 20 |
21 | <% end -%> 22 | 23 |
24 | <%= f.submit "Log in", class: "btn btn-lg btn-primary btn-block" %> 25 |
26 |
27 | <%= render "users/shared/links" %> 28 | <% end %> 29 | 30 | -------------------------------------------------------------------------------- /app/views/users/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /app/views/users/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "users/shared/links" %> 17 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/install-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ########### 3 | 4 | HERE=$PWD 5 | 6 | echo "Please enter your user password, when prompted." 7 | 8 | echo "Installing system packages as root" 9 | 10 | sudo apt-get install build-essential libsqlite3-dev libssl-dev zlib1g-dev libreadline-dev libyaml-dev nodejs git 11 | 12 | echo "Installing everything else as current user." 13 | 14 | # Install rbenv for managing enabling of multiple rubies. 15 | git clone git://github.com/sstephenson/rbenv.git ~/.rbenv 16 | echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc 17 | echo 'eval "$(rbenv init -)"' >> ~/.bashrc 18 | 19 | echo "Now, please run bin/install_readingcloud.sh to finish the installation process." 20 | 21 | exec $SHELL 22 | 23 | -------------------------------------------------------------------------------- /bin/install-readingcloud.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | HERE=$PWD 3 | # Install the ruby-build plugin for easy building of rubies. 4 | mkdir -p ~/.rbenv/plugins 5 | cd ~/.rbenv/plugins 6 | git clone git://github.com/sstephenson/ruby-build.git 7 | cd $HERE 8 | 9 | rbenv install 2.4.0 10 | rbenv global 2.4.0 11 | 12 | gem install bundler 13 | 14 | bundle install 15 | 16 | rake db:setup 17 | 18 | echo "You can now start the server using 'rails s'" 19 | 20 | -------------------------------------------------------------------------------- /bin/install-redhat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ########### 3 | 4 | HERE=$PWD 5 | 6 | echo "Please enter your user password, when prompted." 7 | 8 | echo "Installing system packages as root" 9 | 10 | sudo yum -y groupinstall "Development" 11 | sudo yum -y install epel-release sqlite-devel openssl-devel zlib-devel readline-devel libyaml-devel git 12 | sudo yum -y install nodejs 13 | 14 | echo "Installing everything else as current user." 15 | 16 | # Install rbenv for managing enabling of multiple rubies. 17 | git clone git://github.com/sstephenson/rbenv.git ~/.rbenv 18 | echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc 19 | echo 'eval "$(rbenv init -)"' >> ~/.bashrc 20 | 21 | echo "Now, please run bin/install_readingcloud.sh to finish the installation process." 22 | 23 | exec $SHELL 24 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Readingcloud 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => 'public, max-age=172800' 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | # Compress JavaScripts and CSS. 22 | config.assets.js_compressor = :uglifier 23 | # config.assets.css_compressor = :sass 24 | 25 | # Do not fallback to assets pipeline if a precompiled asset is missed. 26 | config.assets.compile = false 27 | 28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 29 | 30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 31 | # config.action_controller.asset_host = 'http://assets.example.com' 32 | 33 | # Specifies the header that your server uses for sending files. 34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [ :request_id ] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "readingcloud_#{Rails.env}" 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 65 | # the I18n.default_locale when a translation cannot be found). 66 | config.i18n.fallbacks = true 67 | 68 | # Send deprecation notices to registered listeners. 69 | config.active_support.deprecation = :notify 70 | 71 | # Use default logging formatter so that PID and timestamp are not suppressed. 72 | config.log_formatter = ::Logger::Formatter.new 73 | 74 | # Use a different logger for distributed setups. 75 | # require 'syslog/logger' 76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 77 | 78 | if ENV["RAILS_LOG_TO_STDOUT"].present? 79 | logger = ActiveSupport::Logger.new(STDOUT) 80 | logger.formatter = config.log_formatter 81 | config.logger = ActiveSupport::TaggedLogging.new(logger) 82 | end 83 | 84 | # Do not dump schema after migrations. 85 | config.active_record.dump_schema_after_migration = false 86 | end 87 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | Rails.application.config.assets.precompile += %w( ebook-reader.css ) 13 | Rails.application.config.assets.precompile += %w( ebook-reader.js ) 14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Require `belongs_to` associations by default. Previous versions had false. 18 | Rails.application.config.active_record.belongs_to_required_by_default = true 19 | 20 | # Do not halt callback chains when a callback returns false. Previous versions had true. 21 | ActiveSupport.halt_callback_chains_on_return_false = false 22 | 23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 25 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_readingcloud_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | password_change: 27 | subject: "Password Changed" 28 | omniauth_callbacks: 29 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 30 | success: "Successfully authenticated from %{kind} account." 31 | passwords: 32 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 33 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 34 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 35 | updated: "Your password has been changed successfully. You are now signed in." 36 | updated_not_active: "Your password has been changed successfully." 37 | registrations: 38 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 39 | signed_up: "Welcome! You have signed up successfully." 40 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 41 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 42 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 43 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 44 | updated: "Your account has been updated successfully." 45 | sessions: 46 | signed_in: "Signed in successfully." 47 | signed_out: "Signed out successfully." 48 | already_signed_out: "Signed out successfully." 49 | unlocks: 50 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 51 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 52 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 53 | errors: 54 | messages: 55 | already_confirmed: "was already confirmed, please try signing in" 56 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 57 | expired: "has expired, please request a new one" 58 | not_found: "not found" 59 | not_locked: "was not locked" 60 | not_saved: 61 | one: "1 error prohibited this %{resource} from being saved:" 62 | other: "%{count} errors prohibited this %{resource} from being saved:" 63 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum, this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # The code in the `on_worker_boot` will be called if you are using 36 | # clustered mode by specifying a number of `workers`. After each worker 37 | # process is booted this block will be run, if you are using `preload_app!` 38 | # option you will want to use this block to reconnect to any threads 39 | # or connections that may have been created at application boot, Ruby 40 | # cannot share connections between processes. 41 | # 42 | # on_worker_boot do 43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 44 | # end 45 | 46 | # Allow puma to be restarted by `rails restart` command. 47 | plugin :tmp_restart 48 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | devise_for :users 3 | 4 | resources :ebooks do 5 | collection do 6 | post 'import' 7 | get 'choose' 8 | end 9 | end 10 | 11 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html 12 | root 'ebooks#index' 13 | end 14 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 77e7b26b4ad090830884aedeb335b8dc020088fdd10fafc245a2f3256d489fed0af8aba622eb87ea35597f3102a41427070297dd21ded29579ac6ef9e6846ef9 15 | 16 | test: 17 | secret_key_base: 8bbb0721c282a37cfcabf660b71ec70711679b078997b0a5a089043ebf6e85b7ea6acd3b00d8a4a7b03c0f5eba1b9e2e55b5e1e3820247681d838cd3d4964d36 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /db/migrate/20170105071407_create_ebooks.rb: -------------------------------------------------------------------------------- 1 | class CreateEbooks < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :ebooks do |t| 4 | t.string :originalfile 5 | t.string :title 6 | t.string :titles 7 | t.string :languages 8 | t.string :contributors 9 | t.string :coverages 10 | t.string :creators 11 | t.string :dates 12 | t.string :descriptions 13 | t.string :formats 14 | t.string :publishers 15 | t.string :relations 16 | t.string :rights 17 | t.string :sources 18 | t.string :subjects 19 | t.string :types 20 | t.string :uniqueid 21 | t.string :epubver 22 | 23 | t.timestamps 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20170105212429_add_attachment_book_to_ebooks.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentBookToEbooks < ActiveRecord::Migration 2 | def self.up 3 | change_table :ebooks do |t| 4 | t.attachment :book 5 | end 6 | end 7 | 8 | def self.down 9 | remove_attachment :ebooks, :book 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170105230506_remove_original_file_from_ebooks.rb: -------------------------------------------------------------------------------- 1 | class RemoveOriginalFileFromEbooks < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :ebooks, :originalfile, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170106043812_add_cover_to_ebooks.rb: -------------------------------------------------------------------------------- 1 | class AddCoverToEbooks < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :ebooks, :cover, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170107040335_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :users do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.string :current_sign_in_ip 20 | t.string :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps null: false 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /db/migrate/20170218013642_add_book_local_path_to_ebooks.rb: -------------------------------------------------------------------------------- 1 | class AddBookLocalPathToEbooks < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :ebooks, :book_local_path_, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20170218013642) do 14 | 15 | create_table "ebooks", force: :cascade do |t| 16 | t.string "title" 17 | t.string "titles" 18 | t.string "languages" 19 | t.string "contributors" 20 | t.string "coverages" 21 | t.string "creators" 22 | t.string "dates" 23 | t.string "descriptions" 24 | t.string "formats" 25 | t.string "publishers" 26 | t.string "relations" 27 | t.string "rights" 28 | t.string "sources" 29 | t.string "subjects" 30 | t.string "types" 31 | t.string "uniqueid" 32 | t.string "epubver" 33 | t.datetime "created_at", null: false 34 | t.datetime "updated_at", null: false 35 | t.string "book_file_name" 36 | t.string "book_content_type" 37 | t.integer "book_file_size" 38 | t.datetime "book_updated_at" 39 | t.text "cover" 40 | t.string "book_local_path_" 41 | end 42 | 43 | create_table "users", force: :cascade do |t| 44 | t.string "email", default: "", null: false 45 | t.string "encrypted_password", default: "", null: false 46 | t.string "reset_password_token" 47 | t.datetime "reset_password_sent_at" 48 | t.datetime "remember_created_at" 49 | t.integer "sign_in_count", default: 0, null: false 50 | t.datetime "current_sign_in_at" 51 | t.datetime "last_sign_in_at" 52 | t.string "current_sign_in_ip" 53 | t.string "last_sign_in_ip" 54 | t.datetime "created_at", null: false 55 | t.datetime "updated_at", null: false 56 | t.index ["email"], name: "index_users_on_email", unique: true 57 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /docker-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rake db:migrate 3 | bundle exec rails server -b 0.0.0.0 4 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/css/popup.css: -------------------------------------------------------------------------------- 1 | /* http://davidwalsh.name/css-tooltips */ 2 | /* base CSS element */ 3 | .popup { 4 | background: #eee; 5 | border: 1px solid #ccc; 6 | padding: 10px; 7 | border-radius: 8px; 8 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 9 | position: fixed; 10 | max-width: 300px; 11 | font-size: 12px; 12 | 13 | display: none; 14 | margin-left: 2px; 15 | 16 | margin-top: 30px; 17 | } 18 | 19 | .popup.above { 20 | margin-top: -10px; 21 | } 22 | 23 | .popup.left { 24 | margin-left: -20px; 25 | } 26 | 27 | .popup.right { 28 | margin-left: 40px; 29 | } 30 | 31 | .pop_content { 32 | max-height: 225px; 33 | overflow-y: auto; 34 | } 35 | 36 | .pop_content > p { 37 | margin-top: 0; 38 | } 39 | 40 | /* below */ 41 | .popup:before { 42 | position: absolute; 43 | display: inline-block; 44 | border-bottom: 10px solid #eee; 45 | border-right: 10px solid transparent; 46 | border-left: 10px solid transparent; 47 | border-bottom-color: rgba(0, 0, 0, 0.2); 48 | left: 50%; 49 | top: -10px; 50 | margin-left: -6px; 51 | content: ''; 52 | } 53 | 54 | .popup:after { 55 | position: absolute; 56 | display: inline-block; 57 | border-bottom: 9px solid #eee; 58 | border-right: 9px solid transparent; 59 | border-left: 9px solid transparent; 60 | left: 50%; 61 | top: -9px; 62 | margin-left: -5px; 63 | content: ''; 64 | } 65 | 66 | /* above */ 67 | .popup.above:before { 68 | border-bottom: none; 69 | border-top: 10px solid #eee; 70 | border-top-color: rgba(0, 0, 0, 0.2); 71 | top: 100%; 72 | } 73 | 74 | .popup.above:after { 75 | border-bottom: none; 76 | border-top: 9px solid #eee; 77 | top: 100%; 78 | } 79 | 80 | .popup.left:before, 81 | .popup.left:after 82 | { 83 | left: 20px; 84 | } 85 | 86 | .popup.right:before, 87 | .popup.right:after 88 | { 89 | left: auto; 90 | right: 20px; 91 | } 92 | 93 | 94 | .popup.show, .popup.on { 95 | display: block; 96 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/favicon.ico -------------------------------------------------------------------------------- /public/font/entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/entypo.eot -------------------------------------------------------------------------------- /public/font/entypo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/font/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/entypo.ttf -------------------------------------------------------------------------------- /public/font/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/entypo.woff -------------------------------------------------------------------------------- /public/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/fontello.eot -------------------------------------------------------------------------------- /public/font/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2013 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /public/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/fontello.ttf -------------------------------------------------------------------------------- /public/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/font/fontello.woff -------------------------------------------------------------------------------- /public/js/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | //= require epub.min 18 | //= require hooks.min 19 | //= require reader.min 20 | //= require zip.min 21 | //= require localforage.min 22 | //= require dropzone 23 | //= require jquery.infinitescroll 24 | -------------------------------------------------------------------------------- /public/js/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /public/js/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/public/js/channels/.keep -------------------------------------------------------------------------------- /public/js/ebooks.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $(document).ready -> 6 | $("#ebooks .page").infinitescroll 7 | navSelector: "nav.pagination" # selector for the paged navigation (it will be hidden) 8 | nextSelector: "nav.pagination a[rel=next]" # selector for the NEXT link (to page 2) 9 | itemSelector: "#ebooks tr.post" # selector for all items you'll retrieve -------------------------------------------------------------------------------- /public/js/hooks.min.js: -------------------------------------------------------------------------------- 1 | EPUBJS.Hooks.register("beforeChapterDisplay").endnotes=function(a,b){var c=b.contents.querySelectorAll("a[href]"),d=Array.prototype.slice.call(c),e="epub:type",f="noteref",g=EPUBJS.core.folder(location.pathname),h=(g+EPUBJS.cssPath||g,{});EPUBJS.core.addCss(EPUBJS.cssPath+"popup.css",!1,b.render.document.head),d.forEach(function(a){function c(){var c,e,f=b.height,j=b.width,p=225;o||(c=l.cloneNode(!0),o=c.querySelector("p")),h[k]||(h[k]=document.createElement("div"),h[k].setAttribute("class","popup"),pop_content=document.createElement("div"),h[k].appendChild(pop_content),pop_content.appendChild(o),pop_content.setAttribute("class","pop_content"),b.render.document.body.appendChild(h[k]),h[k].addEventListener("mouseover",d,!1),h[k].addEventListener("mouseout",g,!1),b.on("renderer:pageChanged",i,this),b.on("renderer:pageChanged",g,this)),c=h[k],e=a.getBoundingClientRect(),m=e.left,n=e.top,c.classList.add("show"),popRect=c.getBoundingClientRect(),c.style.left=m-popRect.width/2+"px",c.style.top=n+"px",p>f/2.5&&(p=f/2.5,pop_content.style.maxHeight=p+"px"),popRect.height+n>=f-25?(c.style.top=n-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),m-popRect.width<=0?(c.style.left=m+"px",c.classList.add("left")):c.classList.remove("left"),m+popRect.width/2>=j?(c.style.left=m-300+"px",popRect=c.getBoundingClientRect(),c.style.left=m-popRect.width+"px",popRect.height+n>=f-25?(c.style.top=n-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),c.classList.add("right")):c.classList.remove("right")}function d(){h[k].classList.add("on")}function g(){h[k].classList.remove("on")}function i(){setTimeout(function(){h[k].classList.remove("show")},100)}var j,k,l,m,n,o,p=a.getAttribute(e);p==f&&(j=a.getAttribute("href"),k=j.replace("#",""),l=b.render.document.getElementById(k),a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",i,!1))}),a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").mathml=function(a,b){if(b.currentChapter.manifestProperties.indexOf("mathml")!==-1){b.render.iframe.contentWindow.mathmlCallback=a;var c=document.createElement("script");c.type="text/x-mathjax-config",c.innerHTML=' MathJax.Hub.Register.StartupHook("End",function () { window.mathmlCallback(); }); MathJax.Hub.Config({jax: ["input/TeX","input/MathML","output/SVG"],extensions: ["tex2jax.js","mml2jax.js","MathEvents.js"],TeX: {extensions: ["noErrors.js","noUndefined.js","autoload-all.js"]},MathMenu: {showRenderer: false},menuSettings: {zoom: "Click"},messageStyle: "none"}); ',b.doc.body.appendChild(c),EPUBJS.core.addScript("http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML",null,b.doc.head)}else a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").smartimages=function(a,b){var c=b.contents.querySelectorAll("img"),d=Array.prototype.slice.call(c),e=b.height;return"reflowable"!=b.layoutSettings.layout?void a():(d.forEach(function(a){var c=function(){var c,d=a.getBoundingClientRect(),f=d.height,g=d.top,h=a.getAttribute("data-height"),i=h||f,j=Number(getComputedStyle(a,"").fontSize.match(/(\d*(\.\d*)?)px/)[1]),k=j?j/2:0;e=b.contents.clientHeight,g<0&&(g=0),i+g>=e?(ge&&(a.style.maxHeight=e+"px",a.style.width="auto",d=a.getBoundingClientRect(),i=d.height),a.style.display="block",a.style.WebkitColumnBreakBefore="always",a.style.breakBefore="column"),a.setAttribute("data-height",c)):(a.style.removeProperty("max-height"),a.style.removeProperty("margin-top"))},d=function(){b.off("renderer:resized",c),b.off("renderer:chapterUnload",this)};a.addEventListener("load",c,!1),b.on("renderer:resized",c),b.on("renderer:chapterUnload",d),c()}),void(a&&a()))},EPUBJS.Hooks.register("beforeChapterDisplay").transculsions=function(a,b){var c=b.contents.querySelectorAll("[transclusion]"),d=Array.prototype.slice.call(c);d.forEach(function(a){function c(){j=g,k=h,j>chapter.colWidth&&(d=chapter.colWidth/j,j=chapter.colWidth,k*=d),f.width=j,f.height=k}var d,e=a.getAttribute("ref"),f=document.createElement("iframe"),g=a.getAttribute("width"),h=a.getAttribute("height"),i=a.parentNode,j=g,k=h;c(),b.listenUntil("renderer:resized","renderer:chapterUnloaded",c),f.src=e,i.replaceChild(f,a)}),a&&a()}; -------------------------------------------------------------------------------- /public/js/hooks.min.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"hooks.min.js","sources":["../../hooks/default/endnotes.js","../../hooks/default/mathml.js","../../hooks/default/smartimages.js","../../hooks/default/transculsions.js"],"names":["EPUBJS","Hooks","register","endnotes","callback","renderer","notes","contents","querySelectorAll","items","Array","prototype","slice","call","attr","type","folder","core","location","pathname","popups","cssPath","addCss","render","document","head","forEach","item","showPop","pop","itemRect","iheight","height","iwidth","width","maxHeight","txt","el","cloneNode","querySelector","id","createElement","setAttribute","pop_content","appendChild","body","addEventListener","onPop","offPop","on","hidePop","this","getBoundingClientRect","left","top","classList","add","popRect","style","remove","setTimeout","href","epubType","getAttribute","replace","getElementById","mathml","currentChapter","manifestProperties","indexOf","iframe","contentWindow","mathmlCallback","s","innerHTML","doc","addScript","smartimages","images","layoutSettings","layout","size","newHeight","rectHeight","oHeight","fontSize","Number","getComputedStyle","match","fontAdjust","clientHeight","display","removeProperty","unloaded","off","transculsions","trans","orginal_width","orginal_height","chapter","colWidth","ratio","src","parent","parentNode","listenUntil","replaceChild"],"mappings":"AAAAA,OAAOC,MAAMC,SAAS,wBAAwBC,SAAW,SAASC,EAAUC,GAE1E,GAAIC,GAAQD,EAASE,SAASC,iBAAiB,WAC9CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKP,GACnCQ,EAAO,YACPC,EAAO,UACPC,EAAShB,OAAOiB,KAAKD,OAAOE,SAASC,UAErCC,GADWJ,EAAShB,OAAOqB,SAAYL,KAGxChB,QAAOiB,KAAKK,OAAOtB,OAAOqB,QAAU,aAAa,EAAOhB,EAASkB,OAAOC,SAASC,MAGjFhB,EAAMiB,QAAQ,SAASC,GAqBtB,QAASC,KACR,GAICC,GAEAC,EALAC,EAAU1B,EAAS2B,OACnBC,EAAS5B,EAAS6B,MAGlBC,EAAY,GAGTC,KACHP,EAAMQ,EAAGC,WAAU,GACnBF,EAAMP,EAAIU,cAAc,MAKrBnB,EAAOoB,KACVpB,EAAOoB,GAAMhB,SAASiB,cAAc,OACpCrB,EAAOoB,GAAIE,aAAa,QAAS,SAEjCC,YAAcnB,SAASiB,cAAc,OAErCrB,EAAOoB,GAAII,YAAYD,aAEvBA,YAAYC,YAAYR,GACxBO,YAAYD,aAAa,QAAS,eAElCrC,EAASkB,OAAOC,SAASqB,KAAKD,YAAYxB,EAAOoB,IAGjDpB,EAAOoB,GAAIM,iBAAiB,YAAaC,GAAO,GAChD3B,EAAOoB,GAAIM,iBAAiB,WAAYE,GAAQ,GAKhD3C,EAAS4C,GAAG,uBAAwBC,EAASC,MAC7C9C,EAAS4C,GAAG,uBAAwBD,EAAQG,OAI7CtB,EAAMT,EAAOoB,GAIbV,EAAWH,EAAKyB,wBAChBC,EAAOvB,EAASuB,KAChBC,EAAMxB,EAASwB,IAGfzB,EAAI0B,UAAUC,IAAI,QAGlBC,QAAU5B,EAAIuB,wBAGdvB,EAAI6B,MAAML,KAAOA,EAAOI,QAAQvB,MAAQ,EAAI,KAC5CL,EAAI6B,MAAMJ,IAAMA,EAAM,KAInBnB,EAAYJ,EAAU,MACxBI,EAAYJ,EAAU,IACtBY,YAAYe,MAAMvB,UAAYA,EAAY,MAIxCsB,QAAQzB,OAASsB,GAAOvB,EAAU,IACpCF,EAAI6B,MAAMJ,IAAMA,EAAMG,QAAQzB,OAAU,KACxCH,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAInBN,EAAOI,QAAQvB,OAAS,GAC1BL,EAAI6B,MAAML,KAAOA,EAAO,KACxBxB,EAAI0B,UAAUC,IAAI,SAElB3B,EAAI0B,UAAUI,OAAO,QAInBN,EAAOI,QAAQvB,MAAQ,GAAKD,GAE9BJ,EAAI6B,MAAML,KAAOA,EAAO,IAAM,KAE9BI,QAAU5B,EAAIuB,wBACdvB,EAAI6B,MAAML,KAAOA,EAAOI,QAAQvB,MAAQ,KAErCuB,QAAQzB,OAASsB,GAAOvB,EAAU,IACpCF,EAAI6B,MAAMJ,IAAMA,EAAMG,QAAQzB,OAAU,KACxCH,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAGtB9B,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAMvB,QAASZ,KACR3B,EAAOoB,GAAIe,UAAUC,IAAI,MAG1B,QAASR,KACR5B,EAAOoB,GAAIe,UAAUI,OAAO,MAG7B,QAAST,KACRU,WAAW,WACVxC,EAAOoB,GAAIe,UAAUI,OAAO,SAC1B,KAxIJ,GACCE,GACArB,EACAH,EAGAgB,EACAC,EACAlB,EARG0B,EAAWnC,EAAKoC,aAAajD,EAU9BgD,IAAY/C,IAEf8C,EAAOlC,EAAKoC,aAAa,QACzBvB,EAAKqB,EAAKG,QAAQ,IAAK,IACvB3B,EAAKhC,EAASkB,OAAOC,SAASyC,eAAezB,GAG7Cb,EAAKmB,iBAAiB,YAAalB,GAAS,GAC5CD,EAAKmB,iBAAiB,WAAYI,GAAS,MA4HzC9C,GAAUA,KC5JfJ,OAAOC,MAAMC,SAAS,wBAAwBgE,OAAS,SAAS9D,EAAUC,GAGtE,GAAoE,KAAjEA,EAAS8D,eAAeC,mBAAmBC,QAAQ,UAAkB,CAGpEhE,EAASkB,OAAO+C,OAAOC,cAAcC,eAAiBpE,CAGtD,IAAIqE,GAAIjD,SAASiB,cAAc,SAC/BgC,GAAE1D,KAAO,wBACT0D,EAAEC,UAAY,6ZAMdrE,EAASsE,IAAI9B,KAAKD,YAAY6B,GAE9BzE,OAAOiB,KAAK2D,UAAU,gFAAiF,KAAMvE,EAASsE,IAAIlD,UAGvHrB,IAAUA,KCtBrBJ,OAAOC,MAAMC,SAAS,wBAAwB2E,YAAc,SAASzE,EAAUC,GAC7E,GAAIyE,GAASzE,EAASE,SAASC,iBAAiB,OAC/CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKiE,GACnC/C,EAAU1B,EAAS2B,MAGpB,OAAqC,cAAlC3B,EAAS0E,eAAeC,WAC1B5E,MAIDK,EAAMiB,QAAQ,SAASC,GAEtB,GAAIsD,GAAO,WACV,GAKCC,GALGpD,EAAWH,EAAKyB,wBACnB+B,EAAarD,EAASE,OACtBsB,EAAMxB,EAASwB,IACf8B,EAAUzD,EAAKoC,aAAa,eAC5B/B,EAASoD,GAAWD,EAEpBE,EAAWC,OAAOC,iBAAiB5D,EAAM,IAAI0D,SAASG,MAAM,mBAAmB,IAC/EC,EAAaJ,EAAWA,EAAW,EAAI,CAExCtD,GAAU1B,EAASE,SAASmF,aACnB,EAANpC,IAASA,EAAM,GAEftB,EAASsB,GAAOvB,GAETA,EAAQ,EAAduB,GAEF4B,EAAYnD,EAAUuB,EAAMmC,EAC5B9D,EAAK+B,MAAMvB,UAAY+C,EAAY,KACnCvD,EAAK+B,MAAMxB,MAAO,SAEfF,EAASD,IACXJ,EAAK+B,MAAMvB,UAAYJ,EAAU,KACjCJ,EAAK+B,MAAMxB,MAAO,OAClBJ,EAAWH,EAAKyB,wBAChBpB,EAASF,EAASE,QAEnBL,EAAK+B,MAAMiC,QAAU,QACrBhE,EAAK+B,MAA+B,wBAAI,SACxC/B,EAAK+B,MAAmB,YAAI,UAI7B/B,EAAKe,aAAa,cAAewC,KAGjCvD,EAAK+B,MAAMkC,eAAe,cAC1BjE,EAAK+B,MAAMkC,eAAe,gBAIxBC,EAAW,WAEdxF,EAASyF,IAAI,mBAAoBb,GACjC5E,EAASyF,IAAI,yBAA0B3C,MAGxCxB,GAAKmB,iBAAiB,OAAQmC,GAAM,GAEpC5E,EAAS4C,GAAG,mBAAoBgC,GAEhC5E,EAAS4C,GAAG,yBAA0B4C,GAEtCZ,WAIE7E,GAAUA,OCtEfJ,OAAOC,MAAMC,SAAS,wBAAwB6F,cAAgB,SAAS3F,EAAUC,GAO/E,GAAI2F,GAAQ3F,EAASE,SAASC,iBAAiB,kBAC7CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKmF,EAErCvF,GAAMiB,QAAQ,SAASC,GAWtB,QAASsD,KACR/C,EAAQ+D,EACRjE,EAASkE,EAENhE,EAAQiE,QAAQC,WAClBC,EAAQF,QAAQC,SAAWlE,EAE3BA,EAAQiE,QAAQC,SAChBpE,GAAkBqE,GAGnB/B,EAAOpC,MAAQA,EACfoC,EAAOtC,OAASA,EAtBjB,GAOCqE,GAPGC,EAAM3E,EAAKoC,aAAa,OAC3BO,EAAS9C,SAASiB,cAAc,UAChCwD,EAAgBtE,EAAKoC,aAAa,SAClCmC,EAAiBvE,EAAKoC,aAAa,UACnCwC,EAAS5E,EAAK6E,WACdtE,EAAQ+D,EACRjE,EAASkE,CAoBVjB,KAKA5E,EAASoG,YAAY,mBAAoB,2BAA4BxB,GAErEX,EAAOgC,IAAMA,EAGbC,EAAOG,aAAapC,EAAQ3C,KAQ1BvB,GAAUA"} -------------------------------------------------------------------------------- /public/js/hooks/extensions/highlight.js: -------------------------------------------------------------------------------- 1 | EPUBJS.Hooks.register("beforeChapterDisplay").highlight = function(callback, renderer){ 2 | 3 | // EPUBJS.core.addScript("js/libs/jquery.highlight.js", null, renderer.doc.head); 4 | 5 | var s = document.createElement("style"); 6 | s.innerHTML =".highlight { background: yellow; font-weight: normal; }"; 7 | 8 | renderer.render.document.head.appendChild(s); 9 | 10 | if(callback) callback(); 11 | 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/js/libs/screenfull.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * screenfull 3 | * v2.0.0 - 2014-12-22 4 | * (c) Sindre Sorhus; MIT License 5 | */ 6 | (function () { 7 | 'use strict'; 8 | 9 | var isCommonjs = typeof module !== 'undefined' && module.exports; 10 | var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element; 11 | 12 | var fn = (function () { 13 | var val; 14 | var valLength; 15 | 16 | var fnMap = [ 17 | [ 18 | 'requestFullscreen', 19 | 'exitFullscreen', 20 | 'fullscreenElement', 21 | 'fullscreenEnabled', 22 | 'fullscreenchange', 23 | 'fullscreenerror' 24 | ], 25 | // new WebKit 26 | [ 27 | 'webkitRequestFullscreen', 28 | 'webkitExitFullscreen', 29 | 'webkitFullscreenElement', 30 | 'webkitFullscreenEnabled', 31 | 'webkitfullscreenchange', 32 | 'webkitfullscreenerror' 33 | 34 | ], 35 | // old WebKit (Safari 5.1) 36 | [ 37 | 'webkitRequestFullScreen', 38 | 'webkitCancelFullScreen', 39 | 'webkitCurrentFullScreenElement', 40 | 'webkitCancelFullScreen', 41 | 'webkitfullscreenchange', 42 | 'webkitfullscreenerror' 43 | 44 | ], 45 | [ 46 | 'mozRequestFullScreen', 47 | 'mozCancelFullScreen', 48 | 'mozFullScreenElement', 49 | 'mozFullScreenEnabled', 50 | 'mozfullscreenchange', 51 | 'mozfullscreenerror' 52 | ], 53 | [ 54 | 'msRequestFullscreen', 55 | 'msExitFullscreen', 56 | 'msFullscreenElement', 57 | 'msFullscreenEnabled', 58 | 'MSFullscreenChange', 59 | 'MSFullscreenError' 60 | ] 61 | ]; 62 | 63 | var i = 0; 64 | var l = fnMap.length; 65 | var ret = {}; 66 | 67 | for (; i < l; i++) { 68 | val = fnMap[i]; 69 | if (val && val[1] in document) { 70 | for (i = 0, valLength = val.length; i < valLength; i++) { 71 | ret[fnMap[0][i]] = val[i]; 72 | } 73 | return ret; 74 | } 75 | } 76 | 77 | return false; 78 | })(); 79 | 80 | var screenfull = { 81 | request: function (elem) { 82 | var request = fn.requestFullscreen; 83 | 84 | elem = elem || document.documentElement; 85 | 86 | // Work around Safari 5.1 bug: reports support for 87 | // keyboard in fullscreen even though it doesn't. 88 | // Browser sniffing, since the alternative with 89 | // setTimeout is even worse. 90 | if (/5\.1[\.\d]* Safari/.test(navigator.userAgent)) { 91 | elem[request](); 92 | } else { 93 | elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT); 94 | } 95 | }, 96 | exit: function () { 97 | document[fn.exitFullscreen](); 98 | }, 99 | toggle: function (elem) { 100 | if (this.isFullscreen) { 101 | this.exit(); 102 | } else { 103 | this.request(elem); 104 | } 105 | }, 106 | raw: fn 107 | }; 108 | 109 | if (!fn) { 110 | if (isCommonjs) { 111 | module.exports = false; 112 | } else { 113 | window.screenfull = false; 114 | } 115 | 116 | return; 117 | } 118 | 119 | Object.defineProperties(screenfull, { 120 | isFullscreen: { 121 | get: function () { 122 | return !!document[fn.fullscreenElement]; 123 | } 124 | }, 125 | element: { 126 | enumerable: true, 127 | get: function () { 128 | return document[fn.fullscreenElement]; 129 | } 130 | }, 131 | enabled: { 132 | enumerable: true, 133 | get: function () { 134 | // Coerce to boolean in case of old WebKit 135 | return !!document[fn.fullscreenEnabled]; 136 | } 137 | } 138 | }); 139 | 140 | if (isCommonjs) { 141 | module.exports = screenfull; 142 | } else { 143 | window.screenfull = screenfull; 144 | } 145 | })(); 146 | -------------------------------------------------------------------------------- /public/js/libs/screenfull.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * screenfull 3 | * v1.1.0 - 2013-09-06 4 | * https://github.com/sindresorhus/screenfull.js 5 | * (c) Sindre Sorhus; MIT License 6 | */ 7 | !function(a,b){"use strict";var c="undefined"!=typeof Element&&"ALLOW_KEYBOARD_INPUT"in Element,d=function(){for(var a,c,d=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenchange","MSFullscreenerror"]],e=0,f=d.length,g={};f>e;e++)if(a=d[e],a&&a[1]in b){for(e=0,c=a.length;c>e;e++)g[d[0][e]]=a[e];return g}return!1}(),e={request:function(a){var e=d.requestFullscreen;a=a||b.documentElement,/5\.1[\.\d]* Safari/.test(navigator.userAgent)?a[e]():a[e](c&&Element.ALLOW_KEYBOARD_INPUT)},exit:function(){b[d.exitFullscreen]()},toggle:function(a){this.isFullscreen?this.exit():this.request(a)},onchange:function(){},onerror:function(){},raw:d};return d?(Object.defineProperties(e,{isFullscreen:{get:function(){return!!b[d.fullscreenElement]}},element:{enumerable:!0,get:function(){return b[d.fullscreenElement]}},enabled:{enumerable:!0,get:function(){return!!b[d.fullscreenEnabled]}}}),b.addEventListener(d.fullscreenchange,function(a){e.onchange.call(e,a)}),b.addEventListener(d.fullscreenerror,function(a){e.onerror.call(e,a)}),a.screenfull=e,void 0):(a.screenfull=!1,void 0)}(window,document); -------------------------------------------------------------------------------- /public/js/plugins/hypothesis.js: -------------------------------------------------------------------------------- 1 | // Hypothesis Customized embedding 2 | // This hypothesis config function returns a new constructor which modifies 3 | // annotator for a better integration. Below we create our own EpubAnnotationSidebar 4 | // Constructor, customizing the show and hide function to take acount for the reader UI. 5 | 6 | window.hypothesisConfig = function() { 7 | var Annotator = window.Annotator; 8 | var $main = $("#main"); 9 | 10 | function EpubAnnotationSidebar(elem, options) { 11 | options = { 12 | server: true, 13 | origin: true, 14 | showHighlights: true, 15 | Toolbar: {container: '#annotation-controls'} 16 | } 17 | 18 | Annotator.Host.call(this, elem, options); 19 | } 20 | 21 | EpubAnnotationSidebar.prototype = Object.create(Annotator.Host.prototype); 22 | 23 | EpubAnnotationSidebar.prototype.show = function() { 24 | this.frame.css({ 25 | 'margin-left': (-1 * this.frame.width()) + "px" 26 | }); 27 | this.frame.removeClass('annotator-collapsed'); 28 | if (!$main.hasClass('single')) { 29 | $main.addClass("single"); 30 | this.toolbar.find('[name=sidebar-toggle]').removeClass('h-icon-chevron-left').addClass('h-icon-chevron-right'); 31 | this.setVisibleHighlights(true); 32 | } 33 | }; 34 | 35 | EpubAnnotationSidebar.prototype.hide = function() { 36 | this.frame.css({ 37 | 'margin-left': '' 38 | }); 39 | this.frame.addClass('annotator-collapsed'); 40 | if ($main.hasClass('single')) { 41 | $main.removeClass("single"); 42 | this.toolbar.find('[name=sidebar-toggle]').removeClass('h-icon-chevron-right').addClass('h-icon-chevron-left'); 43 | this.setVisibleHighlights(false); 44 | } 45 | }; 46 | 47 | return { 48 | constructor: EpubAnnotationSidebar, 49 | } 50 | }; 51 | 52 | // This is the Epub.js plugin. Annotations are updated on location change. 53 | EPUBJS.reader.plugins.HypothesisController = function (Book) { 54 | var reader = this; 55 | var $main = $("#main"); 56 | 57 | var updateAnnotations = function () { 58 | var annotator = Book.renderer.render.window.annotator; 59 | if (annotator && annotator.constructor.$) { 60 | var annotations = getVisibleAnnotations(annotator.constructor.$); 61 | annotator.showAnnotations(annotations) 62 | } 63 | }; 64 | 65 | var getVisibleAnnotations = function ($) { 66 | var width = Book.renderer.render.iframe.clientWidth; 67 | return $('.annotator-hl').map(function() { 68 | var $this = $(this), 69 | left = this.getBoundingClientRect().left; 70 | 71 | if (left >= 0 && left <= width) { 72 | return $this.data('annotation'); 73 | } 74 | }).get(); 75 | }; 76 | 77 | Book.on("renderer:locationChanged", updateAnnotations); 78 | 79 | return {} 80 | }; 81 | -------------------------------------------------------------------------------- /public/js/plugins/search.js: -------------------------------------------------------------------------------- 1 | EPUBJS.reader.search = {}; 2 | 3 | // Search Server -- https://github.com/futurepress/epubjs-search 4 | EPUBJS.reader.search.SERVER = "https://pacific-cliffs-3579.herokuapp.com"; 5 | 6 | EPUBJS.reader.search.request = function(q, callback) { 7 | var fetch = $.ajax({ 8 | dataType: "json", 9 | url: EPUBJS.reader.search.SERVER + "/search?q=" + encodeURIComponent(q) 10 | }); 11 | 12 | fetch.fail(function(err) { 13 | console.error(err); 14 | }); 15 | 16 | fetch.done(function(results) { 17 | callback(results); 18 | }); 19 | }; 20 | 21 | EPUBJS.reader.plugins.SearchController = function(Book) { 22 | var reader = this; 23 | 24 | var $searchBox = $("#searchBox"), 25 | $searchResults = $("#searchResults"), 26 | $searchView = $("#searchView"), 27 | iframeDoc; 28 | 29 | var searchShown = false; 30 | 31 | var onShow = function() { 32 | query(); 33 | searchShown = true; 34 | $searchView.addClass("shown"); 35 | }; 36 | 37 | var onHide = function() { 38 | searchShown = false; 39 | $searchView.removeClass("shown"); 40 | }; 41 | 42 | var query = function() { 43 | var q = $searchBox.val(); 44 | 45 | if(q == '') { 46 | return; 47 | } 48 | 49 | $searchResults.empty(); 50 | $searchResults.append("
  • Searching...

  • "); 51 | 52 | 53 | 54 | EPUBJS.reader.search.request(q, function(data) { 55 | var results = data.results; 56 | 57 | $searchResults.empty(); 58 | 59 | if(iframeDoc) { 60 | $(iframeDoc).find('body').unhighlight(); 61 | } 62 | 63 | if(results.length == 0) { 64 | $searchResults.append("
  • No Results Found

  • "); 65 | return; 66 | } 67 | 68 | iframeDoc = $("#viewer iframe")[0].contentDocument; 69 | $(iframeDoc).find('body').highlight(q, { element: 'span' }); 70 | 71 | results.forEach(function(result) { 72 | var $li = $("
  • "); 73 | var $item = $(""+result.title+"

    "+result.highlight+"

    "); 74 | 75 | $item.on("click", function(e) { 76 | var $this = $(this), 77 | cfi = $this.data("cfi"); 78 | 79 | e.preventDefault(); 80 | 81 | Book.gotoCfi(cfi+"/1:0"); 82 | 83 | Book.on("renderer:chapterDisplayed", function() { 84 | iframeDoc = $("#viewer iframe")[0].contentDocument; 85 | $(iframeDoc).find('body').highlight(q, { element: 'span' }); 86 | }) 87 | 88 | 89 | 90 | }); 91 | $li.append($item); 92 | $searchResults.append($li); 93 | }); 94 | 95 | }); 96 | 97 | }; 98 | 99 | $searchBox.on("search", function(e) { 100 | var q = $searchBox.val(); 101 | 102 | //-- SearchBox is empty or cleared 103 | if(q == '') { 104 | $searchResults.empty(); 105 | if(reader.SidebarController.getActivePanel() == "Search") { 106 | reader.SidebarController.changePanelTo("Toc"); 107 | } 108 | 109 | $(iframeDoc).find('body').unhighlight(); 110 | iframeDoc = false; 111 | return; 112 | } 113 | 114 | reader.SidebarController.changePanelTo("Search"); 115 | 116 | e.preventDefault(); 117 | }); 118 | 119 | 120 | 121 | return { 122 | "show" : onShow, 123 | "hide" : onHide 124 | }; 125 | }; 126 | -------------------------------------------------------------------------------- /public/js/reader.min.js: -------------------------------------------------------------------------------- 1 | EPUBJS.reader={},EPUBJS.reader.plugins={},function(a,b){var c=(a.ePubReader||{},a.ePubReader=function(a,b){return new EPUBJS.Reader(a,b)});"function"==typeof define&&define.amd?define(function(){return Reader}):"undefined"!=typeof module&&module.exports&&(module.exports=c)}(window,jQuery),EPUBJS.Reader=function(a,b){var c,d,e,f=this,g=$("#viewer"),h=window.location.search;this.settings=EPUBJS.core.defaults(b||{},{bookPath:a,restore:!1,reload:!1,bookmarks:void 0,annotations:void 0,contained:void 0,bookKey:void 0,styles:void 0,sidebarReflow:!1,generatePagination:!1,history:!0}),h&&(e=h.slice(1).split("&"),e.forEach(function(a){var b=a.split("="),c=b[0],d=b[1]||"";f.settings[c]=decodeURIComponent(d)})),this.setBookKey(this.settings.bookPath),this.settings.restore&&this.isSaved()&&this.applySavedSettings(),this.settings.styles=this.settings.styles||{fontSize:"100%"},this.book=c=new EPUBJS.Book(this.settings),this.settings.previousLocationCfi&&c.gotoCfi(this.settings.previousLocationCfi),this.offline=!1,this.sidebarOpen=!1,this.settings.bookmarks||(this.settings.bookmarks=[]),this.settings.annotations||(this.settings.annotations=[]),this.settings.generatePagination&&c.generatePagination(g.width(),g.height()),c.renderTo("viewer"),f.ReaderController=EPUBJS.reader.ReaderController.call(f,c),f.SettingsController=EPUBJS.reader.SettingsController.call(f,c),f.ControlsController=EPUBJS.reader.ControlsController.call(f,c),f.SidebarController=EPUBJS.reader.SidebarController.call(f,c),f.BookmarksController=EPUBJS.reader.BookmarksController.call(f,c),f.NotesController=EPUBJS.reader.NotesController.call(f,c);for(d in EPUBJS.reader.plugins)EPUBJS.reader.plugins.hasOwnProperty(d)&&(f[d]=EPUBJS.reader.plugins[d].call(f,c));return c.ready.all.then(function(){f.ReaderController.hideLoader()}),c.getMetadata().then(function(a){f.MetaController=EPUBJS.reader.MetaController.call(f,a)}),c.getToc().then(function(a){f.TocController=EPUBJS.reader.TocController.call(f,a)}),window.addEventListener("beforeunload",this.unload.bind(this),!1),window.addEventListener("hashchange",this.hashChanged.bind(this),!1),document.addEventListener("keydown",this.adjustFontSize.bind(this),!1),c.on("renderer:keydown",this.adjustFontSize.bind(this)),c.on("renderer:keydown",f.ReaderController.arrowKeys.bind(this)),c.on("renderer:selected",this.selectedRange.bind(this)),this},EPUBJS.Reader.prototype.adjustFontSize=function(a){var b,c=2,d=187,e=189,f=48,g=a.ctrlKey||a.metaKey;this.settings.styles&&(this.settings.styles.fontSize||(this.settings.styles.fontSize="100%"),b=parseInt(this.settings.styles.fontSize.slice(0,-1)),g&&a.keyCode==d&&(a.preventDefault(),this.book.setStyle("fontSize",b+c+"%")),g&&a.keyCode==e&&(a.preventDefault(),this.book.setStyle("fontSize",b-c+"%")),g&&a.keyCode==f&&(a.preventDefault(),this.book.setStyle("fontSize","100%")))},EPUBJS.Reader.prototype.addBookmark=function(a){var b=this.isBookmarked(a);b>-1||(this.settings.bookmarks.push(a),this.trigger("reader:bookmarked",a))},EPUBJS.Reader.prototype.removeBookmark=function(a){var b=this.isBookmarked(a);b!==-1&&(this.settings.bookmarks.splice(b,1),this.trigger("reader:unbookmarked",b))},EPUBJS.Reader.prototype.isBookmarked=function(a){var b=this.settings.bookmarks;return b.indexOf(a)},EPUBJS.Reader.prototype.clearBookmarks=function(){this.settings.bookmarks=[]},EPUBJS.Reader.prototype.addNote=function(a){this.settings.annotations.push(a)},EPUBJS.Reader.prototype.removeNote=function(a){var b=this.settings.annotations.indexOf(a);b!==-1&&delete this.settings.annotations[b]},EPUBJS.Reader.prototype.clearNotes=function(){this.settings.annotations=[]},EPUBJS.Reader.prototype.setBookKey=function(a){return this.settings.bookKey||(this.settings.bookKey="epubjsreader:"+EPUBJS.VERSION+":"+window.location.host+":"+a),this.settings.bookKey},EPUBJS.Reader.prototype.isSaved=function(a){var b;return!!localStorage&&(b=localStorage.getItem(this.settings.bookKey),null!==b)},EPUBJS.Reader.prototype.removeSavedSettings=function(){return!!localStorage&&void localStorage.removeItem(this.settings.bookKey)},EPUBJS.Reader.prototype.applySavedSettings=function(){var a;if(!localStorage)return!1;try{a=JSON.parse(localStorage.getItem(this.settings.bookKey))}catch(a){return!1}return!!a&&(a.styles&&(this.settings.styles=EPUBJS.core.defaults(this.settings.styles||{},a.styles)),this.settings=EPUBJS.core.defaults(this.settings,a),!0)},EPUBJS.Reader.prototype.saveSettings=function(){return this.book&&(this.settings.previousLocationCfi=this.book.getCurrentLocationCfi()),!!localStorage&&void localStorage.setItem(this.settings.bookKey,JSON.stringify(this.settings))},EPUBJS.Reader.prototype.unload=function(){this.settings.restore&&localStorage&&this.saveSettings()},EPUBJS.Reader.prototype.hashChanged=function(){var a=window.location.hash.slice(1);this.book.goto(a)},EPUBJS.Reader.prototype.selectedRange=function(a){var b=new EPUBJS.EpubCFI,c=b.generateCfiFromRangeAnchor(a,this.book.renderer.currentChapter.cfiBase),d="#"+c;this.settings.history&&window.location.hash!=d&&(history.pushState({},"",d),this.currentLocationCfi=c)},RSVP.EventTarget.mixin(EPUBJS.Reader.prototype),EPUBJS.reader.BookmarksController=function(){var a=this.book,b=$("#bookmarksView"),c=b.find("#bookmarks"),d=document.createDocumentFragment(),e=function(){b.show()},f=function(){b.hide()},g=0,h=function(b){var c=document.createElement("li"),d=document.createElement("a");return c.id="bookmark-"+g,c.classList.add("list_item"),d.textContent=b,d.href=b,d.classList.add("bookmark_link"),d.addEventListener("click",function(b){var c=this.getAttribute("href");a.gotoCfi(c),b.preventDefault()},!1),c.appendChild(d),g++,c};return this.settings.bookmarks.forEach(function(a){var b=h(a);d.appendChild(b)}),c.append(d),this.on("reader:bookmarked",function(a){var b=h(a);c.append(b)}),this.on("reader:unbookmarked",function(a){var b=$("#bookmark-"+a);b.remove()}),{show:e,hide:f}},EPUBJS.reader.ControlsController=function(a){var b=this,c=($("#store"),$("#fullscreen")),d=($("#fullscreenicon"),$("#cancelfullscreenicon"),$("#slider")),e=($("#main"),$("#sidebar"),$("#setting")),f=$("#bookmark"),g=function(){b.offline=!1},h=function(){b.offline=!0},i=!1;return a.on("book:online",g),a.on("book:offline",h),d.on("click",function(){b.sidebarOpen?(b.SidebarController.hide(),d.addClass("icon-menu"),d.removeClass("icon-right")):(b.SidebarController.show(),d.addClass("icon-right"),d.removeClass("icon-menu"))}),"undefined"!=typeof screenfull&&(c.on("click",function(){screenfull.toggle($("#container")[0])}),screenfull.raw&&document.addEventListener(screenfull.raw.fullscreenchange,function(){i=screenfull.isFullscreen,i?c.addClass("icon-resize-small").removeClass("icon-resize-full"):c.addClass("icon-resize-full").removeClass("icon-resize-small")})),e.on("click",function(){b.SettingsController.show()}),f.on("click",function(){var a=b.book.getCurrentLocationCfi(),c=b.isBookmarked(a);c===-1?(b.addBookmark(a),f.addClass("icon-bookmark").removeClass("icon-bookmark-empty")):(b.removeBookmark(a),f.removeClass("icon-bookmark").addClass("icon-bookmark-empty"))}),a.on("renderer:locationChanged",function(a){var c="#"+a,d=b.isBookmarked(a);d===-1?f.removeClass("icon-bookmark").addClass("icon-bookmark-empty"):f.addClass("icon-bookmark").removeClass("icon-bookmark-empty"),b.currentLocationCfi=a,b.settings.history&&window.location.hash!=c&&history.pushState({},"",c)}),a.on("book:pageChanged",function(a){}),{}},EPUBJS.reader.MetaController=function(a){var b=a.bookTitle,c=a.creator,d=$("#book-title"),e=$("#chapter-title"),f=$("#title-seperator");document.title=b+" – "+c,d.html(b),e.html(c),f.show()},EPUBJS.reader.NotesController=function(){var a=this.book,b=this,c=$("#notesView"),d=$("#notes"),e=$("#note-text"),f=$("#note-anchor"),g=b.settings.annotations,h=a.renderer,i=[],j=new EPUBJS.EpubCFI,k=function(){c.show()},l=function(){c.hide()},m=function(c){var d,g,h,i,k,l=a.renderer.doc;if(l.caretPositionFromPoint?(d=l.caretPositionFromPoint(c.clientX,c.clientY),g=d.offsetNode,h=d.offset):l.caretRangeFromPoint&&(d=l.caretRangeFromPoint(c.clientX,c.clientY),g=d.startContainer,h=d.startOffset),3!==g.nodeType)for(var p=0;pm/2.5&&(o=m/2.5,pop_content.style.maxHeight=o+"px"),popRect.height+l>=m-25?(b.style.top=l-popRect.height+"px",b.classList.add("above")):b.classList.remove("above"),k-popRect.width<=0?(b.style.left=k+"px",b.classList.add("left")):b.classList.remove("left"),k+popRect.width/2>=n?(b.style.left=k-300+"px",popRect=b.getBoundingClientRect(),b.style.left=k-popRect.width+"px",popRect.height+l>=m-25?(b.style.top=l-popRect.height+"px",b.classList.add("above")):b.classList.remove("above"),b.classList.add("right")):b.classList.remove("right")},f=function(){i[d].classList.add("on")},g=function(){i[d].classList.remove("on")},j=function(){setTimeout(function(){i[d].classList.remove("show")},100)},l=function(){b.ReaderController.slideOut(),k()};a.addEventListener("mouseover",e,!1),a.addEventListener("mouseout",j,!1),a.addEventListener("click",l,!1)};return f.on("click",function(b){f.text("Cancel"),e.prop("disabled","true"),a.on("renderer:click",m)}),g.forEach(function(a){n(a)}),h.registerHook("beforeChapterDisplay",function(a,b){var c=b.currentChapter;g.forEach(function(a){var b=j.parse(a.anchor);if(b.spinePos===c.spinePos)try{o(a)}catch(b){console.log("anchoring failed",a.anchor)}}),a()},!0),{show:k,hide:l}},EPUBJS.reader.ReaderController=function(a){var b=$("#main"),c=$("#divider"),d=$("#loader"),e=$("#next"),f=$("#prev"),g=this,a=this.book,h=function(){var c=a.getCurrentLocationCfi();g.settings.sidebarReflow?(b.removeClass("single"),b.one("transitionend",function(){a.gotoCfi(c)})):b.removeClass("closed")},i=function(){var c=a.getCurrentLocationCfi();g.settings.sidebarReflow?(b.addClass("single"),b.one("transitionend",function(){a.gotoCfi(c)})):b.addClass("closed")},j=function(){d.show(),m()},k=function(){d.hide()},l=function(){c.addClass("show")},m=function(){c.removeClass("show")},n=!1,o=function(b){37==b.keyCode&&("rtl"===a.metadata.direction?a.nextPage():a.prevPage(),f.addClass("active"),n=!0,setTimeout(function(){n=!1,f.removeClass("active")},100),b.preventDefault()),39==b.keyCode&&("rtl"===a.metadata.direction?a.prevPage():a.nextPage(),e.addClass("active"),n=!0,setTimeout(function(){n=!1,e.removeClass("active")},100),b.preventDefault())};return document.addEventListener("keydown",o,!1),e.on("click",function(b){"rtl"===a.metadata.direction?a.prevPage():a.nextPage(),b.preventDefault()}),f.on("click",function(b){"rtl"===a.metadata.direction?a.nextPage():a.prevPage(),b.preventDefault()}),a.on("renderer:spreads",function(a){a?l():m()}),{slideOut:i,slideIn:h,showLoader:j,hideLoader:k,showDivider:l,hideDivider:m,arrowKeys:o}},EPUBJS.reader.SettingsController=function(){var a=(this.book,this),b=$("#settings-modal"),c=$(".overlay"),d=function(){b.addClass("md-show")},e=function(){b.removeClass("md-show")},f=$("#sidebarReflow");return f.on("click",function(){a.settings.sidebarReflow=!a.settings.sidebarReflow}),b.find(".closer").on("click",function(){e()}),c.on("click",function(){e()}),{show:d,hide:e}},EPUBJS.reader.SidebarController=function(a){var b=this,c=$("#sidebar"),d=$("#panels"),e="Toc",f=function(a){var c=a+"Controller";e!=a&&"undefined"!=typeof b[c]&&(b[e+"Controller"].hide(),b[c].show(),e=a,d.find(".active").removeClass("active"),d.find("#show-"+a).addClass("active"))},g=function(){return e},h=function(){b.sidebarOpen=!0,b.ReaderController.slideOut(),c.addClass("open")},i=function(){b.sidebarOpen=!1,b.ReaderController.slideIn(),c.removeClass("open")};return d.find(".show_view").on("click",function(a){var b=$(this).data("view");f(b),a.preventDefault()}),{show:h,hide:i,getActivePanel:g,changePanelTo:f}},EPUBJS.reader.TocController=function(a){var b=this.book,c=$("#tocView"),d=document.createDocumentFragment(),e=!1,f=function(a,b){var c=document.createElement("ul");return b||(b=1),a.forEach(function(a){var d=document.createElement("li"),e=document.createElement("a");toggle=document.createElement("a");var g;d.id="toc-"+a.id,d.classList.add("list_item"),e.textContent=a.label,e.href=a.href,e.classList.add("toc_link"),d.appendChild(e),a.subitems.length>0&&(b++,g=f(a.subitems,b),toggle.classList.add("toc_toggle"),d.insertBefore(toggle,e),d.appendChild(g)),c.appendChild(d)}),c},g=function(){c.show()},h=function(){c.hide()},i=function(a){var b=a.id,d=c.find("#toc-"+b),f=c.find(".currentChapter");c.find(".openChapter");d.length&&(d!=f&&d.has(e).length>0&&f.removeClass("currentChapter"),d.addClass("currentChapter"),d.parents("li").addClass("openChapter"))};b.on("renderer:chapterDisplayed",i);var j=f(a);return d.appendChild(j),c.append(d),c.find(".toc_link").on("click",function(a){var d=this.getAttribute("href");a.preventDefault(),b.goto(d),c.find(".currentChapter").addClass("openChapter").removeClass("currentChapter"),$(this).parent("li").addClass("currentChapter")}),c.find(".toc_toggle").on("click",function(a){var b=$(this).parent("li"),c=b.hasClass("openChapter");a.preventDefault(),c?b.removeClass("openChapter"):b.addClass("openChapter")}),{show:g,hide:h}}; -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/ebooks_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EbooksControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/ebooks.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | originalfile: MyString 5 | title: MyString 6 | titles: MyString 7 | languages: MyString 8 | contributors: MyString 9 | coverages: MyString 10 | creators: MyString 11 | dates: MyString 12 | descriptions: MyString 13 | formats: MyString 14 | publishers: MyString 15 | relations: MyString 16 | rights: MyString 17 | sources: MyString 18 | subjects: MyString 19 | types: MyString 20 | uniqueid: MyString 21 | epubver: MyString 22 | 23 | two: 24 | originalfile: MyString 25 | title: MyString 26 | titles: MyString 27 | languages: MyString 28 | contributors: MyString 29 | coverages: MyString 30 | creators: MyString 31 | dates: MyString 32 | descriptions: MyString 33 | formats: MyString 34 | publishers: MyString 35 | relations: MyString 36 | rights: MyString 37 | sources: MyString 38 | subjects: MyString 39 | types: MyString 40 | uniqueid: MyString 41 | epubver: MyString 42 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/test/models/.keep -------------------------------------------------------------------------------- /test/models/ebook_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EbookTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/tmp/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tschak909/readingcloud/0b9c614e4e76b7cb03fed1364eea047fb1a7d7c4/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/dropzone.css.erb: -------------------------------------------------------------------------------- 1 | /* The MIT License */ 2 | .dropzone, 3 | .dropzone *, 4 | .dropzone-previews, 5 | .dropzone-previews * { 6 | -webkit-box-sizing: border-box; 7 | -moz-box-sizing: border-box; 8 | box-sizing: border-box; 9 | } 10 | .dropzone { 11 | position: relative; 12 | border: 1px solid rgba(0,0,0,0.08); 13 | background: rgba(0,0,0,0.02); 14 | padding: 1em; 15 | } 16 | .dropzone.dz-clickable { 17 | cursor: pointer; 18 | } 19 | .dropzone.dz-clickable .dz-message, 20 | .dropzone.dz-clickable .dz-message span { 21 | cursor: pointer; 22 | } 23 | .dropzone.dz-clickable * { 24 | cursor: default; 25 | } 26 | .dropzone .dz-message { 27 | opacity: 1; 28 | -ms-filter: none; 29 | filter: none; 30 | } 31 | .dropzone.dz-drag-hover { 32 | border-color: rgba(0,0,0,0.15); 33 | background: rgba(0,0,0,0.04); 34 | } 35 | .dropzone.dz-started .dz-message { 36 | display: none; 37 | } 38 | .dropzone .dz-preview, 39 | .dropzone-previews .dz-preview { 40 | background: rgba(255,255,255,0.8); 41 | position: relative; 42 | display: inline-block; 43 | margin: 17px; 44 | vertical-align: top; 45 | border: 1px solid #acacac; 46 | padding: 6px 6px 6px 6px; 47 | } 48 | .dropzone .dz-preview.dz-file-preview [data-dz-thumbnail], 49 | .dropzone-previews .dz-preview.dz-file-preview [data-dz-thumbnail] { 50 | display: none; 51 | } 52 | .dropzone .dz-preview .dz-details, 53 | .dropzone-previews .dz-preview .dz-details { 54 | width: 100px; 55 | height: 100px; 56 | position: relative; 57 | background: #ebebeb; 58 | padding: 5px; 59 | margin-bottom: 22px; 60 | } 61 | .dropzone .dz-preview .dz-details .dz-filename, 62 | .dropzone-previews .dz-preview .dz-details .dz-filename { 63 | overflow: hidden; 64 | height: 100%; 65 | } 66 | .dropzone .dz-preview .dz-details img, 67 | .dropzone-previews .dz-preview .dz-details img { 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | width: 100px; 72 | height: 100px; 73 | } 74 | .dropzone .dz-preview .dz-details .dz-size, 75 | .dropzone-previews .dz-preview .dz-details .dz-size { 76 | position: absolute; 77 | bottom: -28px; 78 | left: 3px; 79 | height: 28px; 80 | line-height: 28px; 81 | } 82 | .dropzone .dz-preview.dz-error .dz-error-mark, 83 | .dropzone-previews .dz-preview.dz-error .dz-error-mark { 84 | display: block; 85 | } 86 | .dropzone .dz-preview.dz-success .dz-success-mark, 87 | .dropzone-previews .dz-preview.dz-success .dz-success-mark { 88 | display: block; 89 | } 90 | .dropzone .dz-preview:hover .dz-details img, 91 | .dropzone-previews .dz-preview:hover .dz-details img { 92 | display: none; 93 | } 94 | .dropzone .dz-preview .dz-success-mark, 95 | .dropzone-previews .dz-preview .dz-success-mark, 96 | .dropzone .dz-preview .dz-error-mark, 97 | .dropzone-previews .dz-preview .dz-error-mark { 98 | display: none; 99 | position: absolute; 100 | width: 40px; 101 | height: 40px; 102 | font-size: 30px; 103 | text-align: center; 104 | right: -10px; 105 | top: -10px; 106 | } 107 | .dropzone .dz-preview .dz-success-mark, 108 | .dropzone-previews .dz-preview .dz-success-mark { 109 | color: #8cc657; 110 | } 111 | .dropzone .dz-preview .dz-error-mark, 112 | .dropzone-previews .dz-preview .dz-error-mark { 113 | color: #ee162d; 114 | } 115 | .dropzone .dz-preview .dz-progress, 116 | .dropzone-previews .dz-preview .dz-progress { 117 | position: absolute; 118 | top: 100px; 119 | left: 6px; 120 | right: 6px; 121 | height: 6px; 122 | background: #d7d7d7; 123 | display: none; 124 | } 125 | .dropzone .dz-preview .dz-progress .dz-upload, 126 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 127 | display: block; 128 | position: absolute; 129 | top: 0; 130 | bottom: 0; 131 | left: 0; 132 | width: 0%; 133 | background-color: #8cc657; 134 | } 135 | .dropzone .dz-preview.dz-processing .dz-progress, 136 | .dropzone-previews .dz-preview.dz-processing .dz-progress { 137 | display: block; 138 | } 139 | .dropzone .dz-preview .dz-error-message, 140 | .dropzone-previews .dz-preview .dz-error-message { 141 | display: none; 142 | position: absolute; 143 | top: -5px; 144 | left: -20px; 145 | background: rgba(245,245,245,0.8); 146 | padding: 8px 10px; 147 | color: #800; 148 | min-width: 140px; 149 | max-width: 500px; 150 | z-index: 500; 151 | } 152 | .dropzone .dz-preview:hover.dz-error .dz-error-message, 153 | .dropzone-previews .dz-preview:hover.dz-error .dz-error-message { 154 | display: block; 155 | } 156 | .dropzone { 157 | border: 1px solid rgba(0,0,0,0.03); 158 | min-height: 360px; 159 | -webkit-border-radius: 3px; 160 | border-radius: 3px; 161 | background: rgba(0,0,0,0.03); 162 | padding: 23px; 163 | } 164 | .dropzone .dz-default.dz-message { 165 | opacity: 1; 166 | -ms-filter: none; 167 | filter: none; 168 | -webkit-transition: opacity 0.3s ease-in-out; 169 | -moz-transition: opacity 0.3s ease-in-out; 170 | -o-transition: opacity 0.3s ease-in-out; 171 | -ms-transition: opacity 0.3s ease-in-out; 172 | transition: opacity 0.3s ease-in-out; 173 | background-image: url("<%= asset_path('spritemap.png') %>"); 174 | background-repeat: no-repeat; 175 | background-position: 0 0; 176 | position: absolute; 177 | width: 428px; 178 | height: 123px; 179 | margin-left: -214px; 180 | margin-top: -61.5px; 181 | top: 50%; 182 | left: 50%; 183 | } 184 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 185 | .dropzone .dz-default.dz-message { 186 | background-image: url("<%= asset_path('spritemap@2x.png') %>"); 187 | -webkit-background-size: 428px 406px; 188 | -moz-background-size: 428px 406px; 189 | background-size: 428px 406px; 190 | } 191 | } 192 | .dropzone .dz-default.dz-message span { 193 | display: none; 194 | } 195 | .dropzone.dz-square .dz-default.dz-message { 196 | background-position: 0 -123px; 197 | width: 268px; 198 | margin-left: -134px; 199 | height: 174px; 200 | margin-top: -87px; 201 | } 202 | .dropzone.dz-drag-hover .dz-message { 203 | opacity: 0.15; 204 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=15)"; 205 | filter: alpha(opacity=15); 206 | } 207 | .dropzone.dz-started .dz-message { 208 | display: block; 209 | opacity: 0; 210 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 211 | filter: alpha(opacity=0); 212 | } 213 | .dropzone .dz-preview, 214 | .dropzone-previews .dz-preview { 215 | -webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.16); 216 | box-shadow: 1px 1px 4px rgba(0,0,0,0.16); 217 | font-size: 14px; 218 | } 219 | .dropzone .dz-preview.dz-image-preview:hover .dz-details img, 220 | .dropzone-previews .dz-preview.dz-image-preview:hover .dz-details img { 221 | display: block; 222 | opacity: 0.1; 223 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; 224 | filter: alpha(opacity=10); 225 | } 226 | .dropzone .dz-preview.dz-success .dz-success-mark, 227 | .dropzone-previews .dz-preview.dz-success .dz-success-mark { 228 | opacity: 1; 229 | -ms-filter: none; 230 | filter: none; 231 | } 232 | .dropzone .dz-preview.dz-error .dz-error-mark, 233 | .dropzone-previews .dz-preview.dz-error .dz-error-mark { 234 | opacity: 1; 235 | -ms-filter: none; 236 | filter: none; 237 | } 238 | .dropzone .dz-preview.dz-error .dz-progress .dz-upload, 239 | .dropzone-previews .dz-preview.dz-error .dz-progress .dz-upload { 240 | background: #ee1e2d; 241 | } 242 | .dropzone .dz-preview .dz-error-mark, 243 | .dropzone-previews .dz-preview .dz-error-mark, 244 | .dropzone .dz-preview .dz-success-mark, 245 | .dropzone-previews .dz-preview .dz-success-mark { 246 | display: block; 247 | opacity: 0; 248 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 249 | filter: alpha(opacity=0); 250 | -webkit-transition: opacity 0.4s ease-in-out; 251 | -moz-transition: opacity 0.4s ease-in-out; 252 | -o-transition: opacity 0.4s ease-in-out; 253 | -ms-transition: opacity 0.4s ease-in-out; 254 | transition: opacity 0.4s ease-in-out; 255 | background-image: url("<%= asset_path('spritemap.png') %>"); 256 | background-repeat: no-repeat; 257 | } 258 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 259 | .dropzone .dz-preview .dz-error-mark, 260 | .dropzone-previews .dz-preview .dz-error-mark, 261 | .dropzone .dz-preview .dz-success-mark, 262 | .dropzone-previews .dz-preview .dz-success-mark { 263 | background-image: url("<%= asset_path('spritemap@2x.png') %>"); 264 | -webkit-background-size: 428px 406px; 265 | -moz-background-size: 428px 406px; 266 | background-size: 428px 406px; 267 | } 268 | } 269 | .dropzone .dz-preview .dz-error-mark span, 270 | .dropzone-previews .dz-preview .dz-error-mark span, 271 | .dropzone .dz-preview .dz-success-mark span, 272 | .dropzone-previews .dz-preview .dz-success-mark span { 273 | display: none; 274 | } 275 | .dropzone .dz-preview .dz-error-mark, 276 | .dropzone-previews .dz-preview .dz-error-mark { 277 | background-position: -268px -123px; 278 | } 279 | .dropzone .dz-preview .dz-success-mark, 280 | .dropzone-previews .dz-preview .dz-success-mark { 281 | background-position: -268px -163px; 282 | } 283 | .dropzone .dz-preview .dz-progress .dz-upload, 284 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 285 | -webkit-animation: loading 0.4s linear infinite; 286 | -moz-animation: loading 0.4s linear infinite; 287 | -o-animation: loading 0.4s linear infinite; 288 | -ms-animation: loading 0.4s linear infinite; 289 | animation: loading 0.4s linear infinite; 290 | -webkit-transition: width 0.3s ease-in-out; 291 | -moz-transition: width 0.3s ease-in-out; 292 | -o-transition: width 0.3s ease-in-out; 293 | -ms-transition: width 0.3s ease-in-out; 294 | transition: width 0.3s ease-in-out; 295 | -webkit-border-radius: 2px; 296 | border-radius: 2px; 297 | position: absolute; 298 | top: 0; 299 | left: 0; 300 | width: 0%; 301 | height: 100%; 302 | background-image: url("<%= asset_path('spritemap.png') %>"); 303 | background-repeat: repeat-x; 304 | background-position: 0px -400px; 305 | } 306 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 307 | .dropzone .dz-preview .dz-progress .dz-upload, 308 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 309 | background-image: url("../images/spritemap@2x.png"); 310 | -webkit-background-size: 428px 406px; 311 | -moz-background-size: 428px 406px; 312 | background-size: 428px 406px; 313 | } 314 | } 315 | .dropzone .dz-preview.dz-success .dz-progress, 316 | .dropzone-previews .dz-preview.dz-success .dz-progress { 317 | display: block; 318 | opacity: 0; 319 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 320 | filter: alpha(opacity=0); 321 | -webkit-transition: opacity 0.4s ease-in-out; 322 | -moz-transition: opacity 0.4s ease-in-out; 323 | -o-transition: opacity 0.4s ease-in-out; 324 | -ms-transition: opacity 0.4s ease-in-out; 325 | transition: opacity 0.4s ease-in-out; 326 | } 327 | .dropzone .dz-preview .dz-error-message, 328 | .dropzone-previews .dz-preview .dz-error-message { 329 | display: block; 330 | opacity: 0; 331 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 332 | filter: alpha(opacity=0); 333 | -webkit-transition: opacity 0.3s ease-in-out; 334 | -moz-transition: opacity 0.3s ease-in-out; 335 | -o-transition: opacity 0.3s ease-in-out; 336 | -ms-transition: opacity 0.3s ease-in-out; 337 | transition: opacity 0.3s ease-in-out; 338 | } 339 | .dropzone .dz-preview:hover.dz-error .dz-error-message, 340 | .dropzone-previews .dz-preview:hover.dz-error .dz-error-message { 341 | opacity: 1; 342 | -ms-filter: none; 343 | filter: none; 344 | } 345 | .dropzone a.dz-remove, 346 | .dropzone-previews a.dz-remove { 347 | background-image: -webkit-linear-gradient(top, #fafafa, #eee); 348 | background-image: -moz-linear-gradient(top, #fafafa, #eee); 349 | background-image: -o-linear-gradient(top, #fafafa, #eee); 350 | background-image: -ms-linear-gradient(top, #fafafa, #eee); 351 | background-image: linear-gradient(to bottom, #fafafa, #eee); 352 | -webkit-border-radius: 2px; 353 | border-radius: 2px; 354 | border: 1px solid #eee; 355 | text-decoration: none; 356 | display: block; 357 | padding: 4px 5px; 358 | text-align: center; 359 | color: #aaa; 360 | margin-top: 26px; 361 | } 362 | .dropzone a.dz-remove:hover, 363 | .dropzone-previews a.dz-remove:hover { 364 | color: #666; 365 | } 366 | @-moz-keyframes loading { 367 | 0% { 368 | background-position: 0 -400px; 369 | } 370 | 371 | 100% { 372 | background-position: -7px -400px; 373 | } 374 | } 375 | @-webkit-keyframes loading { 376 | 0% { 377 | background-position: 0 -400px; 378 | } 379 | 380 | 100% { 381 | background-position: -7px -400px; 382 | } 383 | } 384 | @-o-keyframes loading { 385 | 0% { 386 | background-position: 0 -400px; 387 | } 388 | 389 | 100% { 390 | background-position: -7px -400px; 391 | } 392 | } 393 | @-ms-keyframes loading { 394 | 0% { 395 | background-position: 0 -400px; 396 | } 397 | 398 | 100% { 399 | background-position: -7px -400px; 400 | } 401 | } 402 | @keyframes loading { 403 | 0% { 404 | background-position: 0 -400px; 405 | } 406 | 407 | 100% { 408 | background-position: -7px -400px; 409 | } 410 | } 411 | --------------------------------------------------------------------------------