diff --git a/Gemfile b/Gemfile index 868ef54..5bec36a 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,7 @@ gem 'puma', '~> 5.0' gem 'sidekiq' # Other stuff +gem 'gepub' gem 'marcel' # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index 6588638..2cd1098 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,6 +120,9 @@ GEM faraday-net_http (>= 2.0, < 3.2) faraday-net_http (3.1.0) net-http + gepub (1.0.15) + nokogiri (>= 1.8.2, < 2.0) + rubyzip (> 1.1.1, < 2.4) globalid (1.2.1) activesupport (>= 6.1) hashie (5.0.0) @@ -305,6 +308,7 @@ DEPENDENCIES debug elasticsearch-model fancy_searchable! + gepub kaminari marcel model-msearch diff --git a/app/controllers/chapters_controller.rb b/app/controllers/chapters_controller.rb index c7dd82a..077644c 100644 --- a/app/controllers/chapters_controller.rb +++ b/app/controllers/chapters_controller.rb @@ -6,27 +6,6 @@ class ChaptersController < ApplicationController @story = Story.find(params[:story_id]) @chapter = @story.chapters.find_by(number: params[:id]) - @rendered_html = render_story - end - - private - - def render_story - body = @chapter.body - body.lstrip! - body = body.split "\n" - - # This is fucking bad, this gets rid of the redundant title - this should be fixed upstairs, - # in the actual generation of the Markdown. - if body.length >= 2 && body[0] == @chapter.title && !body[1].empty? && body[1][0] == '=' - body = body[2..] - end - - markdown.render body.join("\n") - end - - def markdown - @@markdown ||= - Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true) + @rendered_html = StoryRenderer.render_chapter(@chapter) end end diff --git a/app/lib/ebook/epub_generator.rb b/app/lib/ebook/epub_generator.rb new file mode 100644 index 0000000..e0c1354 --- /dev/null +++ b/app/lib/ebook/epub_generator.rb @@ -0,0 +1,43 @@ +require 'gepub' + +TEMPLATE_DIRECTORY = Rails.root.join('app/lib/ebook') + +class Ebook::EpubGenerator + include ActionView::Helpers::TagHelper + + def initialize(story) + @story = story + end + + def generate + book = GEPUB::Book.new + book.add_title @story.title, title_type: GEPUB::TITLE_TYPE::MAIN, lang: :en, display_seq: 1 + book.add_creator @story.author.name + book.add_item 'styles.css', content: TEMPLATE_DIRECTORY.join('files/styles.css').open + + book.ordered do + book.add_item('CoverPage.html', content: generate_cover_page).landmark(type: 'cover', title: 'Cover Page') + @story.chapters.each do |chapter| + book.add_item("Chapter#{chapter.number}.html", content: generate_chapter(chapter)) + .toc_text("Chapter #{chapter.number} - #{chapter.title}") + .landmark(type: 'bodymatter', title: chapter.title) + end + end + + book + end + + #private + + def render_template(name, context) + Slim::Template.new(TEMPLATE_DIRECTORY.join("templates/#{name}.html.slim")).render(context) + end + + def generate_cover_page + StringIO.new render_template('cover', @story) + end + + def generate_chapter(chapter) + StringIO.new render_template('chapter', chapter) + end +end diff --git a/app/lib/ebook/files/styles.css b/app/lib/ebook/files/styles.css new file mode 100644 index 0000000..5f6835b --- /dev/null +++ b/app/lib/ebook/files/styles.css @@ -0,0 +1,72 @@ +.double { + margin-top: 1em; +} +.indented { + text-indent: 3em; +} +.i { + font-style: italic; +} +.u { + text-decoration: underline; +} +.b { + font-weight: bold; +} +.c { + text-align: center; +} +.s { + text-decoration: line-through; +} +img.emoticon { + margin: 0; + padding: 0; + border: 0; + height: 1em; +} +hr { + margin-top: 12px; +} +blockquote { + padding: 5px 10px; + margin: 10px; + border-left: 5px solid #aaa; + background: #eee; +} +.authors-note, +.preface { + border-top: 3px double #333; + border-bottom: 3px double #333; +} +.afterward { + border-top: 3px double #333; +} +h1 { + font-size: 1.9em; + margin: 0.8em 0; +} +h2 { + font-size: 1.3em; + margin: 0.5em 0; +} +h3 { + font-size: 0.625em; + margin: 0; +} +.authors-note, +.preface, +.afterward { + background: none; + border-left: none; + padding: 4px 0; +} +.authors-note h1, +.authors-note h2, +.preface h1, +.preface h2, +.afterward h1, +.afterward h2 { + margin: 0.5em 0; + font-size: 1.3em; +} diff --git a/app/lib/ebook/templates/chapter.html.slim b/app/lib/ebook/templates/chapter.html.slim new file mode 100644 index 0000000..797415f --- /dev/null +++ b/app/lib/ebook/templates/chapter.html.slim @@ -0,0 +1,10 @@ +doctype xml +html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" + head + meta http-equiv="Content-Type" content="text/html; charset=UTF-8" + link rel="stylesheet" href="styles.css" + title= title + body + h1= title + .calibre2#content + == StoryRenderer.render_chapter(self) \ No newline at end of file diff --git a/app/lib/ebook/templates/cover.html.slim b/app/lib/ebook/templates/cover.html.slim new file mode 100644 index 0000000..8fb36b2 --- /dev/null +++ b/app/lib/ebook/templates/cover.html.slim @@ -0,0 +1,33 @@ +doctype xml +html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" + head + meta http-equiv="Content-Type" content="text/html; charset=UTF-8" + link rel="stylesheet" href="styles.css" + title Cover + body + table style="width: 100%; height: 100%;" + tr + td + h1= title + p + b by #{author.name} + hr + p + | Rating: #{content_rating} + br + | Tags: #{tags.pluck(:name).join(', ')} + br + | Length: #{num_words} + hr + table.desc + tr style="height: 100%;" valign="top" + td + p.double= short_description + == description_html + tr.bottom + td + hr + p + | Published #{date_published} + br + | Last updated #{date_updated} \ No newline at end of file diff --git a/test.epub b/test.epub new file mode 100644 index 0000000..57236bb Binary files /dev/null and b/test.epub differ