ghost 4.22.1 → 4.23.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 (128) hide show
  1. package/.c8rc.json +24 -0
  2. package/.eslintrc.js +6 -0
  3. package/Gruntfile.js +1 -1
  4. package/content/public/README.md +3 -0
  5. package/core/boot.js +20 -12
  6. package/core/built/assets/{chunk.3.1148677ff3b78e5aeaee.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
  7. package/core/built/assets/{ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css → ghost-dark-42cf6e0c730578940ec069bda45aea41.css} +1 -1
  8. package/core/built/assets/{ghost.min-f7037eca328f4d4eb99f0309c19c9bae.js → ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js} +189 -143
  9. package/core/built/assets/{ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css → ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css} +1 -1
  10. package/core/built/assets/icons/powered-by-tenor.svg +35 -0
  11. package/core/built/assets/icons/tenor.svg +7 -0
  12. package/core/built/assets/{vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js → vendor.min-c9002845b6c30ac978abdadde9f33d7c.js} +8189 -7601
  13. package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
  14. package/core/frontend/apps/private-blogging/lib/router.js +1 -1
  15. package/core/frontend/services/card-assets/service.js +21 -13
  16. package/core/frontend/services/routing/CollectionRouter.js +4 -5
  17. package/core/frontend/services/routing/EmailRouter.js +1 -1
  18. package/core/frontend/services/routing/ParentRouter.js +0 -8
  19. package/core/frontend/services/routing/PreviewRouter.js +1 -1
  20. package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
  21. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
  22. package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
  23. package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
  24. package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
  25. package/core/frontend/services/routing/router-manager.js +7 -2
  26. package/core/frontend/services/rss/generate-feed.js +2 -1
  27. package/core/frontend/src/cards/css/bookmark.css +66 -48
  28. package/core/frontend/src/cards/css/button.css +30 -0
  29. package/core/frontend/src/cards/css/callout.css +50 -0
  30. package/core/frontend/src/cards/css/gallery.css +8 -13
  31. package/core/frontend/src/cards/css/nft.css +94 -0
  32. package/core/frontend/src/cards/css/toggle.css +47 -0
  33. package/core/frontend/src/cards/js/toggle.js +16 -0
  34. package/core/frontend/web/middleware/serve-public-file.js +14 -8
  35. package/core/frontend/web/routes.js +0 -1
  36. package/core/frontend/web/site.js +15 -12
  37. package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
  38. package/core/server/adapters/storage/LocalImagesStorage.js +1 -0
  39. package/core/server/adapters/storage/LocalMediaStorage.js +2 -1
  40. package/core/server/adapters/storage/LocalStorageBase.js +30 -5
  41. package/core/server/api/canary/authentication.js +1 -1
  42. package/core/server/api/canary/files.js +19 -0
  43. package/core/server/api/canary/index.js +4 -0
  44. package/core/server/api/canary/media.js +25 -5
  45. package/core/server/api/canary/oembed.js +3 -0
  46. package/core/server/api/canary/utils/serializers/input/index.js +4 -0
  47. package/core/server/api/canary/utils/serializers/input/media.js +8 -0
  48. package/core/server/api/canary/utils/serializers/output/config.js +21 -14
  49. package/core/server/api/canary/utils/serializers/output/files.js +27 -0
  50. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  51. package/core/server/api/canary/utils/serializers/output/media.js +9 -0
  52. package/core/server/api/canary/utils/validators/input/files.js +7 -0
  53. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  54. package/core/server/api/canary/utils/validators/input/media.js +4 -0
  55. package/core/server/api/v2/authentication.js +1 -1
  56. package/core/server/api/v3/authentication.js +1 -1
  57. package/core/server/data/db/connection.js +7 -0
  58. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  59. package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
  60. package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
  61. package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
  62. package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
  63. package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
  64. package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
  65. package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
  66. package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
  67. package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
  68. package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
  69. package/core/server/data/schema/fixtures/index.js +8 -2
  70. package/core/server/services/mega/post-email-serializer.js +5 -1
  71. package/core/server/services/mega/segment-parser.js +1 -2
  72. package/core/server/services/mega/template.js +69 -1
  73. package/core/server/services/nft-oembed.js +57 -0
  74. package/core/server/services/oembed.js +161 -126
  75. package/core/server/services/public-config/config.js +2 -1
  76. package/core/server/services/stripe/index.js +4 -2
  77. package/core/server/services/url/Resource.js +1 -1
  78. package/core/server/services/url/Resources.js +36 -23
  79. package/core/server/services/url/UrlGenerator.js +23 -20
  80. package/core/server/services/url/UrlService.js +123 -21
  81. package/core/server/services/url/Urls.js +7 -2
  82. package/core/server/services/url/index.js +9 -1
  83. package/core/server/web/admin/app.js +6 -6
  84. package/core/server/web/admin/views/default-prod.html +4 -4
  85. package/core/server/web/admin/views/default.html +4 -4
  86. package/core/server/web/api/app.js +1 -1
  87. package/core/server/web/api/canary/admin/app.js +4 -4
  88. package/core/server/web/api/canary/admin/middleware.js +6 -6
  89. package/core/server/web/api/canary/admin/routes.js +20 -5
  90. package/core/server/web/api/canary/content/app.js +4 -4
  91. package/core/server/web/api/canary/content/middleware.js +3 -3
  92. package/core/server/web/api/middleware/cors.js +7 -7
  93. package/core/server/web/api/v2/admin/app.js +4 -4
  94. package/core/server/web/api/v2/admin/middleware.js +6 -6
  95. package/core/server/web/api/v2/admin/routes.js +5 -5
  96. package/core/server/web/api/v2/content/app.js +4 -4
  97. package/core/server/web/api/v2/content/middleware.js +3 -3
  98. package/core/server/web/api/v3/admin/app.js +4 -4
  99. package/core/server/web/api/v3/admin/middleware.js +6 -6
  100. package/core/server/web/api/v3/admin/routes.js +5 -5
  101. package/core/server/web/api/v3/content/app.js +4 -4
  102. package/core/server/web/api/v3/content/middleware.js +3 -3
  103. package/core/server/web/members/app.js +7 -7
  104. package/core/server/web/oauth/app.js +1 -1
  105. package/core/server/web/parent/app.js +2 -3
  106. package/core/server/web/parent/frontend.js +1 -1
  107. package/core/server/web/shared/index.js +2 -2
  108. package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
  109. package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
  110. package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
  111. package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
  112. package/core/server/web/shared/{middlewares → middleware}/error-handler.js +0 -0
  113. package/core/server/web/shared/{middlewares → middleware}/index.js +0 -0
  114. package/core/server/web/shared/{middlewares → middleware}/maintenance.js +0 -0
  115. package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
  116. package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
  117. package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
  118. package/core/shared/config/defaults.json +10 -2
  119. package/core/shared/config/helpers.js +44 -0
  120. package/core/shared/config/loader.js +1 -1
  121. package/core/shared/config/overrides.json +2 -2
  122. package/core/shared/labs.js +8 -1
  123. package/loggingrc.js +19 -20
  124. package/package.json +35 -35
  125. package/urls.json +597 -0
  126. package/yarn.lock +655 -339
  127. package/core/server/data/schema/fixtures/utils.js +0 -321
  128. package/core/server/web/parent/vhost-utils.js +0 -39
@@ -457,6 +457,110 @@
457
457
  margin: 0 .5em;
458
458
  }
