Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions entry_types/scrolled/config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,7 @@ de:
reset: Zurücksetzen
save: Speichern
chapter_menu_items:
copy_permalink: Permalink kopieren
move_to_main: In Kapitel umwandeln
move_to_excursions: In Exkurs umwandeln
destroy_chapter_menu_item:
Expand Down
1 change: 1 addition & 0 deletions entry_types/scrolled/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@ en:
reset: Reset
save: Save
chapter_menu_items:
copy_permalink: Copy permalink
move_to_main: Turn into chapter
move_to_excursions: Turn into excursion
destroy_chapter_menu_item:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {ScrolledEntry} from 'editor/models/ScrolledEntry';
import {factories} from 'pageflow/testHelpers';
import {normalizeSeed} from 'support';

describe('ScrolledEntry', () => {
describe('#getChapterPermalink', () => {
it('returns permalink with slugified title', () => {
const entry = factories.entry(
ScrolledEntry,
{pretty_url: 'https://example.com/entry'},
{
entryTypeSeed: normalizeSeed({
chapters: [
{id: 1, permaId: 100, configuration: {title: 'My Chapter'}}
]
})
}
);
const chapter = entry.chapters.get(1);

const result = entry.getChapterPermalink(chapter);

expect(result).toEqual('https://example.com/entry#my-chapter');
});

it('uses chapter-permaId for chapters without title', () => {
const entry = factories.entry(
ScrolledEntry,
{pretty_url: 'https://example.com/entry'},
{
entryTypeSeed: normalizeSeed({
chapters: [
{id: 1, permaId: 100}
]
})
}
);
const chapter = entry.chapters.get(1);

const result = entry.getChapterPermalink(chapter);

expect(result).toEqual('https://example.com/entry#chapter-100');
});

it('appends permaId if slug would not be unique', () => {
const entry = factories.entry(
ScrolledEntry,
{pretty_url: 'https://example.com/entry'},
{
entryTypeSeed: normalizeSeed({
chapters: [
{id: 1, permaId: 100, configuration: {title: 'Same Title'}},
{id: 2, permaId: 200, configuration: {title: 'Same Title'}}
]
})
}
);
const chapter1 = entry.chapters.get(1);
const chapter2 = entry.chapters.get(2);

expect(entry.getChapterPermalink(chapter1)).toEqual('https://example.com/entry#same-title');
expect(entry.getChapterPermalink(chapter2)).toEqual('https://example.com/entry#same-title-200');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
CopyPermalinkMenuItem,
ToggleExcursionMenuItem,
DestroyChapterMenuItem
} from 'editor/models/chapterMenuItems';

import {useFakeTranslations} from 'pageflow/testHelpers';
import {useEditorGlobals} from 'support';

describe('ChapterMenuItems', () => {
useFakeTranslations({
'pageflow_scrolled.editor.chapter_menu_items.copy_permalink': 'Copy permalink',
'pageflow_scrolled.editor.chapter_menu_items.move_to_main': 'Move to main',
'pageflow_scrolled.editor.chapter_menu_items.move_to_excursions': 'Move to excursions',
'pageflow_scrolled.editor.destroy_chapter_menu_item.destroy': 'Delete chapter',
'pageflow_scrolled.editor.destroy_chapter_menu_item.confirm_destroy': 'Really delete this chapter?'
});

const {createEntry} = useEditorGlobals();

describe('CopyPermalinkMenuItem', () => {
it('has Copy permalink label', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
const menuItem = new CopyPermalinkMenuItem({}, {entry, chapter});

expect(menuItem.get('label')).toBe('Copy permalink');
});

it('supports separated attribute', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
const menuItem = new CopyPermalinkMenuItem({separated: true}, {entry, chapter});

expect(menuItem.get('separated')).toBe(true);
});

it('copies permalink to clipboard when selected', () => {
const entry = createEntry({chapters: [{id: 1}]});
entry.getChapterPermalink = jest.fn().mockReturnValue('http://example.com/chapter');
const chapter = entry.chapters.get(1);
const menuItem = new CopyPermalinkMenuItem({}, {entry, chapter});
navigator.clipboard = {writeText: jest.fn()};

menuItem.selected();

expect(entry.getChapterPermalink).toHaveBeenCalledWith(chapter);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('http://example.com/chapter');
});
});

describe('ToggleExcursionMenuItem', () => {
it('has Move to excursions label when chapter is in main storyline', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
const menuItem = new ToggleExcursionMenuItem({}, {chapter});

expect(menuItem.get('label')).toBe('Move to excursions');
});

it('has Move to main label when chapter is an excursion', () => {
const entry = createEntry({
storylines: [{id: 1, configuration: {main: true}}, {id: 2}],
chapters: [{id: 1, storylineId: 2}]
});
const chapter = entry.chapters.get(1);
const menuItem = new ToggleExcursionMenuItem({}, {chapter});

expect(menuItem.get('label')).toBe('Move to main');
});

it('calls toggleExcursion on chapter when selected', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
chapter.toggleExcursion = jest.fn();
const menuItem = new ToggleExcursionMenuItem({}, {chapter});

menuItem.selected();

expect(chapter.toggleExcursion).toHaveBeenCalled();
});
});

describe('DestroyChapterMenuItem', () => {
it('has Delete chapter label', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
const menuItem = new DestroyChapterMenuItem({}, {chapter});

expect(menuItem.get('label')).toBe('Delete chapter');
});

it('calls destroyWithDelay on chapter when confirmed', () => {
const entry = createEntry({chapters: [{id: 1}]});
const chapter = entry.chapters.get(1);
chapter.destroyWithDelay = jest.fn();
const menuItem = new DestroyChapterMenuItem({}, {chapter});
window.confirm = jest.fn().mockReturnValue(true);

menuItem.selected();

expect(window.confirm).toHaveBeenCalledWith('Really delete this chapter?');
expect(chapter.destroyWithDelay).toHaveBeenCalled();
});
});
});
47 changes: 47 additions & 0 deletions entry_types/scrolled/package/spec/shared/chapterSlug-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {getChapterSlugs} from 'shared/chapterSlug';

