5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-06-26 15:06:08 +00:00

editor prehistorico

This commit is contained in:
f 2017-10-05 16:42:32 -03:00
parent 3ac158cc93
commit cbe46babdc
No known key found for this signature in database
GPG key ID: F3FDAB97B5F9F7E7
18 changed files with 322 additions and 47 deletions

View file

@ -11,4 +11,5 @@ gem 'sinatra_warden'
group :development do
gem 'pry'
gem 'rubocop'
gem 'sinatra-contrib'
end

View file

@ -4,6 +4,7 @@ GEM
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
ast (2.3.0)
backports (3.8.0)
coderay (1.1.2)
colorator (1.1.0)
email_address (0.1.3)
@ -36,6 +37,7 @@ GEM
rb-inotify (~> 0.9, >= 0.9.7)
mercenary (0.3.6)
method_source (0.8.2)
multi_json (1.12.2)
mustermann (1.0.1)
netaddr (1.5.1)
parallel (1.12.0)
@ -80,6 +82,13 @@ GEM
rack (~> 2.0)
rack-protection (= 2.0.0)
tilt (~> 2.0)
sinatra-contrib (2.0.0)
backports (>= 2.0)
multi_json
mustermann (~> 1.0)
rack-protection (= 2.0.0)
sinatra (= 2.0.0)
tilt (>= 1.3, < 3)
sinatra_warden (0.3.2)
sinatra (>= 1.0.0)
warden (~> 1.0)
@ -101,6 +110,7 @@ DEPENDENCIES
rubocop
sass
sinatra
sinatra-contrib
sinatra_warden
BUNDLED WITH

View file

@ -25,3 +25,29 @@ archivo `Gemfile`.
```bash
bundle install
```
## Ideas
Sutty actua como una granja de sitios Jekyll. La idea principal es que
una sola instancia de Sutty es capaz de gestionar muchos sitios Jekyll.
### Sin base de datos
Sutty es capaz de obtener toda la información necesaria a partir de la
estructura de directorios.
### Sin autenticación
Al menos sin autenticación propia. Sutty es capaz de funcionar con
mecanismos de autenticación diversos. Actualmente solo funciona con
LDAP. Podría ni tener autenticación!
### Sin administradoras ni administradas
No hay una cuenta de administración que decide a quién corresponde un
sitio o quién puede escribir o no. Las usuarias crean sus propios
sitios e invitan a otras usuarias a co-gestionarlos.
### Múltiples versiones de Jekyll
Debe ser capaz de soportar diferentes versiones de Jekyll.

14
lib/jekyll.rb Normal file
View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
require 'jekyll'
# Monkeypatcheando Jekyll
module Jekyll
# Métodos agregados o modificados a Site
class Site
# Obtener un nombre para el sitio
def name
@name ||= File.basename(dest)
end
end
end

View file

@ -1,11 +1,12 @@
# frozen_string_literal: true
require 'yaml'
require_relative 'sutty/models/jekyll'
require 'jekyll'
require_relative 'jekyll'
require_relative 'sutty/models/post'
# Sutty
module Sutty
# La raíz
def self.root
@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
@ -13,7 +14,7 @@ module Sutty
# La configuración
def self.settings
@settings ||= YAML.load(File.read(Sutty.config_file))
@settings ||= YAML.safe_load(File.read(Sutty.config_file))
end
def self.config_file
@ -24,8 +25,37 @@ module Sutty
@sites_dir ||= File.join(Sutty.root, Sutty.settings['sites_dir'])
end
# Comprueba que el directorio parezca ser de jekyll
def self.jekyll?(dir)
File.directory?(dir) && File.exist?(File.join(dir, '_config.yml'))
end
def self.sites
binding.pry
@sites ||= Jekyll.all
@sites ||= Dir.entries('_sites/').map do |j|
# no queremos . ni .. ni archivos ocultos
next if j.start_with? '.'
j = Sutty.path_from_name(j)
next unless Sutty.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
::Jekyll::Site.new(config)
end
end.compact
end
def self.find(name)
Sutty.sites.select { |s| s.name == name }.first
end
def self.path_from_name(name)
File.realpath(File.join(Sutty.sites_dir, name))
end
end

View file

@ -2,8 +2,10 @@
require 'rack-flash'
require 'sinatra/base'
require 'sinatra/reloader' if ENV['RACK_ENV'] == 'development'
require 'sinatra_warden'
require_relative 'login'
require_relative 'site'
require_relative '../sutty'
module Sutty
@ -11,12 +13,17 @@ module Sutty
class App < Sinatra::Base
use Rack::Flash
use Sutty::Login
use Sutty::Site
register Sinatra::Warden
configure :development do
register Sinatra::Reloader
end
set :root, Sutty.root
before do
authorize! '/login'
authorize! '/login' if ENV['RACK_ENV'] == 'production'
end
get '/' do

View file

@ -1,39 +0,0 @@
# frozen_string_literal: true
module Sutty
# Un sitio jekyll
class Jekyll
# Devuelve todos los sitios dentro de sites_dir como instancias de
# Jekyll
def self.all
Dir.entries(Sutty.sites_dir).map do |j|
# no queremos . ni .. ni archivos ocultos
next if j.start_with? '.'
_j = File.realpath(File.join(Sutty.sites_dir, j))
next unless Jekyll.is_jekyll? _j
Jekyll.new(_j, j)
end.compact
end
# Comprueba que el directorio parezca ser de jekyll
def self.is_jekyll?(dir)
File.directory?(dir) && File.exists?(File.join(dir, '_config.yml'))
end
def initialize(dir, name = nil)
@root = dir
@name = name if name
end
def config
@config ||= YAML.load(File.read(File.join(@root, '_config.yml')))
end
def name
@name ||= File.basename(@root)
end
end
end

