diff --git a/Gemfile b/Gemfile index b3609d7..ab95612 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ gem 'cli-ui', '~> 1.3' gem 'thor', '~> 1.0', '>= 1.0.1' # Markdown processing -gem 'redcarpet', '~> 3.5' +gem 'commonmarker' # HTTP Client gem 'faraday' diff --git a/Gemfile.lock b/Gemfile.lock index e9c09d4..fc3738d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,6 +41,8 @@ GEM benchmark (0.1.0) cli-ui (1.3.0) coderay (1.1.3) + commonmarker (0.21.0) + ruby-enum (~> 0.5) concurrent-ruby (1.1.6) debase (0.2.4.1) debase-ruby_core_source (>= 0.10.2) @@ -115,24 +117,25 @@ GEM rbnacl (7.1.1) ffi rchardet (1.8.0) - redcarpet (3.5.0) regexp_parser (1.7.1) reverse_markdown (2.0.0) nokogiri rexml (3.2.4) - rubocop (0.89.0) + rubocop (0.89.1) parallel (~> 1.10) parser (>= 2.7.1.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.7) rexml - rubocop-ast (>= 0.1.0, < 1.0) + rubocop-ast (>= 0.3.0, < 1.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (0.3.0) parser (>= 2.7.1.4) ruby-debug-ide (0.7.2) rake (>= 0.8.1) + ruby-enum (0.8.0) + i18n ruby-progressbar (1.10.1) ruby2_keywords (0.0.2) sassc (2.4.0) @@ -147,7 +150,7 @@ GEM rack-protection (= 2.0.8.1) tilt (~> 2.0) slack-notifier (2.3.2) - solargraph (0.39.13) + solargraph (0.39.15) backport (~> 1.1) benchmark bundler (>= 1.17.2) @@ -178,6 +181,7 @@ DEPENDENCIES activesupport (~> 6.0) aws-sdk-s3 (~> 1.64) cli-ui (~> 1.3) + commonmarker concurrent-ruby (~> 1.1) debase (~> 0.2.4.1) faraday @@ -188,7 +192,6 @@ DEPENDENCIES octokit! rack-livereload rbnacl - redcarpet (~> 3.5) rubocop (~> 0.81) ruby-debug-ide (~> 0.7.2) sassc diff --git a/app/commands/robles_cli.rb b/app/commands/robles_cli.rb index 6eafb74..2e40147 100644 --- a/app/commands/robles_cli.rb +++ b/app/commands/robles_cli.rb @@ -16,6 +16,7 @@ def self.exit_on_failure? def render book = runner.render(publish_file: options['publish_file'], local: options['local']) p book.sections.first.chapters.last + p book.contributors.to_json end desc 'serve', 'starts local preview server' diff --git a/app/lib/image_provider/markdown_image_extractor.rb b/app/lib/image_provider/markdown_image_extractor.rb index 400d819..e60b90e 100644 --- a/app/lib/image_provider/markdown_image_extractor.rb +++ b/app/lib/image_provider/markdown_image_extractor.rb @@ -3,25 +3,6 @@ module ImageProvider # Takes a markdown file, and returns all the images URLs class MarkdownImageExtractor - # Process markdown, and extract a list of images - class MarkdownRenderer < Redcarpet::Render::Base - attr_reader :images - - def initialize - super - @images = [] - end - - def reset - @images = [] - end - - def image(link, _title, _alt_text) - images << link - nil - end - end - include Util::PathExtraction def self.images_from(file) @@ -29,21 +10,23 @@ def self.images_from(file) end def images - md_renderer.reset - renderer.render(markdown) - md_renderer.images.map { |path| { relative_path: cleanpath(path), absolute_path: cleanpath(apply_path(path)) } } + [].tap do |images| + doc.walk do |node| + images << image_record(node.url) if node.type == :image + end + end end - def cleanpath(path) - Pathname.new(path).cleanpath.to_s + def image_record(url) + { relative_path: cleanpath(url), absolute_path: cleanpath(apply_path(url)) } end - def renderer - @renderer ||= Redcarpet::Markdown.new(md_renderer) + def cleanpath(path) + Pathname.new(path).cleanpath.to_s end - def md_renderer - @md_renderer ||= MarkdownRenderer.new + def doc + @doc ||= CommonMarker.render_doc(markdown) end def markdown diff --git a/app/lib/linting/file_existence_checker.rb b/app/lib/linting/file_existence_checker.rb index 74ae0b9..141df99 100644 --- a/app/lib/linting/file_existence_checker.rb +++ b/app/lib/linting/file_existence_checker.rb @@ -5,6 +5,7 @@ module Linting module FileExistenceChecker # This method shouldn't depend on the underlying filesystem def file_exists?(path, case_insensitive: false) + path = path.to_s directory_contents = Dir[Pathname.new(path).dirname + '*'] return directory_contents.include?(path) unless case_insensitive diff --git a/app/lib/linting/linter.rb b/app/lib/linting/linter.rb index 2dd0bf6..bbecd86 100644 --- a/app/lib/linting/linter.rb +++ b/app/lib/linting/linter.rb @@ -43,6 +43,14 @@ def lint_with_ui(options:, show_ui: true) # rubocop:disable Metrics/MethodLength with_spinner(title: 'Validating image references', show: show_ui) do annotations.concat(Linting::ImageLinter.new(book: book).lint) end + + if file_exists?(vend_file) + with_spinner(title: 'Validating {{bold:vend.yaml}}', show: show_ui) do + annotations.concat(Linting::VendLinter.new(file: vend_file).lint) + end + else + puts CLI::UI.fmt('{{x}} Unable to find {{bold:vend.yaml}}--skipping validation.') + end end def with_spinner(title:, show: true) @@ -87,6 +95,10 @@ def check_publish_file_exists false end + def vend_file + Pathname.new(file).dirname + 'vend.yaml' + end + def book # rubocop:disable Metrics/MethodLength @book ||= begin parser = Parser::Publish.new(file: file) diff --git a/app/lib/linting/metadata/publish_attributes.rb b/app/lib/linting/metadata/publish_attributes.rb index 492e95c..52854a5 100644 --- a/app/lib/linting/metadata/publish_attributes.rb +++ b/app/lib/linting/metadata/publish_attributes.rb @@ -4,8 +4,9 @@ module Linting module Metadata # Check for the required attributes in the publish.yaml file class PublishAttributes - REQUIRED_ATTRIBUTES = %i[sku edition title description released_at authors segments materials_url - cover_image version_description difficulty platform language editor].freeze + REQUIRED_ATTRIBUTES = %i[sku edition title description_md released_at authors segments materials_url + cover_image version_description difficulty platform language editor + short_description].freeze attr_reader :file, :attributes diff --git a/app/lib/linting/vend_linter.rb b/app/lib/linting/vend_linter.rb new file mode 100644 index 0000000..21ba6d0 --- /dev/null +++ b/app/lib/linting/vend_linter.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module Linting + # Lints vend.yaml + class VendLinter # rubocop:disable Metrics/ClassLength + VALID_PRICE_BANDS = %w(free 2020_full_book 2020_short_book 2020_deprecated_book).freeze + attr_reader :file, :vend_file + + def initialize(file:) + @file = file + @annotations = [] + end + + def lint + load_file + return @annotations unless @annotations.empty? + + check_price_band + check_total_percentage + return @annotations unless @annotations.empty? + + check_for_razeware_user + @annotations + end + + def check_price_band # rubocop:disable Metrics/MethodLength + if vend_file[:price_band].blank? + @annotations.push( + Annotation.new( + absolute_path: file, + annotation_level: 'warning', + start_line: 0, + end_line: 0, + title: 'Missing price_band attribute.', + message: 'The price_band attribute allows this book to be sold individually and should be included for all books.' + ) + ) + elsif !VALID_PRICE_BANDS.include?(vend_file[:price_band]) + @annotations.push( + Annotation.new( + absolute_path: file, + annotation_level: 'failure', + start_line: 0, + end_line: 0, + title: 'Invalid price_band attribute.', + message: "price_band must be in (#{VALID_PRICE_BANDS.join(', ')})" + ) + ) + end + end + + def check_total_percentage # rubocop:disable Metrics/MethodLength + if vend_file[:contributors].blank? + @annotations.push( + Annotation.new( + absolute_path: file, + annotation_level: 'warning', + start_line: 0, + end_line: 0, + title: 'Missing contributors attribute.', + message: 'The contributors attribute should be included for all books.' + ) + ) + return + end + + total_contributor_percentage = vend_file[:contributors].sum(&:percentage) + return if total_contributor_percentage == 100 + + @annotations.push( + Annotation.new( + absolute_path: file, + annotation_level: 'failure', + start_line: 0, + end_line: 0, + message: "The sum of all contributor percentages should be 100. It is currently #{total_contributor_percentage}.", + title: 'Incorrect contribution sum.' + ) + ) + end + + def check_for_razeware_user # rubocop:disable Metrics/MethodLength + return unless vend_file[:contributors].any? { |contributor| contributor.username == 'razeware' } + + @annotations.push( + Annotation.new( + locate_razeware_username.merge( + absolute_path: file, + annotation_level: 'warning', + message: "The publisher's contribution should be attributed to the _razeware user, not the razeware user.", + title: 'Probable incorrect user.' + ) + ) + ) + end + + private + + def load_file # rubocop:disable Metrics/MethodLength + @vend_file ||= begin + parser = Parser::Vend.new(file: file) + parser.parse + end + rescue Parser::Error => e + line_number = (e.message.match(/at line (\d+)/)&.captures&.first&.to_i || 0) + 1 + @annotations.push( + Annotation.new( + absolute_path: e.file, + annotation_level: 'failure', + start_line: line_number, + end_line: line_number, + message: e.message, + title: 'Unable to parse contributors.' + ) + ) + end + + def locate_razeware_username + IO.foreach(file).with_index do |line, line_number| + next unless line[/username:\s*razeware/] + + start_column = line.index('razeware') + return { + start_line: line_number + 1, + end_line: line_number + 1, + start_column: start_column + 1, + end_column: start_column + 'razeware'.length + 1 + } + end + end + end +end diff --git a/app/lib/parser/publish.rb b/app/lib/parser/publish.rb index a0cf7c5..5afb96d 100644 --- a/app/lib/parser/publish.rb +++ b/app/lib/parser/publish.rb @@ -4,17 +4,20 @@ module Parser # Parses a publish.yaml file, and returns a Book model object class Publish include Util::PathExtraction + include Linting::FileExistenceChecker VALID_BOOK_ATTRIBUTES = %i[sku edition title description released_at materials_url cover_image gallery_image twitter_card_image trailer_video_url version_description professional difficulty platform language editor domains categories who_is_this_for_md - covered_concepts_md hide_chapter_numbers in_flux].freeze + covered_concepts_md hide_chapter_numbers in_flux forum_url + pages short_description recommended_skus].freeze attr_reader :book def parse load_book_segments + load_vend_file apply_additonal_metadata update_authors_on_chapters book @@ -25,6 +28,10 @@ def load_book_segments @book = Parser::BookSegments.new(file: segment_file).parse end + def load_vend_file + book.assign_attributes(vend_file) + end + def apply_additonal_metadata book.assign_attributes(additional_attributes) book.root_path = root_directory @@ -44,12 +51,20 @@ def publish_file @publish_file ||= Psych.load_file(file).deep_symbolize_keys end + def vend_file_path + Pathname.new(file).dirname + 'vend.yaml' + end + def authors @authors = publish_file[:authors].map do |author| Author.new(author) end end + def vend_file + @vend_file ||= file_exists?(vend_file_path) ? Parser::Vend.new(file: vend_file_path).parse : {} + end + def additional_attributes @additional_attributes ||= publish_file.slice(*VALID_BOOK_ATTRIBUTES) .assert_valid_keys(*VALID_BOOK_ATTRIBUTES) diff --git a/app/lib/parser/vend.rb b/app/lib/parser/vend.rb new file mode 100644 index 0000000..6ff03d6 --- /dev/null +++ b/app/lib/parser/vend.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Parser + # Parses a vend.yaml file, and returns a hash of contributors & price_band + class Vend + include Util::PathExtraction + + def parse + { + contributors: contributors, + price_band: vend_file[:price_band] + } + end + + def contributors + return [] unless vend_file[:contributors].present? + + vend_file[:contributors].map do |contributor_attributes| + Contributor.new(contributor_attributes) + end + end + + private + + def vend_file + @vend_file ||= Psych.load_file(file).deep_symbolize_keys + end + end +end diff --git a/app/lib/renderer/markdown_file_renderer.rb b/app/lib/renderer/markdown_file_renderer.rb index 464bd19..9fe4e04 100644 --- a/app/lib/renderer/markdown_file_renderer.rb +++ b/app/lib/renderer/markdown_file_renderer.rb @@ -4,6 +4,7 @@ module Renderer # Read a file and render the markdown class MarkdownFileRenderer include Util::Logging + include Parser::FrontmatterMetadataFinder attr_reader :path attr_reader :image_provider @@ -15,27 +16,43 @@ def initialize(path:, image_provider: nil) def render logger.debug 'MarkdownFileRenderer::render' - redcarpet.render(raw_content) + remove_h1(doc) + rw_renderer.render(doc) + end + + def rw_renderer + @rw_renderer ||= Renderer::RWMarkdownRenderer.new( + options: %i[TABLE_PREFER_STYLE_ATTRIBUTES], + extensions: %i[table strikethrough autolink], + image_provider: image_provider, + root_path: root_directory + ) end def raw_content @raw_content ||= File.read(path) end - def redcarpet_renderer - @redcarpet_renderer ||= RWMarkdownRenderer.new(with_toc_data: true, - image_provider: image_provider, - root_path: root_directory) + def preproccessed_markdown + @preproccessed_markdown ||= begin + removing_pagesetting_notation = raw_content.gsub(/\$\[=[=sp]=\]/, '') + without_metadata(removing_pagesetting_notation.each_line) + end + end + + def doc + @doc ||= CommonMarker.render_doc( + preproccessed_markdown, + %i[SMART STRIKETHROUGH_DOUBLE_TILDE], + %i[table strikethrough autolink] + ) end - def redcarpet - @redcarpet ||= Redcarpet::Markdown.new(redcarpet_renderer, - fenced_code_blocks: true, - disable_indented_code_blocks: true, - autolink: true, - strikethrough: true, - tables: true, - hightlight: true) + def remove_h1(document) + document.walk do |node| + node.delete if node.type == :header && node.header_level.to_i == 1 + end + document end def root_directory diff --git a/app/lib/renderer/markdown_string_renderer.rb b/app/lib/renderer/markdown_string_renderer.rb index 92ed234..e4e0e26 100644 --- a/app/lib/renderer/markdown_string_renderer.rb +++ b/app/lib/renderer/markdown_string_renderer.rb @@ -13,21 +13,15 @@ def initialize(content:) def render logger.debug 'MarkdownStringRenderer::render' - redcarpet.render(content) - end - - def redcarpet_renderer - @redcarpet_renderer ||= RWMarkdownRenderer.new(with_toc_data: true) - end - - def redcarpet - @redcarpet ||= Redcarpet::Markdown.new(redcarpet_renderer, - fenced_code_blocks: true, - disable_indented_code_blocks: true, - autolink: true, - strikethrough: true, - tables: true, - hightlight: true) + doc = CommonMarker.render_doc( + content, + %i[SMART STRIKETHROUGH_DOUBLE_TILDE], + %i[table strikethrough autolink] + ) + doc.to_html( + %i[TABLE_PREFER_STYLE_ATTRIBUTES], + %i[table strikethrough autolink] + ) end end end diff --git a/app/lib/renderer/rw_markdown_renderer.rb b/app/lib/renderer/rw_markdown_renderer.rb index cbc541b..cf75208 100644 --- a/app/lib/renderer/rw_markdown_renderer.rb +++ b/app/lib/renderer/rw_markdown_renderer.rb @@ -2,45 +2,32 @@ module Renderer # Custom implementation of a markdown renderer for RW books - class RWMarkdownRenderer < Redcarpet::Render::HTML - include Redcarpet::Render::SmartyPants - include Parser::FrontmatterMetadataFinder + class RWMarkdownRenderer < CommonMarker::HtmlRenderer include Renderer::ImageAttributes include Util::Logging attr_reader :root_path - def initialize(attributes = {}) + def initialize(options: :DEFAULT, extensions: [], image_provider:, root_path:) logger.debug 'RWMarkdownRenderer::initialize' - super - @image_provider = attributes[:image_provider] - @root_path = attributes[:root_path] + super(options: options, extensions: extensions) + @image_provider = image_provider + @root_path = root_path end - def header(text, header_level) - return nil if header_level == 1 + def image(node) + return super(node) if image_provider.blank? - "#{text}" - end - - def image(link, title, alt_text) - return %(#{alt_text}) if image_provider.blank? - - %( -
- - #{title} - - -
#{title}
-
- ) - end + title = node.title.present? ? escape_html(node.title) : '' + classes = class_list(node.each.select { |child| child.type == :text }.map { |child| escape_html(child.string_content) }.join(' ')) - def preprocess(full_document) - logger.debug 'RWMarkdownRenderer::preprocess' - removing_pagesetting_notation = full_document.gsub(/\$\[=[=sp]=\]/, '') - without_metadata(removing_pagesetting_notation.each_line) + out('
') + out(' ') + out(' ', title, '') + out(' ') + out(' ') + out('
', title, '
') + out('
') end end end diff --git a/app/models/book.rb b/app/models/book.rb index 681041c..85c6ed1 100644 --- a/app/models/book.rb +++ b/app/models/book.rb @@ -7,16 +7,19 @@ class Book include Concerns::ImageAttachable include Concerns::MarkdownRenderable - attr_accessor :sku, :edition, :title, :description, :released_at, :sections, :git_commit_hash, + attr_accessor :sku, :edition, :title, :description_md, :released_at, :sections, :git_commit_hash, :materials_url, :cover_image, :gallery_image, :twitter_card_image, :trailer_video_url, :version_description, :professional, :difficulty, :platform, :language, :editor, :domains, :categories, :who_is_this_for_md, - :covered_concepts_md, :root_path, :hide_chapter_numbers, :in_flux + :covered_concepts_md, :root_path, :hide_chapter_numbers, :in_flux, + :forum_url, :pages, :short_description, :recommended_skus, :contributors, + :price_band attr_image :cover_image_url, source: :cover_image attr_image :gallery_image_url, source: :gallery_image attr_image :twitter_card_image_url, source: :twitter_card_image attr_markdown :who_is_this_for, source: :who_is_this_for_md, file: false attr_markdown :covered_concepts, source: :covered_concepts_md, file: false + attr_markdown :description, source: :description_md, file: false validates :sku, :edition, :title, presence: true validates_inclusion_of :difficulty, in: %w[beginner intermediate advanced] @@ -24,6 +27,7 @@ class Book def initialize(attributes = {}) super @sections ||= [] + @contributors ||= [] @hide_chapter_numbers ||= false @in_flux ||= false end @@ -35,6 +39,7 @@ def attributes twitter_card_image_url: nil, trailer_video_url: nil, version_description: nil, professional: nil, difficulty: nil, platform: nil, language: nil, editor: nil, domains: [], categories: [], who_is_this_for: nil, covered_concepts: nil, hide_chapter_numbers: nil, - in_flux: nil }.stringify_keys + in_flux: nil, forum_url: nil, pages: nil, short_description: nil, recommended_skus: [], + contributors: [], price_band: nil }.stringify_keys end end diff --git a/app/models/contributor.rb b/app/models/contributor.rb new file mode 100644 index 0000000..306f6b9 --- /dev/null +++ b/app/models/contributor.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# For commissions purposes. Username and proportion +class Contributor + include ActiveModel::Model + include ActiveModel::Serializers::JSON + + attr_accessor :username, :percentage + validates :username, :percentage, presence: true + + def proportion + percentage / 100 + end + + # Used for serialisation + def attributes + { username: nil, proportion: nil }.stringify_keys + end +end diff --git a/app/server/robles_server.rb b/app/server/robles_server.rb index eb56186..2c3292d 100644 --- a/app/server/robles_server.rb +++ b/app/server/robles_server.rb @@ -44,6 +44,7 @@ def book @book ||= begin parser = Parser::Publish.new(file: publish_file) book = parser.parse + book.image_attachment_loop { |local_url| servable_image_url(local_url) } book end end @@ -63,6 +64,10 @@ def publish_file '/data/src/publish.yaml' end + def servable_image_url(local_url) + local_url&.gsub(%r{/data/src}, '/assets') + end + def acceptable_image_extension(extension) %w[jpg png gif].include?(extension.downcase) end diff --git a/app/server/views/_index_table_of_contents.html.erb b/app/server/views/_index_table_of_contents.html.erb new file mode 100644 index 0000000..b7ec2fe --- /dev/null +++ b/app/server/views/_index_table_of_contents.html.erb @@ -0,0 +1,19 @@ +
+
+
+ <% book.sections.each do |section| %> +

<%= section.title %>

+ + <% section.chapters.each do |chapter| %> + + <% end %> + <% end %> +
+
+
diff --git a/app/server/views/chapter.html.erb b/app/server/views/chapter.html.erb index 277d3d1..0db5f25 100644 --- a/app/server/views/chapter.html.erb +++ b/app/server/views/chapter.html.erb @@ -1,13 +1,21 @@ - +
<%= erb :'_table_of_contents.html', locals: { book: book } %>
-
+

- <%= chapter.number %> + <%= book.hide_chapter_numbers ? '' : chapter.number %> <%= chapter.title %>

diff --git a/app/server/views/index.html.erb b/app/server/views/index.html.erb index 53d478a..a7fc1dc 100644 --- a/app/server/views/index.html.erb +++ b/app/server/views/index.html.erb @@ -1,5 +1,23 @@ -

<%= book.title %>

+
+
+
+
+
+ +
+
+
+

<%= book.title %>

+ + <%= book.version_description %> + -

<%= book.description %>

+

<%= book.short_description %>

+
+
+
+
-<%= erb :'_table_of_contents.html', locals: { book: book } %> +
+ <%= erb :'_index_table_of_contents.html', locals: { book: book } %> +
diff --git a/app/server/views/styles/application.scss b/app/server/views/styles/application.scss index 98c64d0..de48d82 100644 --- a/app/server/views/styles/application.scss +++ b/app/server/views/styles/application.scss @@ -20,8 +20,8 @@ Side Navbar left: 0; width: 270px !important; height: 100%; - background: $dark-black-mine-shaft; - padding: 0 18px 72px; + background-image: linear-gradient(to right bottom, rgb(51, 51, 51), rgb(54, 54, 56), rgb(56, 57, 61), rgb(58, 60, 67), rgb(59, 64, 72)); + padding: 0 18px 92px; overflow-y: scroll; position: fixed; overflow: -moz-scrollbars-none; @@ -141,9 +141,8 @@ Book Specific Sidebar .c-nav-sidebar--book { .c-sidebar-navigator { - padding-bottom: 0; + padding-bottom: 50; position: relative; - padding-bottom: 0; &:before { content: ""; @@ -187,6 +186,31 @@ header#c-global-header { top: 0; } +.l-color-white { + color: $white; +} + +.l-font-underline--force { + text-decoration: underline !important; +} + +.c-book-header__chapter-title { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; +} + +.e-header-margin { + margin: auto; +} + +.e-home-p { + width: 226px; +} + + /* ========================================================================== Book Chapter ========================================================================== */ @@ -688,18 +712,306 @@ Layout Bits padding-left: 270px; } -l-book-chapter { - padding-left: 270px; -} - .l-whitespace { white-space: nowrap; } +.l-font-header { + font-family: $header; +} + +.l-font-bold { + font-weight: 700; +} + +.l-font-18 { + font-size: 1.125em; +} + +.l-color-white { + color: $white; +} + +.l-flex { + display: flex; +} + +.l-font-24{ + font-size: 1.5rem; /* 24/16 */ +} + +.l-font-italic{ + font-style: italic; +} + +.l-margin-3{ + margin-top: 3px; +} + +.l-display-block{ + display: block; +} + /* ========================================================================== Contents Bits ========================================================================== */ .c-written-tutorial { - padding-left: 60px; - padding-right: 60px; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 80px; +} + + +/* ========================================================================== +INDEX PAGE +========================================================================== */ + +/* ========================================================================== +Title and Description +========================================================================== */ +.l-block { + margin: 0 auto; +} + +.l-block--1080 { + max-width: 1080px; +} + +.l-collection-hero { + padding-top: 72px; + padding-bottom: 72px; + padding-left: 30px; + padding-right: 30px; + overflow: hidden; + + .l-collection-hero__wrapper{ + display: grid; + grid-template-columns: 300px 1fr!important; + grid-column-gap: 90px; + align-items: flex-start!important; + } + + h1 { + line-height: 1.25; + } + + .l-collection-hero_artwork { + + figure { + display: block; + + img { + max-width: 100%; + } + } + } + + .l-collection-hero__copy { + flex-wrap: wrap; + + h1, > span { + text-align: left; + } + } + + .l-collection-hero__copy-markdown { + font-size: 1.1875em !important; /* 21/16 */ /* 19/16 */ + } + + p { + margin-top: 55px; + text-align: left; + } +} + + +.l-book-hero { + padding-bottom: 0 !important; +} + +.l-margin-18 { + margin-top: 18px; +} + +.l-font-header { + font-family: $header; + + ul{ + font-family: $header; + } +} + + +/* ========================================================================== +T.O.C. Wrappery +========================================================================== */ + +.l-padding-bottom-120 { + padding-bottom: 120px; +} + +.l-collection-modules__module { + display: grid; + grid-template-columns: 1fr 360px; + grid-column-gap: 120px; + + .l-collection-modules__module-episodes { + + h2{ + margin-top: 50px; + + &:first-child { + margin-top: 0; + } + + } + + } +} + +.l-book-modules { + + .l-collection-modules__module { + grid-template-columns: 300px 1fr !important; + grid-column-gap: 90px !important; + } +} + +.l-padding-60 { + padding-top: 100px; +} + +.l-block-wrapper { + max-width: 1280px; + margin: 0 auto; + padding: 20px 30px; +} + +.l-block--1080 { + max-width: 1080px; + } + + /* ========================================================================== +T.O.C. +========================================================================== */ + + +.l-collection-modules__module-episodes { + + h2 { + margin-top: 20px; + margin-bottom: 20px; + color: $black-mine-shaft; + } +} + + +.c-tutorial-episode { + position: relative; + padding-left: 64px; + margin-bottom: 35px; + margin-top: 15px; + + &:first-child { + margin-top: 0; + } + + &:last-child { + &:before { + display: none; + } + } + + &:before { + content: ""; + position: absolute; + left: 20px; + top: 40px; + width: 6px; + height: calc(100% - 15px); + background: #DFE7F0; + border-radius: 100px; + transition: .75s all; + } + + a { + color: $black-mine-shaft; + text-decoration: none; + + &:hover{ + color: $green-brand; + } + + } + + h4 { + font-size: 1.3125rem; /* 21/16 */ + } + + p { + font-size: 1rem; + margin-top: 15px; + color: $black-mine-shaft; + } + + .c-tutorial-episode__length { + font-family: $header; + font-size: 1.125em; /* 18/16 */ + margin-left: 8px; + position: relative; + top: 3px; + } + + .o-badge { + margin-left: 12px; + position: relative; + top: 1px; + } + + .o-badge-tutorial { + position: absolute; + top: -9px; + left: 0px; + width: 46px; + height: 46px; + font-size: 1.1875rem; /* 19/16 */ + + svg { + display: none; + } + } +} + + +.c-tutorial-episode--completed { + &:before { + background: $green-brand; + } + + [data-tooltip]:before { + left: 70px !important ; + content: attr(data-unmark-complete) !important; + } +} + + +.o-badge-tutorial { + display: flex; + justify-content: center; + align-items: center; + width: 46px; + height: 46px; + background: $black-mine-shaft; + border: 4px solid $white; + border-radius: 12px; + font-family: $header; + font-size: 1.5em; /* 24/16 */ + font-weight: 700; + color: $white; + overflow: hidden; +} + +.l-flex-align-start { + display: flex; + align-items: flex-start; } diff --git a/bin/robles b/bin/robles index 98beb3c..1f39653 100755 --- a/bin/robles +++ b/bin/robles @@ -1,4 +1,4 @@ -#!/usr/local/bin/ruby +#!/usr/local/bin/ruby -W:no-experimental # frozen_string_literal: true APP_PATH = File.expand_path('../config/application', __dir__)