describe('getChapterSlugs', () => {
it('returns slugified title', () => {
const chapters = [
{configuration: {title: 'My Chapter'}, permaId: 100}
];

expect(getChapterSlugs(chapters)[100]).toBe('my-chapter');
});

it('returns chapter-permaId for empty title', () => {
const chapters = [
{configuration: {title: ''}, permaId: 100}
];

expect(getChapterSlugs(chapters)[100]).toBe('chapter-100');
});

it('returns chapter-permaId for missing title', () => {
const chapters = [
{configuration: {}, permaId: 100}
];

expect(getChapterSlugs(chapters)[100]).toBe('chapter-100');
});

it('appends permaId if slug would not be unique', () => {
const chapters = [
{configuration: {title: 'Same Title'}, permaId: 100},
{configuration: {title: 'Same Title'}, permaId: 200}
];

const slugs = getChapterSlugs(chapters);

expect(slugs[100]).toBe('same-title');
expect(slugs[200]).toBe('same-title-200');
});

it('handles special characters', () => {
const chapters = [
{configuration: {title: 'Über uns & mehr!'}, permaId: 100}
];

expect(getChapterSlugs(chapters)[100]).toBe('ueber-uns-and-mehr');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {duplicateContentElement} from './duplicateContentElement';

import {sortColors} from './sortColors';
import {Scale} from '../../../shared/Scale';
import {getChapterSlugs} from '../../../shared/chapterSlug';

const typographySizeSuffixes = ['xl', 'lg', 'md', 'sm', 'xs'];

Expand Down Expand Up @@ -287,6 +288,16 @@ export const ScrolledEntry = Entry.extend({
return `${this.get('pretty_url')}#section-${section.get('permaId')}`;
},

getChapterPermalink(chapter) {
const allChapters = this.chapters.map(c => ({
permaId: c.get('permaId'),
configuration: c.configuration.attributes
}));
const chapterSlugs = getChapterSlugs(allChapters);

return `${this.get('pretty_url')}#${chapterSlugs[chapter.get('permaId')]}`;
},

getPaletteColors({name} = {}) {
const themeOptions = this.scrolledSeed.config.theme.options

Expand Down
14 changes: 14 additions & 0 deletions entry_types/scrolled/package/src/editor/models/chapterMenuItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@ import Backbone from 'backbone';
import I18n from 'i18n-js';
import {DestroyMenuItem} from 'pageflow/editor';

export const CopyPermalinkMenuItem = Backbone.Model.extend({
initialize(attributes, {entry, chapter}) {
this.entry = entry;
this.chapter = chapter;
this.set('label', I18n.t('pageflow_scrolled.editor.chapter_menu_items.copy_permalink'));
},

selected() {
navigator.clipboard.writeText(
this.entry.getChapterPermalink(this.chapter)
);
}
});

export const ToggleExcursionMenuItem = Backbone.Model.extend({
initialize(attributes, {chapter}) {
this.chapter = chapter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {EditConfigurationView} from 'pageflow/editor';
import {CheckBoxInputView, TextInputView, TextAreaInputView} from 'pageflow/ui';

import {DestroyChapterMenuItem, ToggleExcursionMenuItem} from '../models/chapterMenuItems';
import {
CopyPermalinkMenuItem,
DestroyChapterMenuItem,
ToggleExcursionMenuItem
} from '../models/chapterMenuItems';

export const EditChapterView = EditConfigurationView.extend({
translationKeyPrefix: 'pageflow_scrolled.editor.edit_chapter',

getActionsMenuItems() {
return [
new ToggleExcursionMenuItem({}, {chapter: this.model}),
new CopyPermalinkMenuItem({}, {entry: this.options.entry, chapter: this.model}),
new DestroyChapterMenuItem({separated: true}, {chapter: this.model})
];
},
Expand Down
43 changes: 11 additions & 32 deletions entry_types/scrolled/package/src/entryState/structure.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useMemo, useCallback} from 'react';
import {useEntryStateCollectionItems, useEntryStateCollectionItem} from './EntryStateProvider';
import slugify from 'slugify';
import {getChapterSlugs} from '../shared/chapterSlug';

/**
* Returns a nested data structure representing the chapters and sections
Expand Down Expand Up @@ -273,37 +273,16 @@ export function useChapters() {
const chapters = useEntryStateCollectionItems('chapters');

return useMemo(() => {
const chapterSlugs = {};

return chapters.map((chapter, index) => {
let chapterSlug = chapter.configuration.title;

if (chapterSlug) {
chapterSlug = slugify(chapterSlug, {
lower: true,
locale: 'de',
strict: true
});

if (chapterSlugs[chapterSlug]) {
chapterSlug = chapterSlug+'-'+chapter.permaId; //append permaId if chapter reference is not unique
}

chapterSlugs[chapterSlug] = chapter;
}
else{
chapterSlug = 'chapter-'+chapter.permaId;
}

return ({
id: chapter.id,
permaId: chapter.permaId,
storylineId: chapter.storylineId,
chapterSlug,
index,
...chapter.configuration
});
});
const chapterSlugs = getChapterSlugs(chapters);

return chapters.map((chapter, index) => ({
id: chapter.id,
permaId: chapter.permaId,
storylineId: chapter.storylineId,
chapterSlug: chapterSlugs[chapter.permaId],
index,
...chapter.configuration
}));
}, [chapters]);
}

Expand Down
31 changes: 31 additions & 0 deletions entry_types/scrolled/package/src/shared/chapterSlug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import slugify from 'slugify';

export function getChapterSlugs(chapters) {
const result = {};
const usedSlugs = {};

chapters.forEach(chapter => {
let chapterSlug = chapter.configuration.title;

if (chapterSlug) {
chapterSlug = slugify(chapterSlug, {
lower: true,
locale: 'de',
strict: true
});

if (usedSlugs[chapterSlug]) {
chapterSlug = chapterSlug + '-' + chapter.permaId;
}

usedSlugs[chapterSlug] = true;
}
else {
chapterSlug = 'chapter-' + chapter.permaId;
}

result[chapter.permaId] = chapterSlug;
});

return result;
}
Loading