wcc-contentful 0.0.3 → 0.1.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +51 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +240 -0
  6. data/.rubocop_todo.yml +13 -0
  7. data/CHANGELOG.md +7 -1
  8. data/Gemfile +4 -2
  9. data/Guardfile +36 -0
  10. data/README.md +1 -1
  11. data/Rakefile +5 -3
  12. data/bin/rspec +3 -0
  13. data/lib/generators/wcc/USAGE +24 -0
  14. data/lib/generators/wcc/menu_generator.rb +67 -0
  15. data/lib/generators/wcc/templates/.keep +0 -0
  16. data/lib/generators/wcc/templates/Procfile +3 -0
  17. data/lib/generators/wcc/templates/contentful_shell_wrapper +342 -0
  18. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +85 -0
  19. data/lib/generators/wcc/templates/menu/menu.rb +25 -0
  20. data/lib/generators/wcc/templates/menu/menu_button.rb +25 -0
  21. data/lib/generators/wcc/templates/release +9 -0
  22. data/lib/generators/wcc/templates/wcc_contentful.rb +18 -0
  23. data/lib/wcc/contentful.rb +93 -26
  24. data/lib/wcc/contentful/client_ext.rb +15 -0
  25. data/lib/wcc/contentful/configuration.rb +93 -0
  26. data/lib/wcc/contentful/content_type_indexer.rb +153 -0
  27. data/lib/wcc/contentful/exceptions.rb +34 -0
  28. data/lib/wcc/contentful/graphql.rb +15 -0
  29. data/lib/wcc/contentful/graphql/builder.rb +172 -0
  30. data/lib/wcc/contentful/graphql/types.rb +54 -0
  31. data/lib/wcc/contentful/helpers.rb +28 -0
  32. data/lib/wcc/contentful/indexed_representation.rb +111 -0
  33. data/lib/wcc/contentful/model.rb +24 -0
  34. data/lib/wcc/contentful/model/menu.rb +7 -0
  35. data/lib/wcc/contentful/model/menu_button.rb +15 -0
  36. data/lib/wcc/contentful/model_builder.rb +151 -0
  37. data/lib/wcc/contentful/model_validators.rb +64 -0
  38. data/lib/wcc/contentful/model_validators/dsl.rb +165 -0
  39. data/lib/wcc/contentful/simple_client.rb +127 -0
  40. data/lib/wcc/contentful/simple_client/response.rb +160 -0
  41. data/lib/wcc/contentful/store.rb +8 -0
  42. data/lib/wcc/contentful/store/cdn_adapter.rb +79 -0
  43. data/lib/wcc/contentful/store/memory_store.rb +75 -0
  44. data/lib/wcc/contentful/store/postgres_store.rb +132 -0
  45. data/lib/wcc/contentful/version.rb +3 -1
  46. data/wcc-contentful.gemspec +49 -24
  47. metadata +261 -16
  48. data/lib/wcc/contentful/redirect.rb +0 -33
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wcc
4
+ class MenuGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('templates', __dir__)
6
+
7
+ def create_menus_migration
8
+ now = Time.now.strftime('%Y%m%d%H%M')
9
+ copy_file 'menu/generated_add_menus.ts',
10
+ "db/migrate/#{now}01_generated_add_menus.ts"
11
+ end
12
+
13
+ def ensure_migration_tools_installed
14
+ in_root do
15
+ run 'npm init -y' unless File.exist?('package.json')
16
+ package = JSON.parse(File.read('package.json'))
17
+ deps = package['dependencies']
18
+
19
+ unless deps.try(:[], 'contentful-migration-cli').present?
20
+ run 'npm install --save watermarkchurch/migration-cli'
21
+ end
22
+ end
23
+ end
24
+
25
+ def ensure_wrapper_script_in_bin_dir
26
+ unless inside('bin') { File.exist?('contentful') }
27
+ copy_file 'contentful_shell_wrapper', 'bin/contentful'
28
+ end
29
+
30
+ if inside('bin') { File.exist?('release') }
31
+ release = inside('bin') { File.read('release') }
32
+ unless release.include?('contentful migrate')
33
+ insert_into_file('bin/release', after: 'bundle exec rake db:migrate') do
34
+ <<~HEREDOC
35
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
36
+ $DIR/contentful migrate -y
37
+ HEREDOC
38
+ end
39
+ end
40
+ else
41
+ copy_file 'release', 'bin/release'
42
+ end
43
+
44
+ if in_root { File.exist?('Procfile') }
45
+ procfile = in_root { File.read('Procfile') }
46
+ unless procfile.include?('release:')
47
+ insert_into_file('Procfile') do
48
+ 'release: bin/release'
49
+ end
50
+ end
51
+ else
52
+ copy_file 'Procfile'
53
+ end
54
+ end
55
+
56
+ def ensure_initializer_exists
57
+ return if inside('config/initializers') { File.exist?('wcc_contentful.rb') }
58
+
59
+ copy_file 'wcc_contentful.rb', 'config/initializers/wcc_contentful.rb'
60
+ end
61
+
62
+ def drop_model_overrides_in_lib_dir
63
+ copy_file 'menu/menu.rb', 'lib/wcc/contentful/model/menu.rb'
64
+ copy_file 'menu/menu_button.rb', 'lib/wcc/contentful/model/menu_button.rb'
65
+ end
66
+ end
67
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ web: bundle exec puma -C config/puma.rb
2
+ worker: bundle exec sidekiq
3
+ release: bin/release
@@ -0,0 +1,342 @@
1
+ #!/bin/bash
2
+
3
+ COLOR_NC='\033[0m' # No Color
4
+ COLOR_GRAY='\033[1;30m'
5
+ COLOR_RED='\033[0;31m'
6
+ COLOR_LCYAN='\033[1;36m'
7
+ COLOR_YELLOW='\033[1;33m'
8
+ COLOR_LGREEN='\033[1;32m'
9
+
10
+ logv() {
11
+ ([[ ! -z "$VERBOSE" ]] && >&2 echo -e "${COLOR_GRAY}$@${COLOR_NC}") || true
12
+ }
13
+
14
+ logerr() {
15
+ >&2 echo -e "${COLOR_RED}$@${COLOR_NC}"
16
+ }
17
+
18
+ ## *** Argument Parsing & validation ***
19
+
20
+ usage() {
21
+ echo "$0 <command> [opts]
22
+ Commands:
23
+ migrate [dir|file]
24
+ runs pending migration files in the given directory
25
+ * [dir|file] optional - Default: db/migrate
26
+
27
+ setup [file]
28
+ initializes a space with bare-minimum schema and seeds
29
+ * [file] optional - default: db/contentful-schema.json
30
+
31
+ backup [file]
32
+ downloads a backup of the current space to the given file
33
+ * [file] optional - default: timestamped file in current directory
34
+
35
+ clean [no-init]
36
+ Deletes all data in a given space and optionally sets it up again
37
+ using 'bin/contentful setup'.
38
+ * [no-init] optional - Skips the 'setup' step at the end.
39
+
40
+ restore [file]
41
+ restores a given backup file into the current space
42
+ * [file] optional - default: the most recent backup file in the current directory
43
+
44
+ restore_from -s [to space ID] <from space ID>
45
+ restores all data from the given space into the current space
46
+ * <space ID> required - the ID of the space to receive data from
47
+ * -s [to space ID] optional - the current working space. Default: \$CONTENTFUL_SPACE_ID
48
+
49
+ generate [name]
50
+ Creates a sample migration in the db/migrate directory
51
+ * [name] optional - default: 'contentful_migration'
52
+
53
+ Flags:" && \
54
+ grep " .)\ #" $0
55
+ echo "
56
+ Examples:" && \
57
+ grep -i "#\ example:" $0 | awk '{$1=""; $2=""; print " "$0}'
58
+ }
59
+
60
+ parse_args() {
61
+ OPTIND=1
62
+ local s=$(echo "$1" | tr '[:upper:]' '[:lower:]')
63
+ case "$s" in
64
+ migrate|setup|backup|export|restore|restore_from|import|generate|clean|help|h|\?)
65
+ export subcommand=$s
66
+ OPTIND=2
67
+ ;;
68
+ esac
69
+
70
+ # Parse flags
71
+ while getopts ":hyvs:a:" arg; do
72
+ case $arg in
73
+ y) # Yes - skip prompts
74
+ export YES="-y"
75
+ ;;
76
+ s) # Contentful Space ID - overrides env var CONTENTFUL_SPACE_ID
77
+ export CONTENTFUL_SPACE_ID=$OPTARG
78
+ ;;
79
+ a) # Contentful Mgmt Token - overrides env var CONTENTFUL_MANAGEMENT_TOKEN
80
+ export CONTENTFUL_MANAGEMENT_TOKEN=$OPTARG
81
+ ;;
82
+ v) # Verbose mode - extra output
83
+ export VERBOSE=true
84
+ ;;
85
+ h) # Display help.
86
+ usage
87
+ exit 0
88
+ ;;
89
+ *)
90
+ logerr "Unknown option: '$OPTARG'"
91
+ usage
92
+ exit -1
93
+ ;;
94
+ esac
95
+ done
96
+
97
+ export OPTIND
98
+ }
99
+
100
+ parse_args $@ && shift $(($OPTIND - 1))
101
+ # If they put args before the command like 'bin/contentful -s 1xab migrate -y', try parsing again
102
+ [[ -z "$subcommand" ]] && parse_args $@ && shift $(($OPTIND - 1))
103
+
104
+ require_environment() {
105
+ [[ -z "$CONTENTFUL_SPACE_ID" ]] && logerr "Please set CONTENTFUL_SPACE_ID environment variable or use '-s' flag." && exit -1;
106
+ [[ -z "$CONTENTFUL_MANAGEMENT_TOKEN" ]] && logerr "Please set CONTENTFUL_MANAGEMENT_TOKEN environment variable or use '-a' flag." && exit -1;
107
+ if [[ ! -f node_modules/.bin/contentful-migration ]]; then
108
+ command -v npm >/dev/null 2>&1 || (logerr "I require 'npm' but it's not installed. Please install nodejs."; exit -1)
109
+ logv "npm install"
110
+ npm install
111
+ [[ -f node_modules/.bin/contentful-migration ]] || (logerr "Failed installing node modules - please ensure contentful CLI is installed"; exit -1)
112
+ fi
113
+ }
114
+
115
+ ## *** Utility functions ***
116
+
117
+ confirm() {
118
+ [[ -z "$2" ]] && [[ ! -z "$YES" ]] && logv "$1 (y/n): confirmed by -y flag" && return 0;
119
+
120
+ while true; do
121
+ if [[ -z "$2" ]]; then
122
+ read -p $'\033[1;36m'"$1"' (y/n): '$'\033[0m' yn
123
+ else
124
+ # double confirm - extra dangerous.
125
+ read -p $'\033[0;31m'"$1"' (y/n): '$'\033[0m' yn
126
+ fi
127
+ case $yn in
128
+ [Yy]* ) return 0;;
129
+ [Nn]* ) return 1;;
130
+ * ) echo "Please answer yes or no.";;
131
+ esac
132
+ done
133
+ }
134
+
135
+ get_space_name() {
136
+ logv "curl -s https://api.contentful.com/spaces/$1?access_token=****"
137
+ curl -s https://api.contentful.com/spaces/$1?access_token=$CONTENTFUL_MANAGEMENT_TOKEN | jq -r .name | tr '[:upper:]' '[:lower:]'
138
+ }
139
+
140
+ set -e
141
+
142
+ # *** Commands ***
143
+
144
+ # Example: bin/contentful migrate -y -s 1xab -a $MY_TOKEN db/migrate/20180101120000_add_content_type_dog.ts
145
+ # equivalent to: bin/rake db:migrate
146
+ migrate() {
147
+ ARG="$1"
148
+ [[ -z "$ARG" ]] && ARG="db/migrate"
149
+ [[ -d "$ARG" ]] && ARG="batch $ARG"
150
+
151
+ require_environment
152
+
153
+ logv "contentful-migration -s $CONTENTFUL_SPACE_ID -a ***** $YES -p $ARG"
154
+ node_modules/.bin/ts-node node_modules/.bin/contentful-migration \
155
+ -s $CONTENTFUL_SPACE_ID -a $CONTENTFUL_MANAGEMENT_TOKEN \
156
+ $YES -p $ARG
157
+
158
+ mkdir -p db
159
+ logv "contentful-export -s --export-dir db --content-file contentful-schema.json --space-id $CONTENTFUL_SPACE_ID --management-token ***** --query-entries 'content_type=migrationHistory' --query-assets 'sys.id=false'"
160
+ node_modules/.bin/contentful-export --export-dir db --content-file contentful-schema.json \
161
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
162
+ --query-entries 'content_type=migrationHistory' \
163
+ --query-assets 'sys.id=false'
164
+
165
+ if [[ $(git diff-index --name-only HEAD | grep 'db/contentful-schema.json') == "" ]]; then
166
+ echo -e "${COLOR_LGREEN}✓ Schema in contentful space is equivalent to stored schema${COLOR_NC}"
167
+ else
168
+ echo -e "${COLOR_YELLOW}⚠️ Schema changed after running migrations${COLOR_NC}"
169
+ fi
170
+ }
171
+
172
+ # Example: bin/contentful backup -s 1xab -a $MY_TOKEN 2018_01_01.1xab.dump.json
173
+ # equivalent to: bin/rake db:dump[2018_01_01.dump]
174
+ backup() {
175
+ FILE="$1"
176
+ [[ ! -z "$FILE" ]] && FILE="--content-file $FILE" && shift
177
+
178
+ require_environment
179
+
180
+ logv "contentful-export $FILE --space-id $CONTENTFUL_SPACE_ID --management-token ***** $@"
181
+ node_modules/.bin/contentful-export $FILE \
182
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
183
+ $@
184
+ }
185
+
186
+ # Example: bin/contentful restore -y -s 1xab -a $MY_TOKEN 2018_01_01.1xab.dump.json
187
+ # equivalent to: bin/rake db:restore[2018_01_01.dump]
188
+ restore() {
189
+ FILE="$1"
190
+ if [[ -z "$FILE" ]]; then
191
+ FILE=$(ls contentful-export-$CONTENTFUL_SPACE_ID-* | sort -r | head -n 1)
192
+ [[ -z "$FILE" ]] && logerr "No file given on command line" && exit -1
193
+ fi
194
+
195
+ confirm "Import $FILE into $CONTENTFUL_SPACE_ID?" || exit -1
196
+
197
+ require_environment
198
+
199
+ logv "contentful-import --space-id $CONTENTFUL_SPACE_ID --management-token ***** --content-file $FILE"
200
+ node_modules/.bin/contentful-import \
201
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
202
+ --content-file $FILE
203
+ }
204
+
205
+ # Example: bin/contentful setup -y -s 1xab -a $MY_TOKEN db/my-schema.json
206
+ # equivalent to: bin/rake db:setup
207
+ setup() {
208
+ FILE="$1"
209
+ [[ -z "$FILE" ]] && FILE=db/contentful-schema.json
210
+
211
+ confirm "Initialize space $CONTENTFUL_SPACE_ID from seed file $FILE?" || exit -1
212
+
213
+ require_environment
214
+
215
+ logv "contentful-import --space-id $CONTENTFUL_SPACE_ID --management-token ***** --content-file $FILE"
216
+ node_modules/.bin/contentful-import \
217
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
218
+ --content-file $FILE
219
+
220
+ migrate
221
+ }
222
+
223
+ # Example: bin/contentful clean -s 1xab -a $MY_TOKEN
224
+ clean() {
225
+ command -v jq >/dev/null 2>&1 || (logerr "I require 'jq' but it's not installed. Please run 'brew install jq'"; exit -1)
226
+
227
+ require_environment
228
+
229
+ name=$(get_space_name $CONTENTFUL_SPACE_ID)
230
+
231
+ confirm "This will delete all data and content types from $name. Are you sure?" || exit -1
232
+ [[ "$name" != *staging ]] && [[ "$name" != *test ]] && confirm "$name is not a staging or test environment! Are you really sure?" dangerous!
233
+
234
+ local bkup_file="contentful-export-$CONTENTFUL_SPACE_ID-`date +"%Y-%m-%dT%H-%M-%S"`.json"
235
+ backup $bkup_file
236
+ content_types=$(cat $bkup_file | jq -r '.contentTypes[].sys.id')
237
+ entries=$(cat $bkup_file | jq -r '.entries[].sys.id')
238
+ assets=$(cat $bkup_file | jq -r '.assets[].sys.id')
239
+
240
+ delete() {
241
+ logv "curl -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2/published\?access_token\=*****"
242
+ curl -s --fail -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2/published\?access_token\=$CONTENTFUL_MANAGEMENT_TOKEN > /dev/null
243
+ logv "curl -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2\?access_token\=*****"
244
+ curl -s --fail -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2\?access_token\=$CONTENTFUL_MANAGEMENT_TOKEN > /dev/null
245
+ }
246
+
247
+ [[ ! -z "$assets" ]] && while read -r id; do delete 'assets' $id; done <<< "${assets}"
248
+ [[ ! -z "$entries" ]] && while read -r id; do delete 'entries' $id; done <<< "${entries}"
249
+ [[ ! -z "$content_types" ]] && while read -r id; do delete 'content_types' $id; done <<< "${content_types}"
250
+
251
+ echo -e "${COLOR_LGREEN}All content deleted - prior content saved at${COLOR_NC} $bkup_file"
252
+ [[ "$1" == "no-init" ]] || (setup || logerr "Error setting up space $name!")
253
+ }
254
+
255
+ # Example: bin/contentful restore_from -s $staging_space_id -a $MY_TOKEN $production_space_id
256
+ restore_from() {
257
+ command -v jq >/dev/null 2>&1 || (logerr "I require 'jq' but it's not installed. Please run 'brew install jq'"; exit -1)
258
+
259
+ require_environment
260
+
261
+ from_space_id=$1
262
+ [[ -z "$from_space_id" ]] && logerr "Please provide the space ID from which to restore data" && exit -1
263
+ to_space_id=$CONTENTFUL_SPACE_ID
264
+
265
+ from_name=$(get_space_name $from_space_id)
266
+ to_name=$(get_space_name $CONTENTFUL_SPACE_ID)
267
+
268
+ echo -e "${COLOR_LCYAN}This will backup all data from $from_name, delete all data in $to_name, and then write the $from_name data over it.${COLOR_NC}"
269
+ confirm "Continue?" || exit -1
270
+ export YES='-y' # don't keep bugging the user
271
+
272
+ export CONTENTFUL_SPACE_ID=$from_space_id
273
+ local bkup_file="contentful-export-$from_space_id-`date +"%Y-%m-%dT%H-%M-%S"`.json"
274
+ logv "bin/contentful backup -s $CONTENTFUL_SPACE_ID $bkup_file"
275
+ backup $bkup_file --skip-roles --skip-webhooks
276
+
277
+ export CONTENTFUL_SPACE_ID=$to_space_id
278
+ logv "bin/contentful clean -s $CONTENTFUL_SPACE_ID"
279
+ clean no-init
280
+
281
+ logv "bin/contentful restore -s $CONTENTFUL_SPACE_ID $bkup_file"
282
+ restore $bkup_file
283
+ }
284
+
285
+ # Example: bin/contentful generate add content type dog
286
+ # equivalent to: bin/rails generate migration add_content_type_dog
287
+ generate() {
288
+ migration="
289
+ import Migration from 'contentful-migration-cli'
290
+
291
+ export = function (migration: Migration) {
292
+ const dog = migration.createContentType('dog', {
293
+ name: 'Dog'
294
+ })
295
+
296
+ const name = dog.createField('name')
297
+ name.name('Name')
298
+ .type('Symbol')
299
+ .required(true)
300
+ }
301
+ "
302
+
303
+ timestamp=$(date +%Y%m%d%H%M%S)
304
+ filename="$@"
305
+ [[ -z "$filename" ]] && filename="contentful_migration"
306
+ filename=${filename// /\_}
307
+ filename="db/migrate/${timestamp}_${filename}.ts"
308
+ echo "$migration" > $filename
309
+ echo "generated file $filename"
310
+ }
311
+
312
+ case $subcommand in
313
+ migrate)
314
+ migrate $@
315
+ ;;
316
+ backup|export)
317
+ backup $@
318
+ ;;
319
+ restore|import)
320
+ restore $@
321
+ ;;
322
+ setup)
323
+ setup $@
324
+ ;;
325
+ generate)
326
+ generate $@
327
+ ;;
328
+ clean)
329
+ clean $@
330
+ ;;
331
+ restore_from)
332
+ restore_from $@
333
+ ;;
334
+ help|h|\?)
335
+ usage
336
+ ;;
337
+ *)
338
+ logerr "Unknown command: '$1'"
339
+ usage
340
+ exit -1
341
+ ;;
342
+ esac
@@ -0,0 +1,85 @@
1
+
2
+ import Migration from 'contentful-migration-cli'
3
+
4
+ export = function (migration: Migration) {
5
+ const menu = migration.createContentType('menu')
6
+ .name('Menu')
7
+ .description('A Menu contains a number of Menu Buttons or other Menus, which ' +
8
+ 'will be rendered as drop-downs.')
9
+ .displayField('name')
10
+
11
+ menu.createField('name')
12
+ .name('Menu Name')
13
+ .type('Symbol')
14
+ .required(true)
15
+
16
+ menu.createField('topButton')
17
+ .name('Top Button')
18
+ .type('Link')
19
+ .linkType('Entry')
20
+ .validations([
21
+ {
22
+ linkContentType: [ 'menuButton' ],
23
+ message: 'The Top Button must be a button linking to a URL or page. ' +
24
+ 'If the menu is a dropdown, this button is visible when it is collapsed.'
25
+ }
26
+ ])
27
+
28
+ menu.createField('items')
29
+ .name('Items')
30
+ .type('Array')
31
+ .items({
32
+ type: 'Link',
33
+ linkType: 'Entry',
34
+ validations: [
35
+ {
36
+ linkContentType: [ 'menu', 'menuButton' ],
37
+ message: 'The items must be either buttons or drop-down menus'
38
+ }
39
+ ]
40
+ })
41
+
42
+ const menuButton = migration.createContentType('menuButton')
43
+ .name('Menu Button')
44
+ .description('A Menu Button is a clickable button that goes on a Menu. ' +
45
+ 'It has a link to a Page or a URL.')
46
+ .displayField('text')
47
+
48
+ menuButton.createField('text')
49
+ .name('Text')
50
+ .type('Symbol')
51
+ .required(true)
52
+ .validations([
53
+ {
54
+ size: { min: 1, max: 60 },
55
+ message: 'A Menu Button should have a very short text field - ideally a ' +
56
+ 'single word. Please limit the text to 60 characters.'
57
+ }
58
+ ])
59
+
60
+ menuButton.createField('icon')
61
+ .name('Icon')
62
+ .type('Link')
63
+ .linkType('Asset')
64
+
65
+ menuButton.createField('externalLink')
66
+ .name('External Link')
67
+ .type('Symbol')
68
+ .validations([
69
+ {
70
+ regexp: { pattern: "^(ftp|http|https):\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?$" },
71
+ message: "The external link must be a URL like 'https://www.watermark.org/'"
72
+ }
73
+ ])
74
+
75
+ menuButton.createField('link')
76
+ .name('Page Link')
77
+ .type('Link')
78
+ .linkType('Entry')
79
+ .validations([
80
+ {
81
+ linkContentType: [ 'page' ],
82
+ message: 'The Page Link must be a link to a Page which has a slug.'
83
+ }
84
+ ])
85
+ }