From 8c9ed4c0839e00d835a1141a0650d7418f5cf09f Mon Sep 17 00:00:00 2001 From: f Date: Mon, 29 Jan 2018 19:19:10 -0300 Subject: [PATCH] ver posts y sitios --- app/controllers/application_controller.rb | 15 ++ app/controllers/posts_controller.rb | 7 + app/controllers/sites_controller.rb | 11 ++ app/models/post.rb | 169 ++++++++++++++++++++++ app/models/site.rb | 76 ++++++++++ app/views/posts/index.haml | 8 + app/views/sites/index.haml | 6 + 7 files changed, 292 insertions(+) create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/models/post.rb create mode 100644 app/models/site.rb create mode 100644 app/views/posts/index.haml diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6c68715a..bf8e25d4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,22 @@ +# Forma de ingreso a Sutty class ApplicationController < ActionController::Base protect_from_forgery with: :exception + # No tenemos índice de sutty, vamos directamente a ver el listado de + # sitios def index redirect_to sites_path end + + private + + # Encontrar un sitio por su ID + # TODO volverlo más natural a rails + def find_site + id = params[:site_id] || params[:id] + + current_user.sites.find do |s| + s.id == id + end + end end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 00000000..782d5099 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,7 @@ +class PostsController < ApplicationController + before_action :authenticate! + + def index + @site = find_site + end +end diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 3de9b89e..64f8b9b3 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -1,6 +1,17 @@ +# Controlador de sitios class SitesController < ApplicationController before_action :authenticate! + # Ver un listado de sitios def index + @sites = current_user.sites + end + + # No tenemos propiedades de un sitio aún, así que vamos al listado de + # artículos + def show + site = find_site + + redirect_to site_posts_path(site) end end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 00000000..5ab63ca8 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true +require 'jekyll/utils' + +# Esta clase representa un post en un sitio jekyll e incluye métodos +# para modificarlos y crear nuevos +class Post + attr_accessor :content, :front_matter + attr_reader :post, :site, :errors + + REJECT_FROM_DATA = %w[excerpt slug draft date ext].freeze + + # Trabajar con posts. Si estamos creando uno nuevo, el **site** y + # el **front_matter** son necesarios, sino, **site** y **post**. + # XXX chequear que se den las condiciones + def initialize(site:, post:, front_matter: {}) + @site = site + @post = post + @errors = [] + @front_matter = front_matter + + new_post! unless @post + load_data! unless new? + end + + # Si el archivo destino no existe, el post es nuevo + def new? + !File.exist? @post.path + end + + # Guarda los cambios + def save + merge_data_with_front_matter! + clean_content! + + return unless write + return unless detect_file_rename! + + # Vuelve a leer el post para tomar los cambios + @post.read + true + end + alias :save! :save + + def title + get_metadata 'title' + end + + def date + get_metadata 'date' + end + + def tags + get_metadata 'tags' + end + + def categories + get_metadata 'categories' + end + alias :category :categories + + def path + @post.try :path || File.join(@site.path, '_posts', basename_from_front_matter) + end + + def id + get_metadata 'slug' + end + alias :slug :id + alias :to_s :id + + def basename_changed? + @post.basename != basename_from_front_matter + end + + private + + def new_post + opts = { site: @site, collection: @site.posts } + @post = ::Jekyll::Document.new(path, opts) + @site.posts.docs << @post + @site.posts.docs.sort! + + @post + end + + # Obtiene metadatos + def get_metadata(name) + new? ? @front_matter.dig(name) : @post.data[name] + end + + # Cambiar el nombre del archivo si cambió el título o la fecha. + # Como Jekyll no tiene métodos para modificar un Document, lo + # engañamos eliminando la instancia de @post y recargando otra. + def detect_file_rename! + return true unless basename_changed? + + if File.exist? path + @errors << 'El archivo destino ya existe' + return + end + + FileUtils.mv @post.path, path + replace_post! path + end + + def replace_post!(path) + @site.posts.docs.delete @post + + new_post + end + + # Obtiene el nombre del archivo a partir de los datos que le + # pasemos + def basename_from_front_matter + date = @front_matter[:date].strftime('%F') + title = ::Jekyll::Utils.slugify(@front_matter[:title]) + ext = @post.try :ext || 'markdown' + + "#{date}-#{title}.#{ext}" + end + + # Toma los datos del front matter local y los mueve a los datos + # que van a ir al post. Si hay símbolos se convierten a cadenas, + # porque Jekyll trabaja con cadenas. + def merge_data_with_front_matter! + @data.merge! Hash[@front_matter.map { |k, v| [k.to_s, v] }] + end + + # Carga una copia de los datos del post original excluyendo datos + # que no nos interesan + def load_data! + @data ||= @post.data.reject do |key, _| + REJECT_FROM_DATA.include? key + end + end + + # Aplica limpiezas básicas del contenido + def clean_content! + @content = @content.delete("\r") + end + + # Guarda los cambios en el archivo destino + def write + r = File.open(@post.path, File::RDWR | File::CREAT, 0o640) do |f| + # Bloquear el archivo para que no sea accedido por otro + # proceso u otra editora + f.flock(File::LOCK_EX) + + # Empezar por el principio + f.rewind + + # Escribir + f.write(full_content) + + # Eliminar el resto + f.flush + f.truncate(f.pos) + end + + return true if r.zero? + + @errors << 'No se pudo escribir el archivo' + false + end + + def full_content + "#{@data.to_yaml}---\n\n#{@content}" + end +end diff --git a/app/models/site.rb b/app/models/site.rb new file mode 100644 index 00000000..4c6d17fb --- /dev/null +++ b/app/models/site.rb @@ -0,0 +1,76 @@ +# Un sitio es un directorio dentro del directorio de sitios de cada +# Usuaria +class Site + + attr_accessor :jekyll + + def initialize(jekyll:) + @jekyll = jekyll + end + + # Obtener el nombre del sitio + def name + @name ||= @jekyll.config['source'].split('/').last + end + + # El id es el sitio sin puntos, para no confundir al routeador de + # rails + def id + @id ||= name.tr('.', '-') + end + alias :to_s :id + + # Los posts de este sitio + def posts + return @posts if @posts + + @jekyll.read if @jekyll.posts.docs.empty? + + # Los convertimos a una clase intermedia capaz de acceder a sus + # datos y modificarlos + @posts = @jekyll.posts.docs.map do |post| + Post.new(site: @jekyll, post: post) + end + end + + # El directorio donde se almacenan los sitios + def self.site_path + File.join(Rails.root, '_sites') + end + + # El directorio de los sitios de una usuaria + # + # Los sitios se organizan por usuaria, entonces los sitios que + # administra pueden encontrarse directamente en su directorio. + # + # Si comparten gestión con otras usuarias, se hacen links simbólicos + # entre sí. + def self.site_path_for(usuaria) + File.join(Site.site_path, usuaria.username) + end + + # Comprueba que el directorio parezca ser de jekyll + def self.jekyll?(dir) + File.directory?(dir) && File.exist?(File.join(dir, '_config.yml')) + end + + # Obtener todos los directorios de sitios asociados a esta usuaria + def self.all_for(usuaria) + @sites ||= Pathname.new(Site.site_path_for(usuaria)) + .children.map(&:expand_path).map(&:to_s).map do |j| + + next unless Site.jekyll? j + + Dir.chdir(j) do + config = ::Jekyll.configuration('source' => Dir.pwd) + + # No necesitamos cargar plugins en este momento + %w[plugins gems].each do |unneeded| + config[unneeded] = [] if config.key? unneeded + end + + Site.new(jekyll: ::Jekyll::Site.new(config)) + end + end.compact + end +end diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml new file mode 100644 index 00000000..6c0942c5 --- /dev/null +++ b/app/views/posts/index.haml @@ -0,0 +1,8 @@ +%h1= @site.name + +%table.table.table-condensed.table-striped + %tbody + - @site.posts.each do |post| + %tr + %td= link_to post.title, site_post_path(@site, post) + %td= post.date diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index 83d23fb4..95587891 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -1 +1,7 @@ %h1= t('sites.title') + +%table.table.table-striped.table-condensed + %tbody + - @sites.each do |site| + %tr + %td= link_to site.name, site_path(site)