jekyll-admin-jekyll34 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []