From 01ee4d5d2b522274e9090d5fb2dbe4c8e9e59919 Mon Sep 17 00:00:00 2001 From: Kohei Yoshino Date: Wed, 8 Nov 2023 21:01:52 -0500 Subject: [PATCH] Improve content editor title --- .../components/contents/details/toolbar.svelte | 8 +++++++- .../contents/list/entry-list-item.svelte | 2 +- src/lib/services/contents/view.js | 17 +++++++++-------- src/lib/services/utils/strings.js | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/lib/components/contents/details/toolbar.svelte b/src/lib/components/contents/details/toolbar.svelte index f8392db5..7700b65e 100644 --- a/src/lib/components/contents/details/toolbar.svelte +++ b/src/lib/components/contents/details/toolbar.svelte @@ -21,7 +21,9 @@ revertChanges, saveEntry, } from '$lib/services/contents/editor'; + import { formatSummary } from '$lib/services/contents/view'; import { goBack, goto } from '$lib/services/navigation'; + import { truncate } from '$lib/services/utils/strings'; let showDuplicateToast = false; let showValidationToast = false; @@ -40,6 +42,7 @@ currentValues, } = $entryDraft ?? /** @type {EntryDraft} */ ({})); + $: ({ defaultLocale = 'default' } = collection?._i18n ?? /** @type {I18nConfig} */ ({})); $: collectionLabel = collection?.label || collection?.name; $: collectionLabelSingular = collection?.label_singular || collectionLabel; $: canPreview = @@ -79,7 +82,10 @@ values: { name: collectionFile ? `${collectionLabel} » ${collectionFile.label}` - : collectionLabelSingular, + : `${collectionLabel} » ${truncate( + formatSummary(collection, originalEntry, defaultLocale, { useTemplate: false }), + 25, + )}`, }, })} {/if} diff --git a/src/lib/components/contents/list/entry-list-item.svelte b/src/lib/components/contents/list/entry-list-item.svelte index 63eb2254..d499bcc2 100644 --- a/src/lib/components/contents/list/entry-list-item.svelte +++ b/src/lib/components/contents/list/entry-list-item.svelte @@ -62,7 +62,7 @@ {/if} - {formatSummary($selectedCollection, entry, content, locale)} + {formatSummary($selectedCollection, entry, locale)} diff --git a/src/lib/services/contents/view.js b/src/lib/services/contents/view.js index d41c8d9b..42b0c379 100644 --- a/src/lib/services/contents/view.js +++ b/src/lib/services/contents/view.js @@ -18,7 +18,7 @@ import { import { prefs } from '$lib/services/prefs'; import { getDateTimeParts } from '$lib/services/utils/datetime'; import LocalStorage from '$lib/services/utils/local-storage'; -import { stripSlashes } from '$lib/services/utils/strings'; +import { stripSlashes, truncate } from '$lib/services/utils/strings'; const storageKey = 'sveltia-cms.contents-view'; /** @@ -79,11 +79,9 @@ const transformSummary = (summary, tf, fieldConfig) => { } if (tf.startsWith('truncate')) { - const [, number, string = ''] = tf.match(/^truncate\((\d+)(?:,\s*'?(.*?)'?)?\)$/); - const max = Number(number); - const truncated = String(summary).substring(0, max); + const [, max, ellipsis = ''] = tf.match(/^truncate\((\d+)(?:,\s*'?(.*?)'?)?\)$/); - return String(summary).length > max ? `${truncated}${string}` : truncated; + return truncate(String(summary), Number(max), { ellipsis }); } return summary; @@ -93,12 +91,15 @@ const transformSummary = (summary, tf, fieldConfig) => { * Parse the collection summary template to generate the summary to be displayed on the entry list. * @param {Collection} collection Entry’s collection. * @param {Entry} entry Entry. - * @param {EntryContent} content Entry content. * @param {LocaleCode} locale Locale. + * @param {object} [options] Options. + * @param {boolean} [options.useTemplate] Whether to use the collection’s template if available. * @returns {string} Formatted summary. * @see https://decapcms.org/docs/configuration-options/#summary */ -export const formatSummary = (collection, entry, content, locale) => { +export const formatSummary = (collection, entry, locale, { useTemplate = true } = {}) => { + const { content } = entry.locales[locale]; + const { name: collectionName, folder: collectionFolder, @@ -110,7 +111,7 @@ export const formatSummary = (collection, entry, content, locale) => { // Fields other than `title` should be defined with `identifier_field` as per the Netlify/Decap // CMS document, but actually `name` also works as a fallback. We also use the label` property and // the entry slug. - if (!summaryTemplate) { + if (!useTemplate || !summaryTemplate) { return content[identifierField] || content.title || content.name || content.label || entry.slug; } diff --git a/src/lib/services/utils/strings.js b/src/lib/services/utils/strings.js index 62e0b5c6..7c28cfaf 100644 --- a/src/lib/services/utils/strings.js +++ b/src/lib/services/utils/strings.js @@ -1,3 +1,20 @@ +/** + * Truncate the given string. + * @param {string} string Original string. + * @param {number} max Maximum number of characters. + * @param {object} [options] Options. + * @param {string} [options.ellipsis] Character(s) to be appended if the the truncated string is + * longer than `max`. + * @returns {string} Truncated string. + */ +export const truncate = (string, max, { ellipsis = '…' } = {}) => { + // Don’t use `split()` because it breaks Unicode characters like emoji + const chars = [...string]; + const truncated = chars.slice(0, max).join('').trim(); + + return `${truncated}${chars.length > max ? ellipsis : ''}`; +}; + /** * Escape the given string so it can be used safely for `new RegExp()`. * @param {string} string Original string.