jekyll-admin-jekyll34 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8124bc411abc6a87061492925eeed477af37be3
4
+ data.tar.gz: 96b42b2a0e3dbea0fa13d0a3c1992cf5a5f806bc
5
+ SHA512:
6
+ metadata.gz: 0f90c23700c917732f7c331881c36c96bfcc19b3041b2d3db5d16c61f4de4fcc32695d16e5f86abee191f093d6da2f480371edee4af6fa189fcc0c0b23609261
7
+ data.tar.gz: 1ac6b6c139cfb3a75d904735ea44ef7787d57777fff95951ac5193631647d01a04731afe3b737f8d5d620398332da07866421284ea0f85d7ea5487d8920a657b
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright 2017 Mert Kahyaoğlu and the Jekyll Admin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/jekyll-admin.svg)](https://rubygems.org/gems/jekyll-admin)
2
+ [![Build Status](https://travis-ci.org/jekyll/jekyll-admin.svg?branch=master)](https://travis-ci.org/jekyll/jekyll-admin)
3
+ [![Build status](https://ci.appveyor.com/api/projects/status/biop1r6ae524xlm2/branch/master?svg=true)](https://ci.appveyor.com/project/benbalter/jekyll-admin/branch/master)
4
+ [![NPM Dependencies](https://david-dm.org/jekyll/jekyll-admin.svg)](https://david-dm.org/jekyll/jekyll-admin)
5
+
6
+ A Jekyll plugin that provides users with a traditional CMS-style graphical interface to author content and administer Jekyll sites. The project is divided into two parts. A Ruby-based HTTP API that handles Jekyll and filesystem operations, and a Javascript-based front end, built on that API.
7
+
8
+ ![screenshot of Jekyll Admin](/screenshot.png)
9
+
10
+ ## Installation
11
+
12
+ Refer to the [installing plugins](https://jekyllrb.com/docs/plugins/#installing-a-plugin) section of Jekyll's documentation and install the `jekyll-admin` plugin as you would any other plugin. Here's the short version:
13
+
14
+ 1. Add the following to your site's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'jekyll-admin', group: :jekyll_plugins
18
+ ```
19
+
20
+ 2. Run `bundle install`
21
+
22
+ ## Usage
23
+
24
+ 1. Start Jekyll as you would normally (`bundle exec jekyll serve`)
25
+ 2. Navigate to `http://localhost:4000/admin` to access the administrative interface
26
+
27
+ ## Contributing
28
+
29
+ Interested in contributing to Jekyll Admin? We’d love your help. Jekyll Admin is an open source project, built one contribution at a time by users like you. See [the contributing instructions](.github/CONTRIBUTING.md), and [the development docs](http://jekyll.github.io/jekyll-admin/development/) for more information.
30
+
31
+ ## Looking for a hosted version?
32
+
33
+ Jekyll Admin is intended to be run on your computer alongside your local Jekyll installation. If you're looking for a hosted version, we'd recommend checking out [Siteleaf](https://www.siteleaf.com/) a hosted Jekyll editor with deep GitHub integration (whom we'd also like to thank for inspiring parts of Jekyll Admin itself!).
34
+
35
+ ## License
36
+
37
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,47 @@
1
+ # Default Sinatra to "production" mode (surpress errors) unless
2
+ # otherwise specified by the `RACK_ENV` environmental variable.
3
+ # Must be done prior to requiring Sinatra, or we'll get a LoadError
4
+ # as it looks for sinatra/cross-origin, which is development only
5
+ ENV["RACK_ENV"] = "production" if ENV["RACK_ENV"].to_s.empty?
6
+
7
+ require "json"
8
+ require "jekyll"
9
+ require "base64"
10
+ require "webrick"
11
+ require "sinatra"
12
+ require "fileutils"
13
+ require "sinatra/base"
14
+ require "sinatra/json"
15
+ require "addressable/uri"
16
+ require "sinatra/reloader"
17
+ require "sinatra/namespace"
18
+
19
+ module JekyllAdmin
20
+ autoload :APIable, "jekyll-admin/apiable"
21
+ autoload :DataFile, "jekyll-admin/data_file"
22
+ autoload :Directory, "jekyll-admin/directory"
23
+ autoload :FileHelper, "jekyll-admin/file_helper"
24
+ autoload :PageWithoutAFile, "jekyll-admin/page_without_a_file"
25
+ autoload :PathHelper, "jekyll-admin/path_helper"
26
+ autoload :Server, "jekyll-admin/server"
27
+ autoload :StaticServer, "jekyll-admin/static_server"
28
+ autoload :URLable, "jekyll-admin/urlable"
29
+ autoload :Version, "jekyll-admin/version"
30
+
31
+ def self.site
32
+ @site ||= begin
33
+ site = Jekyll.sites.first
34
+ site.future = true
35
+ site
36
+ end
37
+ end
38
+ end
39
+
40
+ # Monkey Patches
41
+ require_relative "./jekyll/commands/serve"
42
+ require_relative "./jekyll/commands/build"
43
+
44
+ [Jekyll::Page, Jekyll::Document, Jekyll::StaticFile, Jekyll::Collection].each do |klass|
45
+ klass.include JekyllAdmin::APIable
46
+ klass.include JekyllAdmin::URLable
47
+ end
@@ -0,0 +1,153 @@
1
+ module JekyllAdmin
2
+ # Abstract module to be included in Convertible and Document to provide
3
+ # additional, API-specific functionality without duplicating logic
4
+ module APIable
5
+
6
+ CONTENT_FIELDS = %w(next previous content excerpt).freeze
7
+
8
+ # Returns a hash suitable for use as an API response.
9
+ #
10
+ # For Documents and Pages:
11
+ #
12
+ # 1. Adds the file's raw content to the `raw_content` field
13
+ # 2. Adds the file's raw YAML front matter to the `front_matter` field
14
+ #
15
+ # For Static Files it addes the Base64 `encoded_content` field
16
+ #
17
+ # Options:
18
+ #
19
+ # include_content - if true, includes the content in the respond, false by default
20
+ # to support mapping on indexes where we only want metadata
21
+ #
22
+ #
23
+ # Returns a hash (which can then be to_json'd)
24
+ def to_api(include_content: false)
25
+ output = hash_for_api
26
+ output = output.merge(url_fields)
27
+
28
+ # Include content, if requested, otherwise remove it
29
+ if include_content
30
+ output = output.merge(content_fields)
31
+ else
32
+ CONTENT_FIELDS.each { |field| output.delete(field) }
33
+ end
34
+
35
+ # Documents have duplicate output and content fields, Pages do not
36
+ # Since it's an API, use `content` in both for consistency
37
+ output.delete("output")
38
+
39
+ # By default, calling to_liquid on a collection will return a docs
40
+ # array with each rendered document, which we don't want.
41
+ if is_a?(Jekyll::Collection)
42
+ output.delete("docs")
43
+ output["entries_url"] = entries_url
44
+ end
45
+
46
+ if is_a?(Jekyll::Document)
47
+ output["name"] = basename
48
+ end
49
+
50
+ output
51
+ end
52
+
53
+ private
54
+
55
+ # Pages don't have a hash method, but Documents do
56
+ def file_path
57
+ if is_a?(Jekyll::Document)
58
+ path
59
+ else
60
+ File.join(@base, @dir, name)
61
+ end
62
+ end
63
+
64
+ # StaticFiles don't have `attr_accesor` set for @site or @name
65
+ def site
66
+ @site
67
+ end
68
+
69
+ def name
70
+ @name
71
+ end
72
+
73
+ def file_contents
74
+ @file_contents ||= File.read(file_path, file_read_options) if file_exists?
75
+ end
76
+
77
+ def file_read_options
78
+ Jekyll::Utils.merged_file_read_opts(site, {})
79
+ end
80
+
81
+ def front_matter
82
+ return unless file_exists?
83
+ @front_matter ||= if file_contents =~ Jekyll::Document::YAML_FRONT_MATTER_REGEXP
84
+ SafeYAML.load(Regexp.last_match(1))
85
+ else
86
+ {}
87
+ end
88
+ end
89
+
90
+ def raw_content
91
+ return unless file_exists?
92
+ @raw_content ||= if file_contents =~ Jekyll::Document::YAML_FRONT_MATTER_REGEXP
93
+ $POSTMATCH
94
+ else
95
+ file_contents
96
+ end
97
+ end
98
+
99
+ def encoded_content
100
+ @encoded_content ||= Base64.encode64(file_contents) if file_exists?
101
+ end
102
+
103
+ def file_exists?
104
+ return @file_exists if defined? @file_exists
105
+ @file_exists = File.exist?(file_path)
106
+ end
107
+
108
+ # Base hash from which to generate the API output
109
+ def hash_for_api
110
+ output = to_liquid
111
+ if output.respond_to?(:hash_for_json)
112
+ output.hash_for_json
113
+ else
114
+ output.to_h
115
+ end
116
+ end
117
+
118
+ # Returns a hash of content fields for inclusion in the API output
119
+ def content_fields
120
+ output = {}
121
+
122
+ # Include file content-related fields
123
+ if is_a?(Jekyll::StaticFile)
124
+ output["encoded_content"] = encoded_content
125
+ elsif is_a?(JekyllAdmin::DataFile)
126
+ output["content"] = content
127
+ output["raw_content"] = raw_content
128
+ else
129
+ output["raw_content"] = raw_content
130
+ output["front_matter"] = front_matter
131
+ end
132
+
133
+ # Include next and previous documents non-recursively
134
+ if is_a?(Jekyll::Document)
135
+ %w(next previous).each do |direction|
136
+ method = "#{direction}_doc".to_sym
137
+ doc = public_send(method)
138
+ output[direction] = doc.to_api if doc
139
+ end
140
+ end
141
+
142
+ output
143
+ end
144
+
145
+ def url_fields
146
+ return {} unless respond_to?(:http_url) && respond_to?(:api_url)
147
+ {
148
+ "http_url" => http_url,
149
+ "api_url" => api_url,
150
+ }
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,91 @@
1
+ module JekyllAdmin
2
+ class DataFile
3
+ METHODS_FOR_LIQUID = %w(path relative_path slug ext title).freeze
4
+ EXTENSIONS = %w(yaml yml json csv).freeze
5
+
6
+ include APIable
7
+ include URLable
8
+ include PathHelper
9
+ extend PathHelper
10
+
11
+ alias_method :path, :relative_path
12
+
13
+ # Initialize a new DataFile object
14
+ #
15
+ # id - the file ID as passed from the API. This may or may not have an extension
16
+ def initialize(id)
17
+ @id ||= id
18
+ end
19
+
20
+ def exists?
21
+ @exists ||= File.exist?(absolute_path)
22
+ end
23
+
24
+ # Returns unparsed content as it exists on disk
25
+ def raw_content
26
+ @raw_content ||= File.open(absolute_path, "r:UTF-8", &:read)
27
+ end
28
+
29
+ # Returnes (re)parsed content using Jekyll's native parsing mechanism
30
+ def content
31
+ @content ||= data_reader.read_data_file(absolute_path)
32
+ end
33
+
34
+ # Returns the file's extension with preceeding `.`
35
+ def ext
36
+ @ext ||= if File.extname(@id).to_s.empty?
37
+ ".yml"
38
+ else
39
+ File.extname(@id)
40
+ end
41
+ end
42
+ alias_method :extension, :ext
43
+
44
+ # Returns the file's sanitized slug (as used in `site.data[slug]`)
45
+ def slug
46
+ @slug ||= data_reader.sanitize_filename(basename)
47
+ end
48
+
49
+ # Returns the human-readable title of the data file
50
+ def title
51
+ @title ||= Jekyll::Utils.titleize_slug(slug.tr("_", "-"))
52
+ end
53
+
54
+ # Mimics Jekyll's native to_liquid functionality by returning a hash
55
+ # of data file metadata
56
+ def to_liquid
57
+ @to_liquid ||= METHODS_FOR_LIQUID.map { |key| [key, public_send(key)] }.to_h
58
+ end
59
+
60
+ def self.all
61
+ data_dir = sanitized_path DataFile.data_dir
62
+ Dir["#{data_dir}/*.{#{EXTENSIONS.join(",")}}"].map do |path|
63
+ new path_without_site_source(path)
64
+ end
65
+ end
66
+
67
+ # Relative path to data directory within site source
68
+ def self.data_dir
69
+ JekyllAdmin.site.config["data_dir"]
70
+ end
71
+
72
+ private
73
+
74
+ def data_reader
75
+ @data_reader = Jekyll::DataReader.new(JekyllAdmin.site)
76
+ end
77
+
78
+ def basename
79
+ @basename ||= File.basename(@id, ".*")
80
+ end
81
+
82
+ def basename_with_extension
83
+ [basename, extension].join
84
+ end
85
+ alias_method :filename, :basename_with_extension
86
+
87
+ def namespace
88
+ "data"
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ module JekyllAdmin
2
+ class Directory
3
+ extend Forwardable
4
+ def_delegator :@path, :basename, :name
5
+ def_delegator :@path, :mtime, :modified_time
6
+ attr_reader :path, :splat, :content_type, :base
7
+
8
+ include Enumerable
9
+ include JekyllAdmin::URLable
10
+ include JekyllAdmin::APIable
11
+
12
+ TYPE = :directory
13
+
14
+ # Arguments:
15
+ #
16
+ # path - full path of the directory which its entries will be listed
17
+ #
18
+ # base - passes site.source to generate `relative_path` needed for `to_api`
19
+ #
20
+ # content_type - type of the requested directory entries, this is used to generate
21
+ # API endpoint of the directory along with `splat`
22
+ #
23
+ # splat - the requested directory path relative to content namespace
24
+ def initialize(path, base: nil, content_type: nil, splat: nil)
25
+ @base = Pathname.new base
26
+ @content_type = content_type
27
+ @splat = Pathname.new splat
28
+ @path = Pathname.new path
29
+ end
30
+
31
+ def to_liquid
32
+ {
33
+ :name => name,
34
+ :modified_time => modified_time,
35
+ :path => relative_path,
36
+ :type => TYPE,
37
+ }
38
+ end
39
+
40
+ def relative_path
41
+ path.relative_path_from(base).to_s
42
+ end
43
+
44
+ def resource_path
45
+ if content_type == "pages"
46
+ "/pages/#{splat}/#{name}"
47
+ else
48
+ "/collections/#{content_type}/entries/#{splat}/#{name}"
49
+ end
50
+ end
51
+ alias_method :url, :resource_path
52
+
53
+ def http_url
54
+ nil
55
+ end
56
+
57
+ def directories
58
+ path.entries.map do |entry|
59
+ next if [".", ".."].include? entry.to_s
60
+ next unless path.join(entry).directory?
61
+ self.class.new(
62
+ path.join(entry),
63
+ :base => base, :content_type => content_type, :splat => splat
64
+ )
65
+ end.compact!
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,64 @@
1
+ module JekyllAdmin
2
+ module FileHelper
3
+
4
+ # The file the user requested in the URL
5
+ def requested_file
6
+ find_by_path(path)
7
+ end
8
+
9
+ # The file ultimately written to disk
10
+ # This may be the requested file, or in the case of a rename will be read
11
+ # from the new path that was actually written to disk
12
+ def written_file
13
+ find_by_path(write_path)
14
+ end
15
+
16
+ # Write a file to disk with the given content
17
+ def write_file(path, content)
18
+ Jekyll.logger.debug "WRITING:", path
19
+ path = sanitized_path(path)
20
+ FileUtils.mkdir_p File.dirname(path)
21
+ File.write(path, content)
22
+ site.process
23
+ end
24
+
25
+ # Delete the file at the given path
26
+ def delete_file(path)
27
+ Jekyll.logger.debug "DELETING:", path
28
+ FileUtils.rm_f sanitized_path(path)
29
+ site.process
30
+ end
31
+
32
+ private
33
+
34
+ def ensure_requested_file
35
+ ensure_file(requested_file)
36
+ end
37
+
38
+ def ensure_written_file
39
+ ensure_file(written_file)
40
+ end
41
+
42
+ def find_by_path(path)
43
+ files = case namespace
44
+ when "collections"
45
+ collection.docs
46
+ when "data"
47
+ DataFile.all
48
+ when "pages", "static_files"
49
+ site.public_send(namespace.to_sym)
50
+ else
51
+ []
52
+ end
53
+ files.find { |f| sanitized_path(f.path) == path }
54
+ end
55
+
56
+ def ensure_file(file)
57
+ render_404 if file.nil?
58
+ end
59
+
60
+ def ensure_directory
61
+ render_404 unless Dir.exist?(directory_path)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module JekyllAdmin
2
+ class PageWithoutAFile < Jekyll::Page
3
+ def read_yaml(*)
4
+ @data ||= {}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,74 @@
1
+ module JekyllAdmin
2
+ module PathHelper
3
+ def sanitized_path(path)
4
+ path = path_without_site_source(path)
5
+ Jekyll.sanitized_path JekyllAdmin.site.source, path
6
+ end
7
+
8
+ # Returns the basename + extension for the requested file
9
+ def filename
10
+ params["ext"] ||= "yml" if namespace == "data"
11
+ "#{params["path"]}.#{params["ext"]}"
12
+ end
13
+
14
+ # Returns the sanitized path relative to the site source
15
+ def sanitized_relative_path(path)
16
+ path_without_site_source sanitized_path(path)
17
+ end
18
+
19
+ # Returns the sanitized absolute path to the requested object
20
+ def absolute_path
21
+ sanitized_path File.join(directory_path, filename)
22
+ end
23
+ alias_method :path, :absolute_path
24
+
25
+ # Returns the sanitized relative path to the requested object
26
+ def relative_path
27
+ sanitized_relative_path absolute_path
28
+ end
29
+
30
+ # Returns the sanitized absolute path to write the requested object
31
+ def write_path
32
+ if renamed?
33
+ sanitized_path request_payload["path"]
34
+ else
35
+ path
36
+ end
37
+ end
38
+ alias_method :request_path, :write_path
39
+
40
+ # Returns the sanitized relative path to write the requested object
41
+ def relative_write_path
42
+ sanitized_relative_path write_path
43
+ end
44
+
45
+ # Is this request renaming a file?
46
+ def renamed?
47
+ return false unless request_payload["path"]
48
+ ensure_leading_slash(request_payload["path"]) != relative_path
49
+ end
50
+
51
+ private
52
+
53
+ # Returns the path to the requested file's containing directory
54
+ def directory_path
55
+ case namespace
56
+ when "collections"
57
+ sanitized_path File.join(collection.relative_directory, params["splat"].first)
58
+ when "data"
59
+ sanitized_path File.join(DataFile.data_dir)
60
+ else
61
+ sanitized_path params["splat"].first
62
+ end
63
+ end
64
+
65
+ def ensure_leading_slash(input)
66
+ return input if input.nil? || input.empty? || input.start_with?("/")
67
+ "/#{input}"
68
+ end
69
+
70
+ def path_without_site_source(path)
71
+ path.to_s.gsub(%r!\A#{Regexp.escape(JekyllAdmin.site.source)}!, "")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,91 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ ROUTES = %w(collections configuration data pages static_files).freeze
4
+ include JekyllAdmin::PathHelper
5
+ include JekyllAdmin::FileHelper
6
+
7
+ register Sinatra::Namespace
8
+
9
+ configure :development do
10
+ register Sinatra::Reloader
11
+ enable :logging
12
+ end
13
+
14
+ configure :development, :test do
15
+ require "sinatra/cross_origin"
16
+ register Sinatra::CrossOrigin
17
+ enable :cross_origin
18
+ disable :allow_credentials
19
+ set :allow_methods, %i(delete get options post put)
20
+ end
21
+
22
+ get "/" do
23
+ json ROUTES.map { |r| ["#{r}_api", URI.join(base_url, "/_api/", r)] }.to_h
24
+ end
25
+
26
+ # CORS preflight
27
+ options "*" do
28
+ render_404 unless settings.development? || settings.test?
29
+ status 204
30
+ end
31
+
32
+ private
33
+
34
+ def site
35
+ JekyllAdmin.site
36
+ end
37
+
38
+ def render_404
39
+ status 404
40
+ content_type :json
41
+ halt
42
+ end
43
+
44
+ def request_payload
45
+ @request_payload ||= if request_body.to_s.empty?
46
+ {}
47
+ else
48
+ JSON.parse(request_body)
49
+ end
50
+ end
51
+
52
+ def base_url
53
+ "#{request.scheme}://#{request.host_with_port}"
54
+ end
55
+
56
+ def front_matter
57
+ request_payload["front_matter"]
58
+ end
59
+
60
+ def document_body
61
+ body = if front_matter && !front_matter.empty?
62
+ YAML.dump(front_matter).strip
63
+ else
64
+ "---"
65
+ end
66
+ body << "\n---\n\n"
67
+ body << request_payload["raw_content"].to_s
68
+ end
69
+ alias page_body document_body
70
+
71
+ private
72
+
73
+ def request_body
74
+ @request_body ||= begin
75
+ request.body.rewind
76
+ request.body.read
77
+ end
78
+ end
79
+
80
+ def namespace
81
+ namespace = request.path_info.split("/")[1].to_s.downcase
82
+ namespace if ROUTES.include?(namespace)
83
+ end
84
+ end
85
+ end
86
+
87
+ require "jekyll-admin/server/collection"
88
+ require "jekyll-admin/server/configuration"
89
+ require "jekyll-admin/server/data"
90
+ require "jekyll-admin/server/page"
91
+ require "jekyll-admin/server/static_file"
@@ -0,0 +1,82 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ namespace "/collections" do
4
+ get do
5
+ json(site.collections.map { |c| c[1].to_api })
6
+ end
7
+
8
+ get "/:collection_id" do
9
+ ensure_collection
10
+ json collection.to_api
11
+ end
12
+
13
+ get "/:collection_id/*?/?:path.:ext" do
14
+ ensure_requested_file
15
+ json requested_file.to_api(:include_content => true)
16
+ end
17
+
18
+ get "/:collection_id/entries/?*" do
19
+ ensure_directory
20
+ json entries.map(&:to_api)
21
+ end
22
+
23
+ put "/:collection_id/*?/?:path.:ext" do
24
+ ensure_collection
25
+
26
+ if renamed?
27
+ ensure_requested_file
28
+ delete_file path
29
+ end
30
+
31
+ write_file write_path, document_body
32
+ json written_file.to_api(:include_content => true)
33
+ end
34
+
35
+ delete "/:collection_id/*?/?:path.:ext" do
36
+ ensure_requested_file
37
+ delete_file path
38
+ content_type :json
39
+ status 200
40
+ halt
41
+ end
42
+
43
+ private
44
+
45
+ def collection
46
+ collection = site.collections.find { |l, _c| l == params["collection_id"] }
47
+ collection[1] if collection
48
+ end
49
+
50
+ def document_id
51
+ path = "#{params["splat"].first}/#{filename}"
52
+ path.gsub(%r!(\d{4})/(\d{2})/(\d{2})/(.*)!, '\1-\2-\3-\4')
53
+ end
54
+
55
+ def directory_docs
56
+ collection.docs.find_all { |d| File.dirname(d.path) == directory_path }
57
+ end
58
+
59
+ def ensure_collection
60
+ render_404 if collection.nil?
61
+ end
62
+
63
+ def ensure_directory
64
+ ensure_collection
65
+ render_404 unless Dir.exist?(directory_path)
66
+ end
67
+
68
+ def entries
69
+ args = {
70
+ :base => site.source,
71
+ :content_type => params["collection_id"],
72
+ :splat => params["splat"].first,
73
+ }
74
+ # get the directories inside the requested directory
75
+ directory = JekyllAdmin::Directory.new(directory_path, args)
76
+ directories = directory.directories
77
+ # merge directories with the documents at the same level
78
+ directories.concat(directory_docs.sort_by(&:date).reverse)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ namespace "/configuration" do
4
+ get do
5
+ json raw_configuration.to_liquid
6
+ end
7
+
8
+ put do
9
+ write_file(configuration_path, configuration_body)
10
+ json raw_configuration.to_liquid
11
+ end
12
+
13
+ private
14
+
15
+ def overrides
16
+ {
17
+ "source" => sanitized_path("/"),
18
+ }
19
+ end
20
+
21
+ # Computed configuration, with updates and defaults
22
+ def configuration
23
+ @configuration ||= Jekyll.configuration(overrides)
24
+ end
25
+
26
+ # Raw configuration, as it sits on disk
27
+ def raw_configuration
28
+ configuration.read_config_file(configuration_path)
29
+ end
30
+
31
+ # Returns the path to the *first* config file discovered
32
+ def configuration_path
33
+ sanitized_path configuration.config_files(overrides).first
34
+ end
35
+
36
+ # The user's uploaded configuration for updates
37
+ def configuration_body
38
+ YAML.dump request_payload
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ # supported extensions, in order of preference, for now, no .csv
4
+ EXTENSIONS = %w(yml json).freeze
5
+
6
+ namespace "/data" do
7
+ get do
8
+ json DataFile.all.map(&:to_api)
9
+ end
10
+
11
+ get "/:path.?:ext?" do
12
+ ensure_requested_file
13
+ json requested_file.to_api(:include_content => true)
14
+ end
15
+
16
+ put "/:path.?:ext?" do
17
+ if renamed?
18
+ ensure_requested_file
19
+ delete_file path
20
+ end
21
+
22
+ write_file write_path, data_file_body
23
+ json written_file.to_api(:include_content => true)
24
+ end
25
+
26
+ delete "/:path.?:ext?" do
27
+ ensure_requested_file
28
+ delete_file path
29
+ content_type :json
30
+ status 200
31
+ halt
32
+ end
33
+
34
+ private
35
+
36
+ def data_file_body
37
+ if !request_payload["raw_content"].to_s.empty?
38
+ request_payload["raw_content"]
39
+ elsif !request_payload["content"].to_s.empty?
40
+ YAML.dump(request_payload["content"]).sub(%r!\A---\n!, "")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,90 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ namespace "/pages" do
4
+ get "/*?/?:path.:ext" do
5
+ ensure_requested_file
6
+ json requested_file.to_api(:include_content => true)
7
+ end
8
+
9
+ get "/?*" do
10
+ ensure_directory
11
+ json entries.map(&:to_api)
12
+ end
13
+
14
+ put "/*?/?:path.:ext" do
15
+ ensure_html_content
16
+
17
+ if renamed?
18
+ ensure_requested_file
19
+ delete_file path
20
+ end
21
+
22
+ write_file write_path, page_body
23
+
24
+ json written_file.to_api(:include_content => true)
25
+ end
26
+
27
+ delete "/*?/?:path.:ext" do
28
+ ensure_requested_file
29
+ delete_file path
30
+ content_type :json
31
+ status 200
32
+ halt
33
+ end
34
+
35
+ private
36
+
37
+ def ensure_html_content
38
+ return if html_content?
39
+ content_type :json
40
+ halt 422, json("error_message" => "Invalid file extension for pages")
41
+ end
42
+
43
+ def html_content?
44
+ page = JekyllAdmin::PageWithoutAFile.new(
45
+ site,
46
+ site.source,
47
+ "",
48
+ request_payload["path"] || filename
49
+ )
50
+ page.data = request_payload["front_matter"]
51
+ page.html?
52
+ end
53
+
54
+ def pages
55
+ site.pages.select(&:html?)
56
+ end
57
+
58
+ def directory_pages
59
+ pages.find_all do |p|
60
+ sanitized_path(File.dirname(p.path)) == directory_path
61
+ end
62
+ end
63
+
64
+ # returns relative path of root level directories that contain pages
65
+ def directory_paths
66
+ pages.map { |p| File.dirname(p.path).split("/")[0] }.uniq
67
+ end
68
+
69
+ def entries
70
+ args = {
71
+ :base => site.source,
72
+ :content_type => "pages",
73
+ :splat => params["splat"].first,
74
+ }
75
+ # get all directories inside the requested directory
76
+ directory = JekyllAdmin::Directory.new(directory_path, args)
77
+ directories = directory.directories
78
+
79
+ # exclude root level directories which do not have pages
80
+ if params["splat"].first.empty?
81
+ directories = directories.select do |d|
82
+ directory_paths.include? d.name.to_s
83
+ end
84
+ end
85
+ # merge directories with the pages at the same level
86
+ directories.concat(directory_pages)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,61 @@
1
+ module JekyllAdmin
2
+ class Server < Sinatra::Base
3
+ namespace "/static_files" do
4
+ get do
5
+ json static_files.map(&:to_api)
6
+ end
7
+
8
+ get "/*" do
9
+ if requested_file
10
+ json requested_file.to_api(:include_content => true)
11
+ elsif !static_files_for_path.empty?
12
+ json static_files_for_path.map(&:to_api)
13
+ else
14
+ render_404
15
+ end
16
+ end
17
+
18
+ put "/*" do
19
+ if renamed?
20
+ ensure_requested_file
21
+ delete_file path
22
+ end
23
+
24
+ write_file(write_path, static_file_body)
25
+ json written_file.to_api(:include_content => true)
26
+ end
27
+
28
+ delete "/*" do
29
+ ensure_requested_file
30
+ delete_file path
31
+ content_type :json
32
+ status 200
33
+ halt
34
+ end
35
+
36
+ private
37
+
38
+ def static_file_body
39
+ if !request_payload["raw_content"].to_s.empty?
40
+ request_payload["raw_content"].to_s
41
+ else
42
+ Base64.decode64 request_payload["encoded_content"].to_s
43
+ end
44
+ end
45
+
46
+ def static_files
47
+ site.static_files
48
+ end
49
+
50
+ def file_list_dir(path) end
51
+
52
+ def static_files_for_path
53
+ # Joined with / to ensure user can't do partial paths
54
+ base_path = File.join(path, "/")
55
+ static_files.select do |f|
56
+ f.path.start_with? base_path
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module JekyllAdmin
2
+ class StaticServer < Sinatra::Base
3
+ set :public_dir, File.expand_path("./public", File.dirname(__FILE__))
4
+
5
+ MUST_BUILD_MESSAGE = "Front end not yet built. Run `script/build` to build.".freeze
6
+
7
+ # Allow `/admin` and `/admin/`, and `/admin/*` to serve `/public/dist/index.html`
8
+ get "/*" do
9
+ send_file index_path
10
+ end
11
+
12
+ # Provide a descriptive error message in dev. if frontend is not build
13
+ not_found do
14
+ status 404
15
+ MUST_BUILD_MESSAGE if settings.development? || settings.test?
16
+ end
17
+
18
+ private
19
+
20
+ def index_path
21
+ @index_path ||= File.join(settings.public_folder, "index.html")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ module JekyllAdmin
2
+ # Abstract module to be included in Convertible and Document to provide
3
+ # additional, URL-specific functionality without duplicating logic
4
+ module URLable
5
+
6
+ # Absolute URL to the HTTP(S) rendered/served representation of this resource
7
+ def http_url
8
+ return if is_a?(Jekyll::Collection) || is_a?(JekyllAdmin::DataFile)
9
+ return if is_a?(Jekyll::Document) && !collection.write?
10
+ @http_url ||= Addressable::URI.new(
11
+ :scheme => scheme, :host => host, :port => port,
12
+ :path => path_with_base(JekyllAdmin.site.config["baseurl"], url)
13
+ ).normalize.to_s
14
+ end
15
+
16
+ # Absolute URL to the API representation of this resource
17
+ def api_url
18
+ @api_url ||= Addressable::URI.new(
19
+ :scheme => scheme, :host => host, :port => port,
20
+ :path => path_with_base("/_api", resource_path)
21
+ ).normalize.to_s
22
+ end
23
+
24
+ def entries_url
25
+ return unless is_a?(Jekyll::Collection)
26
+ "#{api_url}/entries"
27
+ end
28
+
29
+ private
30
+
31
+ # URL path relative to `_api/` to retreive the given resource via the API
32
+ # Note: we can't use a case statement here, because === doesn't like includes
33
+ def resource_path
34
+ if is_a?(Jekyll::Document)
35
+ "/collections/#{relative_path.sub(%r!\A_!, "")}"
36
+ elsif is_a?(Jekyll::Collection)
37
+ "/collections/#{label}"
38
+ elsif is_a?(JekyllAdmin::DataFile)
39
+ relative_path.sub(%r!\A/#{DataFile.data_dir}!, "/data")
40
+ elsif is_a?(Jekyll::StaticFile)
41
+ "/static_files/#{relative_path}"
42
+ elsif is_a?(Jekyll::Page)
43
+ "/pages/#{relative_path}"
44
+ end
45
+ end
46
+
47
+ # URI.join doesn't like joining two relative paths, and File.join may join
48
+ # with `\` rather than with `/` on windows
49
+ def path_with_base(base, path)
50
+ [base, path].join("/").squeeze("/")
51
+ end
52
+
53
+ def scheme
54
+ JekyllAdmin.site.config["scheme"] || "http"
55
+ end
56
+
57
+ def host
58
+ JekyllAdmin.site.config["host"].sub("127.0.0.1", "localhost")
59
+ end
60
+
61
+ def port
62
+ JekyllAdmin.site.config["port"]
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module JekyllAdmin
2
+ VERSION = "0.4.3".freeze
3
+ end
@@ -0,0 +1,14 @@
1
+ module Jekyll
2
+ module Commands
3
+ class Build < Command
4
+ class << self
5
+ alias_method :original_build, :build
6
+
7
+ def build(site, options)
8
+ options["watch"] = false
9
+ original_build(site, options)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module Jekyll
2
+ module Commands
3
+ class Serve < Command
4
+ class << self
5
+ def start_up_webrick(opts, destination)
6
+ server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
7
+ server.mount(opts["baseurl"], Servlet, destination, file_handler_opts)
8
+
9
+ jekyll_admin_monkey_patch(server)
10
+
11
+ Jekyll.logger.info "Server address:", server_address(server, opts)
12
+ launch_browser server, opts if opts["open_url"]
13
+ boot_or_detach server, opts
14
+ end
15
+
16
+ def jekyll_admin_monkey_patch(server)
17
+ server.mount "/admin", Rack::Handler::WEBrick, JekyllAdmin::StaticServer
18
+ server.mount "/_api", Rack::Handler::WEBrick, JekyllAdmin::Server
19
+ Jekyll.logger.info "JekyllAdmin mode:", ENV["RACK_ENV"] || "production"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-admin-jekyll34
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.3
5
+ platform: ruby
6
+ authors:
7
+ - Mert Kahyaoğlu
8
+ - GitHub Open Source
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2017-04-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jekyll
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '3.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '3.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: sinatra
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '1.4'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '1.4'
42
+ - !ruby/object:Gem::Dependency
43
+ name: sinatra-contrib
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '1.4'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '1.4'
56
+ - !ruby/object:Gem::Dependency
57
+ name: addressable
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '2.4'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.4'
70
+ - !ruby/object:Gem::Dependency
71
+ name: bundler
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '1.7'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '1.7'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rake
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '10.0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '10.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '3.4'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '3.4'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rubocop
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0.35'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0.35'
126
+ - !ruby/object:Gem::Dependency
127
+ name: sinatra-cross_origin
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0.3'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0.3'
140
+ - !ruby/object:Gem::Dependency
141
+ name: gem-release
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0.7'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0.7'
154
+ description: Jekyll::Admin is a drop in administrative framework for Jekyll sites.
155
+ email:
156
+ - mertkahyaoglu93@gmail.com
157
+ - opensource@github.com
158
+ executables: []
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - LICENSE
163
+ - README.md
164
+ - lib/jekyll-admin.rb
165
+ - lib/jekyll-admin/apiable.rb
166
+ - lib/jekyll-admin/data_file.rb
167
+ - lib/jekyll-admin/directory.rb
168
+ - lib/jekyll-admin/file_helper.rb
169
+ - lib/jekyll-admin/page_without_a_file.rb
170
+ - lib/jekyll-admin/path_helper.rb
171
+ - lib/jekyll-admin/server.rb
172
+ - lib/jekyll-admin/server/collection.rb
173
+ - lib/jekyll-admin/server/configuration.rb
174
+ - lib/jekyll-admin/server/data.rb
175
+ - lib/jekyll-admin/server/page.rb
176
+ - lib/jekyll-admin/server/static_file.rb
177
+ - lib/jekyll-admin/static_server.rb
178
+ - lib/jekyll-admin/urlable.rb
179
+ - lib/jekyll-admin/version.rb
180
+ - lib/jekyll/commands/build.rb
181
+ - lib/jekyll/commands/serve.rb
182
+ homepage: https://github.com/honey-be/jekyll-admin-jekyll34
183
+ licenses:
184
+ - MIT
185
+ metadata:
186
+ allowed_push_host: https://rubygems.org
187
+ post_install_message:
188
+ rdoc_options: []
189
+ require_paths:
190
+ - lib
191
+ required_ruby_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ required_rubygems_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ requirements: []
202
+ rubyforge_project:
203
+ rubygems_version: 2.6.11
204
+ signing_key:
205
+ specification_version: 4
206
+ summary: wp-admin for Jekyll, but better
207
+ test_files: []