jekyll-admin 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -1
  3. data/lib/jekyll-admin.rb +14 -13
  4. data/lib/jekyll-admin/apiable.rb +71 -10
  5. data/lib/jekyll-admin/data_file.rb +19 -16
  6. data/lib/jekyll-admin/directory.rb +24 -26
  7. data/lib/jekyll-admin/file_helper.rb +14 -0
  8. data/lib/jekyll-admin/path_helper.rb +8 -1
  9. data/lib/jekyll-admin/public/12f0820c451bdc75f4d1ef97732bf6e8.woff +0 -0
  10. data/lib/jekyll-admin/public/{03945ac4fc7fdefc44bc110bf1ba2393.svg → 792dcd18baf5f544aabcad1883d673c2.svg} +14 -8
  11. data/lib/jekyll-admin/public/{bfc14ac982326f7d0b1340e20d3e0c37.ttf → bc7c4a59f924cf037aad6e1f9edba366.eot} +0 -0
  12. data/lib/jekyll-admin/public/bundle.js +27 -17
  13. data/lib/jekyll-admin/public/bundle.js.map +1 -1
  14. data/lib/jekyll-admin/public/{e44520ab9079ea7633bfa874bed5d21d.eot → eceddf474df95d8d4a7e316668c3be85.ttf} +0 -0
  15. data/lib/jekyll-admin/public/styles.css +103 -2
  16. data/lib/jekyll-admin/public/styles.css.map +1 -1
  17. data/lib/jekyll-admin/server.rb +21 -12
  18. data/lib/jekyll-admin/server/{collection.rb → collections.rb} +11 -4
  19. data/lib/jekyll-admin/server/configuration.rb +5 -2
  20. data/lib/jekyll-admin/server/data.rb +3 -1
  21. data/lib/jekyll-admin/server/{draft.rb → drafts.rb} +8 -3
  22. data/lib/jekyll-admin/server/{page.rb → pages.rb} +22 -4
  23. data/lib/jekyll-admin/server/site_meta.rb +25 -0
  24. data/lib/jekyll-admin/server/static_files.rb +83 -0
  25. data/lib/jekyll-admin/static_server.rb +3 -1
  26. data/lib/jekyll-admin/urlable.rb +4 -0
  27. data/lib/jekyll-admin/version.rb +3 -1
  28. data/lib/jekyll/commands/serve.rb +2 -0
  29. metadata +14 -14
  30. data/lib/jekyll-admin/page_without_a_file.rb +0 -7
  31. data/lib/jekyll-admin/public/99adb54b0f30c0758bb4cb9ed5b80aa8.woff +0 -0
  32. data/lib/jekyll-admin/server/static_file.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23d2ff89a0eaf62c63c8b52e919ae8a347ec623c4f0db5a2810ca387d5aa92f0
4
- data.tar.gz: 5ac8a35dfa123e685c61a75488e19c0106dd55b3984e60b6fd8ed083d87b67a7
3
+ metadata.gz: 812331dbfeef3f0d734b9ed66c1d9a1acdc3dc10c86cbeaac9ec86be92fe2bc9
4
+ data.tar.gz: 8b072ab67bfccb13f8a33f56504da72296337a03ccb633a742e2fac0cd00e247
5
5
  SHA512:
