diff --git a/apps/admin-x-settings/src/components/settings/advanced/labs/AlphaFeatures.tsx b/apps/admin-x-settings/src/components/settings/advanced/labs/AlphaFeatures.tsx index 8ec45acd939..f0dab043a2f 100644 --- a/apps/admin-x-settings/src/components/settings/advanced/labs/AlphaFeatures.tsx +++ b/apps/admin-x-settings/src/components/settings/advanced/labs/AlphaFeatures.tsx @@ -19,10 +19,6 @@ const features = [{ title: 'Email customization', description: 'Adding more control over the newsletter template', flag: 'emailCustomization' -},{ - title: 'Collections', - description: 'Enables Collections 2.0', - flag: 'collections' },{ title: 'Collections Card', description: 'Enables the Collections Card for pages - requires Collections and the beta Editor to be enabled', diff --git a/ghost/admin/app/components/collections/collection-form.hbs b/ghost/admin/app/components/collections/collection-form.hbs deleted file mode 100644 index 2ebd3e73e1a..00000000000 --- a/ghost/admin/app/components/collections/collection-form.hbs +++ /dev/null @@ -1,90 +0,0 @@ -
-
-

Basic settings

-
-
-
- - - - - - - - - - - - - - - - - - - - - -

Maximum: 500 characters. You’ve used {{gh-count-down-characters @collection.description 500}}

-
- - - - - -
-
- -
-
- - - -
- - {{#if type.name}}{{type.name}}{{else}}Unknown type{{/if}} - - - {{#if (eq this.selectedType.value 'manual')}} -

Add posts to this collection one by one through post settings menu.

- {{/if}} - - {{#if (eq this.selectedType.value 'automatic')}} - - {{/if}} -
-
-
-
-
diff --git a/ghost/admin/app/components/collections/collection-form.js b/ghost/admin/app/components/collections/collection-form.js deleted file mode 100644 index bb545cd807b..00000000000 --- a/ghost/admin/app/components/collections/collection-form.js +++ /dev/null @@ -1,65 +0,0 @@ -import Component from '@glimmer/component'; -import {action} from '@ember/object'; -import {inject} from 'ghost-admin/decorators/inject'; -import {inject as service} from '@ember/service'; -import {slugify} from '@tryghost/string'; - -const TYPES = [{ - name: 'Manual', - value: 'manual' -}, { - name: 'Automatic', - value: 'automatic' -}]; - -export default class CollectionForm extends Component { - @service feature; - @service settings; - - @inject config; - - availableTypes = TYPES; - - get selectedType() { - const {collection} = this.args; - return this.availableTypes.findBy('value', collection.type) || {value: '!unknown'}; - } - - @action - setCollectionProperty(property, newValue) { - const {collection} = this.args; - - if (newValue) { - newValue = newValue.trim(); - } - - // Generate slug based on name for new collection when empty - if (property === 'title' && collection.isNew && !this.hasChangedSlug) { - let slugValue = slugify(newValue); - if (/^#/.test(newValue)) { - slugValue = 'hash-' + slugValue; - } - collection.slug = slugValue; - } - - // ensure manual changes of slug don't get reset when changing name - if (property === 'slug') { - this.hasChangedSlug = !!newValue; - } - - collection[property] = newValue; - - // clear validation message when typing - collection.hasValidated.addObject(property); - } - - @action - changeType(type) { - this.setCollectionProperty('type', type.value); - } - - @action - validateCollectionProperty(property) { - return this.args.collection.validate({property}); - } -} diff --git a/ghost/admin/app/components/collections/delete-collection-modal.hbs b/ghost/admin/app/components/collections/delete-collection-modal.hbs deleted file mode 100644 index 1484a837679..00000000000 --- a/ghost/admin/app/components/collections/delete-collection-modal.hbs +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/ghost/admin/app/components/collections/delete-collection-modal.js b/ghost/admin/app/components/collections/delete-collection-modal.js deleted file mode 100644 index 1e05a188138..00000000000 --- a/ghost/admin/app/components/collections/delete-collection-modal.js +++ /dev/null @@ -1,29 +0,0 @@ -import Component from '@glimmer/component'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default class DeleteCollectionModal extends Component { - @service notifications; - @service router; - - @task({drop: true}) - *deleteCollectionTask() { - try { - const {collection} = this.args.data; - - if (collection.isDeleted) { - return true; - } - - yield collection.destroyRecord(); - - this.notifications.closeAlerts('collection.delete'); - this.router.transitionTo('collections'); - return true; - } catch (error) { - this.notifications.showAPIError(error, {key: 'collection.delete.failed'}); - } finally { - this.args.close(); - } - } -} diff --git a/ghost/admin/app/components/collections/list-item.hbs b/ghost/admin/app/components/collections/list-item.hbs deleted file mode 100644 index a955bb1ecf3..00000000000 --- a/ghost/admin/app/components/collections/list-item.hbs +++ /dev/null @@ -1,32 +0,0 @@ -
  • - -

    - {{@collection.title}} -

    - {{#if @collection.description}} -

    - {{@collection.description}} -

    - {{/if}} -
    - - - {{@collection.slug}} - - - {{#if @collection.count.posts}} - - {{gh-pluralize @collection.count.posts "post"}} - - {{else}} - - {{gh-pluralize @collection.count.posts "post"}} - - {{/if}} - - -
    - {{svg-jar "arrow-right" class="w6 h6 fill-midgrey pa1"}} -
    -
    -
  • diff --git a/ghost/admin/app/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index ff161f55095..490d5ac364c 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/ember'; import Component from '@glimmer/component'; import React, {Suspense} from 'react'; -import fetch from 'fetch'; import ghostPaths from 'ghost-admin/utils/ghost-paths'; import moment from 'moment-timezone'; import {action} from '@ember/object'; @@ -276,24 +275,6 @@ export default class KoenigLexicalEditor extends Component { return response; }; - const fetchCollectionPosts = async (collectionSlug) => { - if (!this.contentKey) { - const integrations = await this.store.findAll('integration'); - const contentIntegration = integrations.findBy('slug', 'ghost-core-content'); - this.contentKey = contentIntegration?.contentKey.secret; - } - - const postsUrl = new URL(this.ghostPaths.url.admin('/api/content/posts/'), window.location.origin); - postsUrl.searchParams.append('key', this.contentKey); - postsUrl.searchParams.append('collection', collectionSlug); - postsUrl.searchParams.append('limit', 12); - - const response = await fetch(postsUrl.toString()); - const {posts} = await response.json(); - - return posts; - }; - const fetchAutocompleteLinks = async () => { const defaults = [ {label: 'Homepage', value: window.location.origin + '/'}, @@ -455,13 +436,11 @@ export default class KoenigLexicalEditor extends Component { unsplash: this.settings.unsplash ? unsplashConfig.defaultHeaders : null, tenor: this.config.tenor?.googleApiKey ? this.config.tenor : null, fetchAutocompleteLinks, - fetchCollectionPosts, fetchEmbed, fetchLabels, renderLabels: !this.session.user.isContributor, feature: { collectionsCard: this.feature.collectionsCard, - collections: this.feature.collections, contentVisibility: this.feature.contentVisibility }, deprecated: { // todo fix typo diff --git a/ghost/admin/app/controllers/collection.js b/ghost/admin/app/controllers/collection.js deleted file mode 100644 index d147e27df98..00000000000 --- a/ghost/admin/app/controllers/collection.js +++ /dev/null @@ -1,43 +0,0 @@ -import Controller from '@ember/controller'; -import DeleteCollectionModal from '../components/collections/delete-collection-modal'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default class CollectionController extends Controller { - @service modals; - @service notifications; - @service router; - - get collection() { - return this.model; - } - - @action - confirmDeleteCollection() { - return this.modals.open(DeleteCollectionModal, { - collection: this.model - }); - } - - @task({drop: true}) - *saveTask() { - let {collection} = this; - - try { - if (collection.get('errors').length !== 0) { - return; - } - yield collection.save(); - - // replace 'new' route with 'collection' route - this.replaceRoute('collection', collection); - - return collection; - } catch (error) { - if (error) { - this.notifications.showAPIError(error, {key: 'collection.save'}); - } - } - } -} diff --git a/ghost/admin/app/controllers/collections.js b/ghost/admin/app/controllers/collections.js deleted file mode 100644 index dd1ce224c95..00000000000 --- a/ghost/admin/app/controllers/collections.js +++ /dev/null @@ -1,38 +0,0 @@ -import Controller from '@ember/controller'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {tracked} from '@glimmer/tracking'; - -export default class CollectionsController extends Controller { - @service router; - - queryParams = ['type']; - @tracked type = 'public'; - - get collections() { - return this.model; - } - - get filteredCollections() { - return this.collections.filter((collection) => { - return (!collection.isNew); - }); - } - - get sortedCollections() { - return this.filteredCollections.sort((collectionA, collectionB) => { - // ignorePunctuation means the # in internal collection names is ignored - return collectionA.title.localeCompare(collectionB.title, undefined, {ignorePunctuation: true}); - }); - } - - @action - changeType(type) { - this.type = type; - } - - @action - newCollection() { - this.router.transitionTo('collection.new'); - } -} diff --git a/ghost/admin/app/controllers/lexical-editor.js b/ghost/admin/app/controllers/lexical-editor.js index f6a3373cb35..0de1d6f8bbc 100644 --- a/ghost/admin/app/controllers/lexical-editor.js +++ b/ghost/admin/app/controllers/lexical-editor.js @@ -261,11 +261,6 @@ export default class LexicalEditorController extends Controller { }); } - @computed - get collections() { - return this.store.peekAll('collection'); - } - @computed('session.user.{isAdmin,isEditor}') get canManageSnippets() { let {user} = this.session; @@ -986,10 +981,6 @@ export default class LexicalEditorController extends Controller { *backgroundLoaderTask() { yield this.store.query('snippet', {limit: 'all'}); - if (this.post?.displayName === 'page' && this.feature.get('collections') && this.feature.get('collectionsCard')) { - yield this.store.query('collection', {limit: 'all'}); - } - this.search.refreshContentTask.perform(); this.syncMobiledocSnippets(); } @@ -1235,7 +1226,7 @@ export default class LexicalEditorController extends Controller { const isDraft = this.post.get('status') === 'draft'; const slugContainsUntitled = slug.includes('untitled'); const isTitleSet = title && title.trim() !== '' && title !== DEFAULT_TITLE; - + if (isDraft && slugContainsUntitled && isTitleSet) { Sentry.captureException(new Error('Draft post has title set with untitled slug'), { extra: { diff --git a/ghost/admin/app/mixins/validation-engine.js b/ghost/admin/app/mixins/validation-engine.js index ce7af01f45e..f249db59575 100644 --- a/ghost/admin/app/mixins/validation-engine.js +++ b/ghost/admin/app/mixins/validation-engine.js @@ -1,6 +1,5 @@ // TODO: remove usage of Ember Data's private `Errors` class when refactoring validations // eslint-disable-next-line -import CollectionValidator from 'ghost-admin/validators/collection'; import CustomViewValidator from 'ghost-admin/validators/custom-view'; import DS from 'ember-data'; // eslint-disable-line import IntegrationValidator from 'ghost-admin/validators/integration'; @@ -68,7 +67,6 @@ export default Mixin.create({ signin: SigninValidator, signup: SignupValidator, tag: TagSettingsValidator, - collection: CollectionValidator, user: UserValidator, member: MemberValidator, integration: IntegrationValidator, diff --git a/ghost/admin/app/router.js b/ghost/admin/app/router.js index 092e55f0a7d..8116c7755a2 100644 --- a/ghost/admin/app/router.js +++ b/ghost/admin/app/router.js @@ -48,10 +48,6 @@ Router.map(function () { this.route('tag.new', {path: '/tags/new'}); this.route('tag', {path: '/tags/:tag_slug'}); - this.route('collections'); - this.route('collection.new', {path: '/collections/new'}); - this.route('collection', {path: '/collections/:collection_slug'}); - this.route('demo-x', function () { this.route('demo-x', {path: '/*sub'}); }); diff --git a/ghost/admin/app/routes/collection.js b/ghost/admin/app/routes/collection.js deleted file mode 100644 index f19f609f766..00000000000 --- a/ghost/admin/app/routes/collection.js +++ /dev/null @@ -1,93 +0,0 @@ -import * as Sentry from '@sentry/ember'; -import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; -import ConfirmUnsavedChangesModal from '../components/modals/confirm-unsaved-changes'; -import {action} from '@ember/object'; -import {inject as service} from '@ember/service'; - -export default class CollectionRoute extends AuthenticatedRoute { - @service modals; - @service router; - @service session; - - // ensures if a tag model is passed in directly we show it immediately - // and refresh in the background - _requiresBackgroundRefresh = true; - - beforeModel() { - super.beforeModel(...arguments); - - if (this.session.user.isAuthorOrContributor) { - return this.transitionTo('home'); - } - } - - model(params) { - this._requiresBackgroundRefresh = false; - - if (params.collection_slug) { - return this.store.queryRecord('collection', {slug: params.collection_slug}); - } else { - return this.store.createRecord('collection'); - } - } - - serialize(collection) { - return {collection_slug: collection.get('slug')}; - } - - setupController(controller, tag) { - super.setupController(...arguments); - - if (this._requiresBackgroundRefresh) { - tag.reload(); - } - } - - deactivate() { - this._requiresBackgroundRefresh = true; - - this.confirmModal = null; - this.hasConfirmed = false; - } - - @action - async willTransition(transition) { - if (this.hasConfirmed) { - return true; - } - - transition.abort(); - - // wait for any existing confirm modal to be closed before allowing transition - if (this.confirmModal) { - return; - } - - if (this.controller.saveTask?.isRunning) { - await this.controller.saveTask.last; - } - - const shouldLeave = await this.confirmUnsavedChanges(); - - if (shouldLeave) { - this.controller.model.rollbackAttributes(); - this.hasConfirmed = true; - return transition.retry(); - } - } - - async confirmUnsavedChanges() { - if (this.controller.model?.hasDirtyAttributes) { - Sentry.captureMessage('showing unsaved changes modal for collections route'); - this.confirmModal = this.modals - .open(ConfirmUnsavedChangesModal) - .finally(() => { - this.confirmModal = null; - }); - - return this.confirmModal; - } - - return true; - } -} diff --git a/ghost/admin/app/routes/collection/new.js b/ghost/admin/app/routes/collection/new.js deleted file mode 100644 index bf5a125ba80..00000000000 --- a/ghost/admin/app/routes/collection/new.js +++ /dev/null @@ -1,6 +0,0 @@ -import CollectionRoute from '../collection'; - -export default class NewRoute extends CollectionRoute { - controllerName = 'collection'; - templateName = 'collection'; -} diff --git a/ghost/admin/app/routes/collections.js b/ghost/admin/app/routes/collections.js deleted file mode 100644 index 942a61a9f64..00000000000 --- a/ghost/admin/app/routes/collections.js +++ /dev/null @@ -1,31 +0,0 @@ -import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; - -export default class CollectionsRoute extends AuthenticatedRoute { - // authors aren't allowed to manage tags - beforeModel() { - super.beforeModel(...arguments); - - if (this.session.user.isAuthorOrContributor) { - return this.transitionTo('home'); - } - } - - // set model to a live array so all collections are shown and created/deleted collections - // are automatically added/removed. Also load all collections in the background, - // pausing to show the loading spinner if no collections have been loaded yet - model() { - let promise = this.store.query('collection', {limit: 'all', include: 'count.posts'}); - let collections = this.store.peekAll('collection'); - if (this.store.peekAll('collection').get('length') === 0) { - return promise.then(() => collections); - } else { - return collections; - } - } - - buildRouteInfoMetadata() { - return { - titleToken: 'Collections' - }; - } -} diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index 4d9e44e8f22..2e7132bbdb0 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -67,7 +67,6 @@ export default class FeatureService extends Service { @feature('i18n') i18n; @feature('announcementBar') announcementBar; @feature('signupCard') signupCard; - @feature('collections') collections; @feature('mailEvents') mailEvents; @feature('collectionsCard') collectionsCard; @feature('importMemberTier') importMemberTier; diff --git a/ghost/admin/app/templates/collection.hbs b/ghost/admin/app/templates/collection.hbs deleted file mode 100644 index 10f6ff4829a..00000000000 --- a/ghost/admin/app/templates/collection.hbs +++ /dev/null @@ -1,48 +0,0 @@ -
    -
    - -
    -
    - - Collections - - {{svg-jar "arrow-right-small"}} {{if this.collection.isNew "New collection" "Edit collection"}} -
    -

    - {{if this.collection.isNew "New collection" this.collection.title}} -

    -
    - -
    - -
    -
    - - - - - {{#unless this.collection.isNew}} -
    - -
    - {{/unless}} - - {{#if this.collection.postIds}} -
    -

    Collection has {{this.collection.postIds.length}} posts

    -
      - {{#each this.collection.postIds as |post|}} -
    1. {{post}}
    2. - {{/each}} -
    -
    - {{/if}} -
    diff --git a/ghost/admin/app/templates/collections.hbs b/ghost/admin/app/templates/collections.hbs deleted file mode 100644 index 0ac7ec14a20..00000000000 --- a/ghost/admin/app/templates/collections.hbs +++ /dev/null @@ -1,34 +0,0 @@ -
    - -

    Collections

    -
    - New collection -
    -
    - -
    -
      - {{#if this.sortedCollections}} -
    1. -
      Collection
      -
      -
    2. - - - - {{else}} -
    3. -
      - {{svg-jar "collections-placeholder" class="gh-collections-placeholder"}} -

      Start organizing your content.

      - - Create a new collection - -
      -
    4. - {{/if}} -
    -
    -
    - -{{outlet}} diff --git a/ghost/admin/app/templates/lexical-editor.hbs b/ghost/admin/app/templates/lexical-editor.hbs index 252dc8129b4..5ed0bcd05c8 100644 --- a/ghost/admin/app/templates/lexical-editor.hbs +++ b/ghost/admin/app/templates/lexical-editor.hbs @@ -93,7 +93,6 @@ @cardOptions={{hash post=this.post snippets=this.snippets - collections=this.collections deleteSnippet=(if this.canManageSnippets this.confirmDeleteSnippet) createSnippet=(if this.canManageSnippets this.createSnippet) }} diff --git a/ghost/admin/app/validators/collection.js b/ghost/admin/app/validators/collection.js deleted file mode 100644 index 98a89ed4a7c..00000000000 --- a/ghost/admin/app/validators/collection.js +++ /dev/null @@ -1,18 +0,0 @@ -import BaseValidator from './base'; -import {isBlank} from '@ember/utils'; - -export default BaseValidator.create({ - properties: ['title'], - - name(model) { - let title = model.title; - let hasValidated = model.hasValidated; - - if (isBlank(title)) { - model.errors.add('title', 'Please enter a title.'); - this.invalidate(); - } - - hasValidated.addObject('title'); - } -}); diff --git a/ghost/admin/tests/unit/routes/collection-test.js b/ghost/admin/tests/unit/routes/collection-test.js deleted file mode 100644 index e44eb2134a8..00000000000 --- a/ghost/admin/tests/unit/routes/collection-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import {describe, it} from 'mocha'; -import {expect} from 'chai'; -import {setupTest} from 'ember-mocha'; - -describe('Unit | Route | collection', function () { - setupTest(); - - it('exists', function () { - let route = this.owner.lookup('route:collection'); - expect(route).to.be.ok; - }); -}); diff --git a/ghost/admin/tests/unit/routes/collections-test.js b/ghost/admin/tests/unit/routes/collections-test.js deleted file mode 100644 index 65d3e0d9423..00000000000 --- a/ghost/admin/tests/unit/routes/collections-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import {describe, it} from 'mocha'; -import {expect} from 'chai'; -import {setupTest} from 'ember-mocha'; - -describe('Unit | Route | collections', function () { - setupTest(); - - it('exists', function () { - let route = this.owner.lookup('route:collections'); - expect(route).to.be.ok; - }); -});