69
lib/sutty/models/post.rb Normal file
View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
require 'yaml'
module Sutty
module Models
# Un post
class Post
attr_accessor :content, :front_matter
attr_reader :post, :site
REJECT_FROM_DATA = %w[excerpt slug draft date ext].freeze
def initialize(site, post = nil)
@site = site
@post = post
end
def new?
!@post.is_a? Jekyll::Document
end
def save
front_matter_from_data!
clean_content!
return unless write.zero?
@post.read
true
end
private
def front_matter_from_data!
@front_matter ||= @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
def write
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
end
def full_content
"#{@front_matter.to_yaml}---\n\n#{@content}"
end
end
end
end

64
lib/sutty/post.rb Normal file
View file

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'sinatra/base'
require 'sinatra/namespace'
require 'sinatra/reloader' if ENV['RACK_ENV'] == 'development'
require 'sinatra_warden'
require_relative 'login'
require_relative '../sutty'
module Sutty
# El gestor de posts
class Post < Sinatra::Base
use Sutty::Login
register Sinatra::Warden
register Sinatra::Namespace
set :root, Sutty.root
configure :development do
register Sinatra::Reloader
end
namespace '/sites/:name/posts' do
before do
authorize! '/login' if ENV['RACK_ENV'] == 'production'
@site = Sutty.find(params['name'])
@site.read if @site.posts.docs.empty?
end
get do
haml :'posts/index'
end
namespace '/:basename' do
before do
@post = @site.posts.docs.select do |d|
d.basename == params['basename']
end.first
end
get do
haml :'posts/show'
end
get '/edit' do
haml :'posts/edit'
end
post do
post = Sutty::Models::Post.new(@site, @post)
post.content = params[:post][:content]
if post.save
flash[:success] = 'Artículo guardado con éxito'
redirect to("/sites/#{@site.name}/posts/#{@post.basename}")
else
flash[:error] = 'Hubo un error al guardar el artículo'
redirect back
end
end
end
end
end
end

39
lib/sutty/site.rb Normal file
View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'sinatra/base'
require 'sinatra/namespace'
require 'sinatra/reloader' if ENV['RACK_ENV'] == 'development'
require 'sinatra_warden'
require_relative 'login'
require_relative 'post'
require_relative '../sutty'
module Sutty
# El gestor de sitios
class Site < Sinatra::Base
use Sutty::Login
register Sinatra::Warden
register Sinatra::Namespace
set :root, Sutty.root
set :read, false
configure :development do
register Sinatra::Reloader
end
namespace '/sites/:name' do
before do
authorize! '/login' if ENV['RACK_ENV'] == 'production'
@site = Sutty.find(params['name'])
@site.read if @site.posts.docs.empty?
end
get do
haml :'sites/show'
end
use Sutty::Post
end
end
end

View file

@ -5,4 +5,8 @@
%tbody
- Sutty.sites.each do |site|
%tr
%td= site.name
%td
%a{href: '/sites/' + site.name}
= site.name
%td
%a{href: 'http://' + site.name} visitar

View file

@ -6,7 +6,7 @@
%link{rel: 'stylesheet', type: 'text/css', href: '/assets/css/bootstrap.min.css'}
%link{rel: 'stylesheet', type: 'text/css', href: '/stylesheets/sutty.css'}
%body{class: @has_cover ? 'background-cover' : ''}
.container
.container-fluid
= yield
%footer.footer
%p{style: @has_cover ? 'color: white' : ''}

6
views/posts/_form.haml Normal file
View file

@ -0,0 +1,6 @@
%form{method: 'post', action: "/sites/#{@site.name}/posts/#{@post.basename}"}
.form-group
%button.btn.btn-success{type: 'submit'} Guardar
.form-group
%textarea.form-control{name: 'post[content]', id: 'contents', autofocus: true}
= @post.content

9
views/posts/edit.haml Normal file
View file

@ -0,0 +1,9 @@
.row
.col-2
= haml :'sites/_nav'
.col-10
%h1
Editando
= @post.data['title']
= haml :'posts/_form'

13
views/posts/index.haml Normal file
View file

@ -0,0 +1,13 @@
.row
.col-2
= haml :'sites/_nav'
.col-10
%h1= @site.name
%table.table.table-striped
%tbody
- @site.posts.docs.each do |post|
%tr
%td
%a{href: "/sites/#{@site.name}/posts/#{post.basename}"}= post.data['title']
%td= post.date.strftime('%F')

12
views/posts/show.haml Normal file
View file

@ -0,0 +1,12 @@
.row
.col-2
= haml :'sites/_nav'
.col-10
%a.btn.btn-info{href: "/sites/#{@site.name}/posts/#{@post.basename}/edit"}
Editar
%h1= @post.data['title']
.content
:markdown
#{@post.content}

5
views/sites/_nav.haml Normal file
View file

@ -0,0 +1,5 @@
%ul.nav.flex-column
%li.nav-item
%a.nav-link{href: "/sites/#{@site.name}/posts"}
Posts
%span.badge.badge-secondary= @site.posts.docs.count

5
views/sites/show.haml Normal file
View file

@ -0,0 +1,5 @@
.row
.col-2
= haml :'sites/_nav'
.col-10
%h1= @site.name