6
- metadata.gz: 4527e5b2b8c03417a198784c2949f47f596e3d004982a6a603a0f007f16094559eab7815b2b916e3e93c4f62dd6289401a33eea218c60e3f6a303ee257a96ff1
7
- data.tar.gz: d6770f35c386dfd341731e6ea27ba403d53c6b8d1afd9acbe8b2ce97917bb178598e32f21e8232ef566e3337126e23534ce755ebf8f78cfc2f532322e22ef2a3
6
+ metadata.gz: 70be96805de811a7b00bd279318e5285bd3f22ae5efc2a9849a8eba4e82c2a725319c31219ba347cd2582aad148b428586a0932ac8140e81cf260ae036553ea8
7
+ data.tar.gz: 3b17ef26207b523088fc917efd755d7844a9d8238e5ac24245c07d590d9d22a5b95382c775ecf035c8cc2aa2587a989f55d34899f17eeba9b48ff45f9a875dc6
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  [![Gem Version](https://img.shields.io/gem/v/jekyll-admin.svg)](https://rubygems.org/gems/jekyll-admin)
2
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)
3
+ [![Build status](https://ci.appveyor.com/api/projects/status/u6u9tn7rk5tln33s/branch/master?svg=true)](https://ci.appveyor.com/project/jekyll/jekyll-admin)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/jekyll/jekyll-admin/badge.svg?branch=master)](https://coveralls.io/github/jekyll/jekyll-admin?branch=master)
5
5
  [![NPM Dependencies](https://david-dm.org/jekyll/jekyll-admin.svg)](https://david-dm.org/jekyll/jekyll-admin)
6
+ [![Financial Contributors on Open Collective](https://opencollective.com/jekyll-admin/all/badge.svg?label=financial+contributors)](https://opencollective.com/jekyll-admin)
6
7
 
7
8
  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.
8
9
 
@@ -41,10 +42,53 @@ jekyll_admin:
41
42
  - configuration
42
43
  ```
43
44
 
45
+ ### Customizing collection label in Sidebar
46
+
47
+ The plugin allows you to customize the name of a collection that is displayed in the sidebar by defining it in the collection's
48
+ metadata in the config file. For example, if your source's *posts* are actually *news-items* on the deployed site, then it can
49
+ be distracting to see the label `Posts` in the admin's sidebar. This situation can be resolved with the following configuration:
50
+
51
+ ```yaml
52
+ collections:
53
+ posts:
54
+ output: true
55
+ sidebar_label: News
56
+ ```
57
+
44
58
  ## Contributing
45
59
 
46
60
  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](https://jekyll.github.io/jekyll-admin/development/) for more information.
47
61
 
62
+ ## Contributors
63
+
64
+ ### Code Contributors
65
+
66
+ This project exists thanks to all the people who contribute. [[Contribute](.github/CONTRIBUTING.md)].
67
+ <a href="https://github.com/jekyll/jekyll-admin/graphs/contributors"><img src="https://opencollective.com/jekyll-admin/contributors.svg?width=890&button=false" /></a>
68
+
69
+ ### Financial Contributors
70
+
71
+ Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/jekyll-admin/contribute)]
72
+
73
+ #### Individuals
74
+
75
+ <a href="https://opencollective.com/jekyll-admin"><img src="https://opencollective.com/jekyll-admin/individuals.svg?width=890"></a>
76
+
77
+ #### Organizations
78
+
79
+ Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/jekyll-admin/contribute)]
80
+
81
+ <a href="https://opencollective.com/jekyll-admin/organization/0/website"><img src="https://opencollective.com/jekyll-admin/organization/0/avatar.svg"></a>
82
+ <a href="https://opencollective.com/jekyll-admin/organization/1/website"><img src="https://opencollective.com/jekyll-admin/organization/1/avatar.svg"></a>
83
+ <a href="https://opencollective.com/jekyll-admin/organization/2/website"><img src="https://opencollective.com/jekyll-admin/organization/2/avatar.svg"></a>
84
+ <a href="https://opencollective.com/jekyll-admin/organization/3/website"><img src="https://opencollective.com/jekyll-admin/organization/3/avatar.svg"></a>
85
+ <a href="https://opencollective.com/jekyll-admin/organization/4/website"><img src="https://opencollective.com/jekyll-admin/organization/4/avatar.svg"></a>
86
+ <a href="https://opencollective.com/jekyll-admin/organization/5/website"><img src="https://opencollective.com/jekyll-admin/organization/5/avatar.svg"></a>
87
+ <a href="https://opencollective.com/jekyll-admin/organization/6/website"><img src="https://opencollective.com/jekyll-admin/organization/6/avatar.svg"></a>
88
+ <a href="https://opencollective.com/jekyll-admin/organization/7/website"><img src="https://opencollective.com/jekyll-admin/organization/7/avatar.svg"></a>
89
+ <a href="https://opencollective.com/jekyll-admin/organization/8/website"><img src="https://opencollective.com/jekyll-admin/organization/8/avatar.svg"></a>
90
+ <a href="https://opencollective.com/jekyll-admin/organization/9/website"><img src="https://opencollective.com/jekyll-admin/organization/9/avatar.svg"></a>
91
+
48
92
  ## License
49
93
 
50
94
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Default Sinatra to "production" mode (surpress errors) unless
2
4
  # otherwise specified by the `RACK_ENV` environmental variable.
3
5
  # Must be done prior to requiring Sinatra, or we'll get a LoadError
@@ -17,16 +19,15 @@ require "sinatra/reloader"
17
19
  require "sinatra/namespace"
18
20
 
19
21
  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"
22
+ autoload :APIable, "jekyll-admin/apiable"
23
+ autoload :DataFile, "jekyll-admin/data_file"
24
+ autoload :Directory, "jekyll-admin/directory"
25
+ autoload :FileHelper, "jekyll-admin/file_helper"
26
+ autoload :PathHelper, "jekyll-admin/path_helper"
27
+ autoload :Server, "jekyll-admin/server"
28
+ autoload :StaticServer, "jekyll-admin/static_server"
29
+ autoload :URLable, "jekyll-admin/urlable"
30
+ autoload :Version, "jekyll-admin/version"
30
31
 
31
32
  def self.site
32
33
  @site ||= begin
@@ -37,10 +38,10 @@ module JekyllAdmin
37
38
  end
38
39
  end
39
40
 
40
- # Monkey Patches
41
- require_relative "./jekyll/commands/serve"
42
-
43
41
  [Jekyll::Page, Jekyll::Document, Jekyll::StaticFile, Jekyll::Collection].each do |klass|
44
42
  klass.include JekyllAdmin::APIable
45
43
  klass.include JekyllAdmin::URLable
46
44
  end
45
+
46
+ # Monkey Patches
47
+ require_relative "jekyll/commands/serve"
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JekyllAdmin
2
4
  # Abstract module to be included in Convertible and Document to provide
3
5
  # additional, API-specific functionality without duplicating logic
4
6
  module APIable
5
7
  CONTENT_FIELDS = %w(next previous content excerpt).freeze
8
+ API_SCAFFOLD = %w(name path relative_path).map { |i| [i, nil] }.to_h.freeze
6
9
 
7
10
  # Returns a hash suitable for use as an API response.
8
11
  #
@@ -18,15 +21,13 @@ module JekyllAdmin
18
21
  # include_content - if true, includes the content in the respond, false by default
19
22
  # to support mapping on indexes where we only want metadata
20
23
  #
21
- #
22
24
  # Returns a hash (which can then be to_json'd)
23
25
  def to_api(include_content: false)
24
- output = hash_for_api
25
- output = output.merge(url_fields)
26
+ output = API_SCAFFOLD.merge hash_for_api
26
27
 
27
28
  # Include content, if requested, otherwise remove it
28
29
  if include_content
29
- output = output.merge(content_fields)
30
+ output.merge!(content_fields)
30
31
  else
31
32
  CONTENT_FIELDS.each { |field| output.delete(field) }
32
33
  end
@@ -35,24 +36,73 @@ module JekyllAdmin
35
36
  # Since it's an API, use `content` in both for consistency
36
37
  output.delete("output")
37
38
 
39
+ output["name"] = basename if is_a?(Jekyll::Document)
40
+ output["from_theme"] = from_theme_gem? if is_a?(Jekyll::StaticFile)
41
+ output["relative_path"] = relative_path_for_api
42
+
38
43
  # By default, calling to_liquid on a collection will return a docs
39
44
  # array with each rendered document, which we don't want.
40
45
  if is_a?(Jekyll::Collection)
41
46
  output.delete("docs")
47
+ output["name"] = label
48
+ output["path"] = relative_directory
42
49
  output["entries_url"] = entries_url
43
50
  end
44
51
 
45
- if is_a?(Jekyll::Document)
46
- output["relative_path"] = relative_path.sub("_drafts/", "") if draft?
47
- output["name"] = basename
52
+ output.merge!(url_fields)
53
+ output
54
+ end
55
+
56
+ private
57
+
58
+ # Relative path of files and directories with their *special directories*
59
+ # and any leading slashes stripped away.
60
+ #
61
+ # Examples:
62
+ # `_drafts/foo/draft-post.md` => `foo/draft-post.md`
63
+ # `_posts/foo/2019-10-18-hello.md` => `foo/2019-10-18-hello.md`
64
+ # `/assets/img/logo.png` => `assets/img/logo.png`
65
+ def relative_path_for_api
66
+ return unless respond_to?(:relative_path) && relative_path
67
+
68
+ @relative_path_for_api ||= begin
69
+ rel_path = relative_path.dup
70
+ strip_leading_slash!(rel_path)
71
+ strip_leading_special_directory!(rel_path)
72
+ strip_leading_slash!(rel_path)
73
+
74
+ rel_path
48
75
  end
76
+ end
49
77
 
50
- output["from_theme"] = from_theme_gem? if is_a?(Jekyll::StaticFile)
78
+ # Prefer substituting substrings instead of using a regex in order to avoid multiple
79
+ # regex allocations. String literals are frozen and reused.
51
80
 
52
- output
81
+ def strip_leading_slash!(path)
82
+ return unless path.start_with?("/")
83
+
84
+ path.sub!("/", "")
53
85
  end
54
86
 
55
- private
87
+ def strip_leading_special_directory!(path)
88
+ return unless special_directory && path.start_with?(special_directory)
89
+
90
+ path.sub!(special_directory, "")
91
+ end
92
+
93
+ def special_directory
94
+ return @special_directory if defined?(@special_directory)
95
+
96
+ @special_directory = begin
97
+ if is_a?(Jekyll::Document) && draft?
98
+ "_drafts"
99
+ elsif is_a?(Jekyll::Document)
100
+ collection.relative_directory
101
+ elsif is_a?(Jekyll::Collection)
102
+ relative_directory
103
+ end
104
+ end
105
+ end
56
106
 
57
107
  # Pages don't have a hash method, but Documents do
58
108
  def file_path
@@ -84,6 +134,16 @@ module JekyllAdmin
84
134
  Jekyll::Utils.merged_file_read_opts(site, {})
85
135
  end
86
136
 
137
+ def front_matter_defaults
138
+ return unless file_exists?
139
+
140
+ @front_matter_defaults ||= begin
141
+ return {} unless respond_to?(:relative_path) && respond_to?(:type)
142
+
143
+ site.frontmatter_defaults.all(relative_path, type)
144
+ end
145
+ end
146
+
87
147
  def front_matter
88
148
  return unless file_exists?
89
149
 
@@ -137,6 +197,7 @@ module JekyllAdmin
137
197
  else
138
198
  output["raw_content"] = raw_content
139
199
  output["front_matter"] = front_matter
200
+ output["front_matter_defaults"] = front_matter_defaults
140
201
  end
141
202
 
142
203
  # Include next and previous documents non-recursively
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JekyllAdmin
2
4
  class DataFile
3
- METHODS_FOR_LIQUID = %w(path relative_path slug ext title).freeze
5
+ METHODS_FOR_LIQUID = %w(name path relative_path slug ext title).freeze
4
6
  EXTENSIONS = %w(yaml yml json csv).freeze
5
7
 
6
8
  include APIable
@@ -8,16 +10,26 @@ module JekyllAdmin
8
10
  include PathHelper
9
11
  extend PathHelper
10
12
 
11
- attr_reader :id
13
+ attr_reader :id, :ext
12
14
 
13
15
  # Initialize a new DataFile object
14
16
  #
15
17
  # id - the file ID as passed from the API. This may or may not have an extension
16
18
  def initialize(id)
17
- @id = File.extname(id).empty? ? "#{id}.yml" : id
19
+ extname = File.extname(id)
20
+ if extname.empty?
21
+ @id = "#{id}.yml"
22
+ @ext = ".yml"
23
+ else
24
+ @id = id
25
+ @ext = extname
26
+ end
18
27
  end
19
28
  alias_method :relative_path, :id
20
29
 
30
+ # Returns the file's extension with preceeding `.`
31
+ alias_method :extension, :ext
32
+
21
33
  def exists?
22
34
  @exists ||= File.exist?(absolute_path)
23
35
  end
@@ -32,16 +44,6 @@ module JekyllAdmin
32
44
  @content ||= data_reader.read_data_file(absolute_path)
33
45
  end
34
46
 
35
- # Returns the file's extension with preceeding `.`
36
- def ext
37
- @ext ||= if File.extname(id).to_s.empty?
38
- ".yml"
39
- else
40
- File.extname(id)
41
- end
42
- end
43
- alias_method :extension, :ext
44
-
45
47
  # Returns the file's sanitized slug (as used in `site.data[slug]`)
46
48
  def slug
47
49
  @slug ||= data_reader.sanitize_filename(basename)
@@ -54,7 +56,7 @@ module JekyllAdmin
54
56
 
55
57
  # Returns path relative to site source
56
58
  def path
57
- ensure_leading_slash(File.join(DataFile.data_dir, relative_path))
59
+ @path ||= File.join(DataFile.data_dir, relative_path)
58
60
  end
59
61
 
60
62
  def absolute_path
@@ -92,9 +94,10 @@ module JekyllAdmin
92
94
  end
93
95
 
94
96
  def basename_with_extension
95
- [basename, extension].join
97
+ "#{basename}#{extension}"
96
98
  end
97
- alias_method :filename, :basename_with_extension
99
+ alias_method :name, :basename_with_extension
100
+ public :name
98
101
 
99
102
  def namespace
100
103
  "data"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JekyllAdmin
2
4
  class Directory
3
5
  extend Forwardable
@@ -9,45 +11,41 @@ module JekyllAdmin
9
11
  include JekyllAdmin::URLable
10
12
  include JekyllAdmin::APIable
11
13
 
12
- TYPE = :directory
14
+ RESOURCE_TYPES = %w(pages data drafts static_files).freeze
15
+ DOT_DIRECTORIES = [".", ".."].freeze
13
16
 
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`
17
+ private_constant :RESOURCE_TYPES, :DOT_DIRECTORIES
18
+
19
+ # Parameters:
20
+ # path - The full path of the directory at which its entries will be listed.
22
21
  #
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
22
+ # Named parameters:
23
+ # base: - The full path to the directory from source.
24
+ # splat: - The requested directory path relative to content namespace.
25
+ # content_type: - The type of the requested directory entries. Corresponds to
26
+ # the resources' API namespace.
27
+ def initialize(path, base:, splat:, content_type:)
28
+ @path = Pathname.new path
29
+ @base = Pathname.new base
27
30
  @splat = Pathname.new splat
28
- @path = Pathname.new path
31
+ @content_type = content_type
29
32
  end
30
33
 
31
34
  def to_liquid
32
35
  {
33
- :name => name,
34
- :modified_time => modified_time,
35
- :path => relative_path,
36
- :type => TYPE,
36
+ "name" => name,
37
+ "modified_time" => modified_time,
38
+ "path" => relative_path,
39
+ "type" => "directory",
37
40
  }
38
41
  end
39
42
 
40
43
  def relative_path
41
- if content_type == "drafts"
42
- path.relative_path_from(base).to_s.sub("_drafts/", "")
43
- else
44
- path.relative_path_from(base).to_s
45
- end
44
+ @relative_path ||= path.relative_path_from(base).to_s
46
45
  end
47
46
 
48
47
  def resource_path
49
- types = %w(pages data drafts)
50
- if types.include?(content_type)
48
+ if RESOURCE_TYPES.include?(content_type)
51
49
  "/#{content_type}/#{splat}/#{name}"
52
50
  else
53
51
  "/collections/#{content_type}/entries/#{splat}/#{name}"
@@ -61,7 +59,7 @@ module JekyllAdmin
61
59
 
62
60
  def directories
63
61
  path.entries.map do |entry|
64
- next if [".", ".."].include? entry.to_s
62
+ next if DOT_DIRECTORIES.include? entry.to_s
65
63
  next unless path.join(entry).directory?
66
64
 
67
65
  self.class.new(
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JekyllAdmin
2
4
  module FileHelper
3
5
  # The file the user requested in the URL
@@ -50,6 +52,10 @@ module JekyllAdmin
50
52
  ensure_file(written_file)
51
53
  end
52
54
 
55
+ def ensure_not_overwriting_existing_file
56
+ ensure_not_file(written_file)
57
+ end
58
+
53
59
  def find_by_path(path)
54
60
  files = case namespace
55
61
  when "collections"
@@ -70,6 +76,14 @@ module JekyllAdmin
70
76
  render_404 if file.nil?
71
77
  end
72
78
 
79
+ def ensure_not_file(file)
80
+ return if file.nil?
81
+
82
+ Jekyll.logger.warn "Jekyll Admin:", "Could not create file."
83
+ Jekyll.logger.warn "", "Path #{file.relative_path.inspect} already exists!"
84
+ render_404
85
+ end
86
+
73
87
  def ensure_directory
74
88
  render_404 unless Dir.exist?(directory_path)
75
89
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JekyllAdmin
2
4
  module PathHelper
3
5
  def sanitized_path(path)
@@ -49,6 +51,11 @@ module JekyllAdmin
49
51
  ensure_leading_slash(request_payload["path"]) != relative_path
50
52
  end
51
53
 
54
+ # Is this request creating a new file?
55
+ def new?
56
+ !request_payload["path"]
57
+ end
58
+
52
59
  private
53
60
 
54
61
  # Returns the path to the requested file's containing directory
@@ -56,7 +63,7 @@ module JekyllAdmin
56
63
  sanitized_path(
57
64
  case namespace
58
65
  when "collections"
59
- File.join(collection.relative_directory, params["splat"].first)
66
+ File.join(collection.directory, params["splat"].first)
60
67
  when "data"
61
68
  File.join(DataFile.data_dir, params["splat"].first)
62
69
  when "drafts"