mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 11:26:21 +00:00
editor prehistorico
This commit is contained in:
parent
3ac158cc93
commit
cbe46babdc
18 changed files with 322 additions and 47 deletions
1
Gemfile
1
Gemfile
|
@ -11,4 +11,5 @@ gem 'sinatra_warden'
|
||||||
group :development do
|
group :development do
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
gem 'rubocop'
|
gem 'rubocop'
|
||||||
|
gem 'sinatra-contrib'
|
||||||
end
|
end
|
||||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -4,6 +4,7 @@ GEM
|
||||||
addressable (2.5.2)
|
addressable (2.5.2)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
|
backports (3.8.0)
|
||||||
coderay (1.1.2)
|
coderay (1.1.2)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
email_address (0.1.3)
|
email_address (0.1.3)
|
||||||
|
@ -36,6 +37,7 @@ GEM
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
mercenary (0.3.6)
|
mercenary (0.3.6)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
|
multi_json (1.12.2)
|
||||||
mustermann (1.0.1)
|
mustermann (1.0.1)
|
||||||
netaddr (1.5.1)
|
netaddr (1.5.1)
|
||||||
parallel (1.12.0)
|
parallel (1.12.0)
|
||||||
|
@ -80,6 +82,13 @@ GEM
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-protection (= 2.0.0)
|
rack-protection (= 2.0.0)
|
||||||
tilt (~> 2.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_warden (0.3.2)
|
||||||
sinatra (>= 1.0.0)
|
sinatra (>= 1.0.0)
|
||||||
warden (~> 1.0)
|
warden (~> 1.0)
|
||||||
|
@ -101,6 +110,7 @@ DEPENDENCIES
|
||||||
rubocop
|
rubocop
|
||||||
sass
|
sass
|
||||||
sinatra
|
sinatra
|
||||||
|
sinatra-contrib
|
||||||
sinatra_warden
|
sinatra_warden
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
|
26
README.md
26
README.md
|
@ -25,3 +25,29 @@ archivo `Gemfile`.
|
||||||
```bash
|
```bash
|
||||||
bundle install
|
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
14
lib/jekyll.rb
Normal 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
|
40
lib/sutty.rb
40
lib/sutty.rb
|
@ -1,11 +1,12 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require_relative 'sutty/models/jekyll'
|
require 'jekyll'
|
||||||
|
require_relative 'jekyll'
|
||||||
|
require_relative 'sutty/models/post'
|
||||||
|
|
||||||
# Sutty
|
# Sutty
|
||||||
module Sutty
|
module Sutty
|
||||||
|
|
||||||
# La raíz
|
# La raíz
|
||||||
def self.root
|
def self.root
|
||||||
@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
||||||
|
@ -13,7 +14,7 @@ module Sutty
|
||||||
|
|
||||||
# La configuración
|
# La configuración
|
||||||
def self.settings
|
def self.settings
|
||||||
@settings ||= YAML.load(File.read(Sutty.config_file))
|
@settings ||= YAML.safe_load(File.read(Sutty.config_file))
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.config_file
|
def self.config_file
|
||||||
|
@ -24,8 +25,37 @@ module Sutty
|
||||||
@sites_dir ||= File.join(Sutty.root, Sutty.settings['sites_dir'])
|
@sites_dir ||= File.join(Sutty.root, Sutty.settings['sites_dir'])
|
||||||
end
|
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
|
def self.sites
|
||||||
binding.pry
|
@sites ||= Dir.entries('_sites/').map do |j|
|
||||||
@sites ||= Jekyll.all
|
# 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
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
require 'rack-flash'
|
require 'rack-flash'
|
||||||
require 'sinatra/base'
|
require 'sinatra/base'
|
||||||
|
require 'sinatra/reloader' if ENV['RACK_ENV'] == 'development'
|
||||||
require 'sinatra_warden'
|
require 'sinatra_warden'
|
||||||
require_relative 'login'
|
require_relative 'login'
|
||||||
|
require_relative 'site'
|
||||||
require_relative '../sutty'
|
require_relative '../sutty'
|
||||||
|
|
||||||
module Sutty
|
module Sutty
|
||||||
|
@ -11,12 +13,17 @@ module Sutty
|
||||||
class App < Sinatra::Base
|
class App < Sinatra::Base
|
||||||
use Rack::Flash
|
use Rack::Flash
|
||||||
use Sutty::Login
|
use Sutty::Login
|
||||||
|
use Sutty::Site
|
||||||
register Sinatra::Warden
|
register Sinatra::Warden
|
||||||
|
|
||||||
|
configure :development do
|
||||||
|
register Sinatra::Reloader
|
||||||
|
end
|
||||||
|
|
||||||
set :root, Sutty.root
|
set :root, Sutty.root
|
||||||
|
|
||||||
before do
|
before do
|
||||||
authorize! '/login'
|
authorize! '/login' if ENV['RACK_ENV'] == 'production'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/' do
|
get '/' do
|
||||||
|
|
|
@ -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
69
lib/sutty/models/post.rb
Normal 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
64
lib/sutty/post.rb
Normal 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
39
lib/sutty/site.rb
Normal 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
|
|
@ -5,4 +5,8 @@
|
||||||
%tbody
|
%tbody
|
||||||
- Sutty.sites.each do |site|
|
- Sutty.sites.each do |site|
|
||||||
%tr
|
%tr
|
||||||
%td= site.name
|
%td
|
||||||
|
%a{href: '/sites/' + site.name}
|
||||||
|
= site.name
|
||||||
|
%td
|
||||||
|
%a{href: 'http://' + site.name} visitar
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
%link{rel: 'stylesheet', type: 'text/css', href: '/assets/css/bootstrap.min.css'}
|
%link{rel: 'stylesheet', type: 'text/css', href: '/assets/css/bootstrap.min.css'}
|
||||||
%link{rel: 'stylesheet', type: 'text/css', href: '/stylesheets/sutty.css'}
|
%link{rel: 'stylesheet', type: 'text/css', href: '/stylesheets/sutty.css'}
|
||||||
%body{class: @has_cover ? 'background-cover' : ''}
|
%body{class: @has_cover ? 'background-cover' : ''}
|
||||||
.container
|
.container-fluid
|
||||||
= yield
|
= yield
|
||||||
%footer.footer
|
%footer.footer
|
||||||
%p{style: @has_cover ? 'color: white' : ''}
|
%p{style: @has_cover ? 'color: white' : ''}
|
||||||
|
|
6
views/posts/_form.haml
Normal file
6
views/posts/_form.haml
Normal 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
9
views/posts/edit.haml
Normal 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
13
views/posts/index.haml
Normal 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
12
views/posts/show.haml
Normal 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
5
views/sites/_nav.haml
Normal 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
5
views/sites/show.haml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.row
|
||||||
|
.col-2
|
||||||
|
= haml :'sites/_nav'
|
||||||
|
.col-10
|
||||||
|
%h1= @site.name
|
Loading…
Reference in a new issue