459
459
 
460
+ .kg-nft-card-container {
461
+ position: relative;
462
+ display: flex;
463
+ flex: auto;
464
+ flex-direction: column;
465
+ text-decoration: none;
466
+ font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;
467
+ font-size: 1.4rem;
468
+ font-weight: 400;
469
+ box-shadow: 0 2px 6px -2px rgb(0 0 0 / 10%), 0 0 1px rgb(0 0 0 / 40%);
470
+ width: 100%;
471
+ max-width: 512px;
472
+ color: #15212A;
473
+ background: #fff;
474
+ border-radius: 5px;
475
+ transition: none;
476
+ margin: 0 auto;
477
+ }
478
+
479
+ .kg-nft-metadata {
480
+ padding: 2.0rem;
481
+ }
482
+
483
+ .kg-nft-image-container {
484
+ position: relative;
485
+ }
486
+
487
+ .kg-nft-image {
488
+ display: flex;
489
+ border-radius: 5px 5px 0 0;
490
+ }
491
+
492
+ .kg-nft-header {
493
+ display: flex;
494
+ justify-content: space-between;
495
+ align-items: flex-start;
496
+ gap: 20px;
497
+ }
498
+
499
+ .kg-nft-header h4.kg-nft-title {
500
+ font-size: 1.9rem;
501
+ font-weight: 700;
502
+ margin: 0;
503
+ color: #15212A;
504
+ }
505
+
506
+ .kg-nft-header amp-img {
507
+ max-width: 114px;
508
+ max-height: 26px;
509
+ }
510
+
511
+ .kg-nft-opensea-logo {
512
+ margin-top: 2px;
513
+ width: 100px;
514
+ }
515
+
516
+ .kg-nft-creator {
517
+ font-family: inherit;
518
+ color: #95A1AD;
519
+ }
520
+
521
+ .kg-nft-creator span {
522
+ font-weight: 500;
523
+ color: #15212A;
524
+ }
525
+
526
+ .kg-nft-card p.kg-nft-description {
527
+ font-size: 1.4rem;
528
+ line-height: 1.4em;
529
+ margin: 2.0rem 0 0;
530
+ color: #222;
531
+ }
532
+
533
+ .kg-button-card {
534
+ display: flex;
535
+ position: static;
536
+ align-items: center;
537
+ width: 100%;
538
+ justify-content: center;
539
+ }
540
+
541
+ .kg-btn {
542
+ display: flex;
543
+ position: static;
544
+ align-items: center;
545
+ padding: 0 2.0rem;
546
+ height: 4.0rem;
547
+ line-height: 4.0rem;
548
+ font-size: 1.65rem;
549
+ font-weight: 600;
550
+ text-decoration: none;
551
+ border-radius: 5px;
552
+ transition: opacity 0.2s ease-in-out;
553
+ }
554
+
555
+ .kg-btn:hover {
556
+ opacity: 0.85;
557
+ }
558
+
559
+ .kg-btn-accent {
560
+ background-color: var(--ghost-accent-color, #1292EE);
561
+ color: #fff;
562
+ }
563
+
460
564
  .kg-width-full.kg-card-hascaption {
461
565
  display: grid;
462
566
  grid-template-columns: inherit;
@@ -36,7 +36,7 @@ privateRouter
36
36
  .post(
37
37
  bodyParser.urlencoded({extended: true}),
38
38
  middleware.redirectPrivateToHomeIfLoggedIn,
39
- web.shared.middlewares.brute.privateBlog,
39
+ web.shared.middleware.brute.privateBlog,
40
40
  middleware.doLoginToPrivateSite,
41
41
  _renderer
42
42
  );
@@ -2,12 +2,13 @@ const Minifier = require('@tryghost/minifier');
2
2
  const _ = require('lodash');
3
3
  const path = require('path');
4
4
  const fs = require('fs').promises;
5
+ const logging = require('@tryghost/logging');
6
+ const config = require('../../../shared/config');
5
7
 
6
8
  class CardAssetService {
7
9
  constructor(options = {}) {
8
- // @TODO: use our config paths concept
9
- this.src = options.src || path.join(__dirname, '../../src/cards');
10
- this.dest = options.dest || path.join(__dirname, '../../public');
10
+ this.src = options.src || path.join(config.get('paths').assetSrc, 'cards');
11
+ this.dest = options.dest || config.getContentPath('public');
11
12
  this.minifier = new Minifier({src: this.src, dest: this.dest});
12
13
 
13
14
  if ('config' in options) {
@@ -49,7 +50,14 @@ class CardAssetService {
49
50
  }
50
51
 
51
52
  async minify(globs) {
52
- return await this.minifier.minify(globs);
53
+ try {
54
+ return await this.minifier.minify(globs);
55
+ } catch (err) {
56
+ // @TODO: Convert this back to a proper error once the underlying bug is fixed
57
+ if (err.code === 'EACCES') {
58
+ logging.warn('Ghost was not able to write card asset files due to permissions.');
59
+ }
60
+ }
53
61
  }
54
62
 
55
63
  async clearFiles() {
@@ -59,8 +67,8 @@ class CardAssetService {
59
67
  try {
60
68
  await fs.unlink(path.join(this.dest, 'cards.min.css'));
61
69
  } catch (error) {
62
- // Don't worry if the file didn't exist
63
- if (error.code !== 'ENOENT') {
70
+ // Don't worry if the file didn't exist or we don't have perms here
71
+ if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
64
72
  throw error;
65
73
  }
66
74
  }
@@ -68,8 +76,8 @@ class CardAssetService {
68
76
  try {
69
77
  await fs.unlink(path.join(this.dest, 'cards.min.js'));
70
78
  } catch (error) {
71
- // Don't worry if the file didn't exist
72
- if (error.code !== 'ENOENT') {
79
+ // Don't worry if the file didn't exist or we don't have perms here
80
+ if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
73
81
  throw error;
74
82
  }
75
83
  }
@@ -82,19 +90,19 @@ class CardAssetService {
82
90
  /**
83
91
  * A theme can declare which cards it supports, and we'll do the rest
84
92
  *
85
- * @param {Array|boolean} config
93
+ * @param {Array|boolean} cardAssetConfig
86
94
  * @returns
87
95
  */
88
- async load(config) {
89
- if (config) {
90
- this.config = config;
96
+ async load(cardAssetConfig) {
97
+ if (cardAssetConfig) {
98
+ this.config = cardAssetConfig;
91
99
  }
92
100
 
93
101
  await this.clearFiles();
94
102
 
95
103
  const globs = this.generateGlobs();
96
104
 
97
- this.files = await this.minify(globs);
105
+ this.files = await this.minify(globs) || [];
98
106
  }
99
107
  }
100
108
 
@@ -3,7 +3,7 @@ const urlUtils = require('../../../shared/url-utils');
3
3
  const ParentRouter = require('./ParentRouter');
4
4
 
5
5
  const controllers = require('./controllers');
6
- const middlewares = require('./middlewares');
6
+ const middleware = require('./middleware');
7
7
  const RSSRouter = require('./RSSRouter');
8
8
 
9
9
  /**
@@ -27,7 +27,6 @@ class CollectionRouter extends ParentRouter {
27
27
  this.rss = object.rss !== false;
28
28
 
29
29
  this.permalinks = {
30
- originalValue: object.permalink,
31
30
  value: object.permalink
32
31
  };
33
32
 
@@ -71,7 +70,7 @@ class CollectionRouter extends ParentRouter {
71
70
  this.mountRoute(this.route.value, controllers.collection);
72
71
 
73
72
  // REGISTER: enable pagination by default
74
- this.router().param('page', middlewares.pageParam);
73
+ this.router().param('page', middleware.pageParam);
75
74
  this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers.collection);
76
75
 
77
76
  // REGISTER: is rss enabled?
@@ -93,7 +92,7 @@ class CollectionRouter extends ParentRouter {
93
92
  }
94
93
 
95
94
  /**
96
- * @description Prepare index context for further middlewares/controllers.
95
+ * @description Prepare index context for further middleware/controllers.
97
96
  */
98
97
  _prepareEntriesContext(req, res, next) {
99
98
  res.routerOptions = {
@@ -116,7 +115,7 @@ class CollectionRouter extends ParentRouter {
116
115
  }
117
116
 
118
117
  /**
119
- * @description Prepare entry context for further middlewares/controllers.
118
+ * @description Prepare entry context for further middleware/controllers.
120
119
  */
121
120
  _prepareEntryContext(req, res, next) {
122
121
  res.routerOptions.context = ['post'];
@@ -30,7 +30,7 @@ class EmailRouter extends ParentRouter {
30
30
  }
31
31
 
32
32
  /**
33
- * @description Prepare context for further middlewares/controllers.
33
+ * @description Prepare context for further middleware/controllers.
34
34
  * @param {Object} req
35
35
  * @param {Object} res
36
36
  * @param {Function} next
@@ -180,14 +180,6 @@ class ParentRouter {
180
180
  return this.permalinks;
181
181
  }
182
182
 
183
- /**
184
- * @description Get configured filter of this router.
185
- * @returns {String}
186
- */
187
- getFilter() {
188
- return this.filter;
189
- }
190
-
191
183
  /**
192
184
  * @description Get main route of this router.
193
185
  *
@@ -30,7 +30,7 @@ class PreviewRouter extends ParentRouter {
30
30
  }
31
31
 
32
32
  /**
33
- * @description Prepare context for further middlewares/controllers.
33
+ * @description Prepare context for further middleware/controllers.
34
34
  * @param {Object} req
35
35
  * @param {Object} res
36
36
  * @param {Function} next
@@ -52,7 +52,7 @@ class StaticPagesRouter extends ParentRouter {
52
52
  }
53
53
 
54
54
  /**
55
- * @description Prepare context for futher middlewares/controllers.
55
+ * @description Prepare context for futher middleware/controllers.
56
56
  * @param {Object} req
57
57
  * @param {Object} res
58
58
  * @param {Function} next
@@ -3,7 +3,7 @@ const errors = require('@tryghost/errors');
3
3
  const urlUtils = require('../../../shared/url-utils');
4
4
  const RSSRouter = require('./RSSRouter');
5
5
  const controllers = require('./controllers');
6
- const middlewares = require('./middlewares');
6
+ const middleware = require('./middleware');
7
7
  const ParentRouter = require('./ParentRouter');
8
8
 
9
9
  /**
@@ -59,14 +59,14 @@ class StaticRoutesRouter extends ParentRouter {
59
59
  this.mountRoute(this.route.value, controllers[this.controller]);
60
60
 
61
61
  // REGISTER: pagination
62
- this.router().param('page', middlewares.pageParam);
62
+ this.router().param('page', middleware.pageParam);
63
63
  this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers[this.controller]);
64
64
 
65
65
  this.routerCreated(this);
66
66
  }
67
67
 
68
68
  /**
69
- * @description Prepare channel context for further middlewares/controllers.
69
+ * @description Prepare channel context for further middleware/controllers.
70
70
  * @param {Object} req
71
71
  * @param {Object} res
72
72
  * @param {Function} next
@@ -102,7 +102,7 @@ class StaticRoutesRouter extends ParentRouter {
102
102
  }
103
103
 
104
104
  /**
105
- * @description Prepare static route context for further middlewares/controllers.
105
+ * @description Prepare static route context for further middleware/controllers.
106
106
  * @param {Object} req
107
107
  * @param {Object} res
108
108
  * @param {Function} next
@@ -4,7 +4,7 @@ const ParentRouter = require('./ParentRouter');
4
4
  const RSSRouter = require('./RSSRouter');
5
5
  const urlUtils = require('../../../shared/url-utils');
6
6
  const controllers = require('./controllers');
7
- const middlewares = require('./middlewares');
7
+ const middleware = require('./middleware');
8
8
 
9
9
  /**
10
10
  * @description Taxonomies are groupings of posts based on a common relation.
@@ -51,7 +51,7 @@ class TaxonomyRouter extends ParentRouter {
51
51
  this.mountRoute(this.permalinks.getValue(), controllers.channel);
52
52
 
53
53
  // REGISTER: enable pagination for each taxonomy by default
54
- this.router().param('page', middlewares.pageParam);
54
+ this.router().param('page', middleware.pageParam);
55
55
  this.mountRoute(urlUtils.urlJoin(this.permalinks.value, 'page', ':page(\\d+)'), controllers.channel);
56
56
 
57
57
  // REGISTER: edit redirect to admin client e.g. /tag/:slug/edit
@@ -63,7 +63,7 @@ class TaxonomyRouter extends ParentRouter {
63
63
  }
64
64
 
65
65
  /**
66
- * @description Prepare context for routing middlewares/controllers.
66
+ * @description Prepare context for routing middleware/controllers.
67
67
  * @param {Object} req
68
68
  * @param {Object} res
69
69
  * @param {Function} next
@@ -43,7 +43,12 @@ class RouterManager {
43
43
  return;
44
44
  }
45
45
 
46
- this.urlService.onRouterAddedType(router);
46
+ this.urlService.onRouterAddedType(
47
+ router.identifier,
48
+ router.filter,
49
+ router.getResourceType(),
50
+ router.getPermalinks().getValue()
51
+ );
47
52
  }
48
53
 
49
54
  /**
@@ -179,7 +184,7 @@ class RouterManager {
179
184
  if (collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
180
185
  debug('handleTimezoneEdit: trigger regeneration');
181
186
 
182
- this.urlService.onRouterUpdated(collectionRouter);
187
+ this.urlService.onRouterUpdated(collectionRouter.identifier);
183
188
  }
184
189
  }
185
190
  }
@@ -1,6 +1,5 @@
1
1
  const downsize = require('downsize');
2
2
  const Promise = require('bluebird');
3
- const cheerio = require('cheerio');
4
3
  const RSS = require('rss');
5
4
  const urlUtils = require('../../../shared/url-utils');
6
5
  const {routerManager} = require('../routing');
@@ -19,6 +18,8 @@ const generateTags = function generateTags(data) {
19
18
  };
20
19
 
21
20
  const generateItem = function generateItem(post, secure) {
21
+ const cheerio = require('cheerio');
22
+
22
23
  const itemUrl = routerManager.getUrlByResourceId(post.id, {secure, absolute: true});
23
24
  const htmlContent = cheerio.load(post.html || '');
24
25
  const item = {
@@ -1,83 +1,101 @@
1
- /* style.css */
2
-
3
- .kg-bookmark-card {
4
- width: 100%;
1
+ .kg-bookmark-card,
2
+ .kg-bookmark-publisher {
5
3
  position: relative;
4
+ width: 100%;
6
5
  }
7
6
 
8
- .kg-bookmark-container {
9
- display: flex;
10
- flex-wrap: wrap;
11
- flex-direction: row-reverse;
12
- color: currentColor;
13
- font-family: inherit;
14
- text-decoration: none;
15
- border: 1px solid rgba(0, 0, 0, 0.1);
16
- }
17
-
7
+ .kg-bookmark-container,
18
8
  .kg-bookmark-container:hover {
9
+ display: flex;
19
10
  text-decoration: none;
11
+ border-radius: 4px;
12
+ box-shadow: inset 0 0 0 1px rgb(124 139 154 / 25%);
13
+ overflow: hidden;
20
14
  }
21
15
 
22
16
  .kg-bookmark-content {
23
- flex-basis: 0;
24
- flex-grow: 999;
17
+ display: flex;
18
+ flex-direction: column;
19
+ flex-grow: 1;
20
+ flex-basis: 100%;
21
+ align-items: flex-start;
22
+ justify-content: flex-start;
25
23
  padding: 20px;
26
- order: 1;
27
24
  }
28
25
 
29
26
  .kg-bookmark-title {
27
+ font-size: 1.5rem;
28
+ line-height: 1.4em;
30
29
  font-weight: 600;
31
30
  }
32
31
 
33
- .kg-bookmark-metadata,
34
32
  .kg-bookmark-description {
35
- margin-top: .5em;
33
+ display: -webkit-box;
34
+ font-size: 1.4rem;
35
+ line-height: 1.5em;
36
+ margin-top: 3px;
37
+ font-weight: 400;
38
+ max-height: 44px;
39
+ overflow-y: hidden;
40
+ opacity: 0.7;
41
+ -webkit-line-clamp: 2;
42
+ -webkit-box-orient: vertical;
36
43
  }
37
44
 
38
45
  .kg-bookmark-metadata {
46
+ display: flex;
39
47
  align-items: center;
40
- white-space: nowrap;
41
- overflow: hidden;
42
- text-overflow: ellipsis;
48
+ margin-top: 22px;
49
+ width: 100%;
50
+ font-size: 1.4rem;
51
+ font-weight: 500;
43
52
  }
44
53
 
45
- .kg-bookmark-description {
46
- display: -webkit-box;
47
- -webkit-box-orient: vertical;
48
- -webkit-line-clamp: 2;
49
- overflow: hidden;
54
+ .kg-bookmark-metadata > *:not(img) {
55
+ opacity: 0.7;
50
56
  }
51
57
 
52
58
  .kg-bookmark-icon {
53
- display: inline-block;
54
- width: 1em;
55
- height: 1em;
56
- vertical-align: text-bottom;
57
- margin-right: .5em;
58
- margin-bottom: .05em;
59
+ width: 20px;
60
+ height: 20px;
61
+ margin-right: 6px;
59
62
  }
60
63
 
61
- .kg-bookmark-thumbnail {
62
- display: flex;
63
- flex-basis: 24rem;
64
- flex-grow: 1;
64
+ .kg-bookmark-author,
65
+ .kg-bookmark-publisher {
66
+ display: inline;
65
67
  }
66
68
 
67
- .kg-bookmark-thumbnail img {
68
- max-width: 100%;
69
- height: auto;
70
- vertical-align: bottom;
71
- object-fit: cover;
72
- }
73
-
74
- .kg-bookmark-author {
75
- white-space: nowrap;
69
+ .kg-bookmark-publisher {
76
70
  text-overflow: ellipsis;
77
71
  overflow: hidden;
72
+ max-width: 240px;
73
+ white-space: nowrap;
74
+ display: block;
75
+ line-height: 1.65em;
76
+ }
77
+
78
+ .kg-bookmark-metadata > span:nth-of-type(2) {
79
+ font-weight: 400;
78
80
  }
79
81
 
80
- .kg-bookmark-publisher::before {
82
+ .kg-bookmark-metadata > span:nth-of-type(2):before {
81
83
  content: "•";
82
- margin: 0 .5em;
84
+ margin: 0 6px;
85
+ }
86
+
87
+ .kg-bookmark-thumbnail {
88
+ position: relative;
89
+ flex-grow: 1;
90
+ min-width: 33%;
91
+ }
92
+
93
+ .kg-bookmark-thumbnail img {
94
+ width: 100%;
95
+ height: 100%;
96
+ object-fit: cover;
97
+ position: absolute;
98
+ top: 0;
99
+ left: 0;
100
+ border-radius: 0 4px 4px 0;
83
101
  }
@@ -0,0 +1,30 @@
1
+ .kg-button-card {
2
+ display: flex;
3
+ position: static;
4
+ align-items: center;
5
+ width: 100%;
6
+ justify-content: center;
7
+ }
8
+
9
+ a.kg-btn {
10
+ display: flex;
11
+ position: static;
12
+ align-items: center;
13
+ padding: 0 2.0rem;
14
+ height: 4.0rem;
15
+ line-height: 4.0rem;
16
+ font-size: 1.65rem;
17
+ font-weight: 600;
18
+ text-decoration: none;
19
+ border-radius: 5px;
20
+ transition: opacity 0.2s ease-in-out;
21
+ }
22
+
23
+ a.kg-btn:hover {
24
+ opacity: 0.85;
25
+ }
26
+
27
+ a.kg-btn-accent {
28
+ background-color: var(--ghost-accent-color);
29
+ color: #fff;
30
+ }
@@ -0,0 +1,50 @@
1
+ .kg-card-callout {
2
+ display: flex;
3
+ align-items: center;
4
+ padding: 20px 28px;
5
+ border-radius: 3px;
6
+ }
7
+
8
+ .kg-card-callout-grey {
9
+ background: rgba(124, 139, 154, 0.13);
10
+ }
11
+
12
+ .kg-card-callout-white {
13
+ background: transparent;
14
+ box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25);
15
+ }
16
+
17
+ .kg-card-callout-blue {
18
+ background: rgba(33, 172, 232, 0.12);
19
+ }
20
+
21
+ .kg-card-callout-green {
22
+ background: rgba(52, 183, 67, 0.12);
23
+ }
24
+
25
+ .kg-card-callout-yellow {
26
+ background: rgba(240, 165, 15, 0.13);
27
+ }
28
+
29
+ .kg-card-callout-red {
30
+ background: rgba(209, 46, 46, 0.11);
31
+ }
32
+
33
+ .kg-card-callout-pink {
34
+ background: rgba(225, 71, 174, 0.11);
35
+ }
36
+
37
+ .kg-card-callout-purple {
38
+ background: rgba(135, 85, 236, 0.12);
39
+ }
40
+
41
+ .kg-card-callout-accent {
42
+ background: var(--ghost-accent-color);
43
+ color: #fff;
44
+ }
45
+
46
+ .kg-callout-emoji {
47
+ padding-right: 12px;
48
+ line-height: 1.6;
49
+ font-size: 2rem;
50
+ }
@@ -1,17 +1,12 @@
1
- .kg-gallery-card {
2
- margin: 0 0 1.5em;
3
- }
4
-
5
- .kg-gallery-card figcaption {
6
- margin: -1.0em 0 1.5em;
1
+ .kg-image-card:not(.kg-card-hascaption) + .kg-image-card,
2
+ .kg-image-card:not(.kg-card-hascaption) + .kg-gallery-card,
3
+ .kg-gallery-card:not(.kg-card-hascaption) + .kg-image-card,
4
+ .kg-gallery-card:not(.kg-card-hascaption) + .kg-gallery-card {
5
+ margin-top: 1.2rem;
7
6
  }
8
7
 
9
8
  .kg-gallery-container {
10
- display: flex;
11
- flex-direction: column;
12
- margin: 1.5em auto;
13
- max-width: 1040px;
14
- width: 100vw;
9
+ position: relative;
15
10
  }
16
11
 
17
12
  .kg-gallery-row {
@@ -28,9 +23,9 @@
28
23
  }
29
24
 
30
25
  .kg-gallery-row:not(:first-of-type) {
31
- margin: 0.75em 0 0 0;
26
+ margin: 1.2rem 0 0;
32
27
  }
33
28
 
34
29
  .kg-gallery-image:not(:first-of-type) {
35
- margin: 0 0 0 0.75em;
30
+ margin: 0 0 0 1.2rem;
36
31
  }