add-cms
This commit is contained in:
140
source/admin/packages/decap-cms-lib-widgets/CHANGELOG.md
Normal file
140
source/admin/packages/decap-cms-lib-widgets/CHANGELOG.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.2.1](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.2.0...decap-cms-lib-widgets@3.2.1) (2025-07-10)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.1.0...decap-cms-lib-widgets@3.2.0) (2025-06-26)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# [3.1.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.2...decap-cms-lib-widgets@3.1.0) (2024-11-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- summary string transformations format ([#7221](https://github.com/decaporg/decap-cms/issues/7221)) ([#7313](https://github.com/decaporg/decap-cms/issues/7313)) ([1d0cd61](https://github.com/decaporg/decap-cms/commit/1d0cd611812860450ff15b31eee7f764d6026306))
|
||||
|
||||
## [3.0.2](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.1...decap-cms-lib-widgets@3.0.2) (2024-03-21)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [3.0.1](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.1-beta.2...decap-cms-lib-widgets@3.0.1) (2024-02-01)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [3.0.1-beta.2](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.1-beta.1...decap-cms-lib-widgets@3.0.1-beta.2) (2024-01-31)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [3.0.1-beta.1](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.1-beta.0...decap-cms-lib-widgets@3.0.1-beta.1) (2024-01-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- change dayjs to per-package dependency ([#6992](https://github.com/decaporg/decap-cms/issues/6992)) ([0c278b0](https://github.com/decaporg/decap-cms/commit/0c278b0a83d93233d3b3e860d3029df20fe1c501))
|
||||
|
||||
## [3.0.1-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@3.0.0...decap-cms-lib-widgets@3.0.1-beta.0) (2023-11-23)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- replace moment with dayjs ([#6980](https://github.com/decaporg/decap-cms/issues/6980)) ([22370b1](https://github.com/decaporg/decap-cms/commit/22370b13e49a4a5f58a60ebd4bc40ce4b141eb11))
|
||||
|
||||
# [3.0.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.9.0...decap-cms-lib-widgets@3.0.0) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# [1.9.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.9.0-beta.0...decap-cms-lib-widgets@1.9.0) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# 1.9.0-beta.0 (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
- rename packages ([#6863](https://github.com/decaporg/decap-cms/issues/6863)) ([d515e7b](https://github.com/decaporg/decap-cms/commit/d515e7bd33216a775d96887b08c4f7b1962941bb))
|
||||
|
||||
## [1.8.2-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.8.1...decap-cms-lib-widgets@1.8.2-beta.0) (2023-07-27)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [1.8.1](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.8.0...decap-cms-lib-widgets@1.8.1) (2022-04-13)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# [1.8.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.7.0...decap-cms-lib-widgets@1.8.0) (2022-01-21)
|
||||
|
||||
### Features
|
||||
|
||||
- add truncate filter to summary tag ([#6105](https://github.com/decaporg/decap-cms/issues/6105)) ([d66c573](https://github.com/decaporg/decap-cms/commit/d66c573697c6a66919e048f0fde9cf2f8ea6acac))
|
||||
|
||||
# [1.7.0](https://github.com/decaporg/decap-cms/compare/decap-cms-lib-widgets@1.6.3...decap-cms-lib-widgets@1.7.0) (2021-10-11)
|
||||
|
||||
### Features
|
||||
|
||||
- add string template filters "default" and "ternary" ([#3677](https://github.com/decaporg/decap-cms/issues/3677)) ([#5878](https://github.com/decaporg/decap-cms/issues/5878)) ([c791158](https://github.com/decaporg/decap-cms/commit/c791158dd5ea8ea03930f9881a86c71cb1770836))
|
||||
|
||||
## [1.6.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.6.2...decap-cms-lib-widgets@1.6.3) (2021-06-01)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [1.6.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.6.1...decap-cms-lib-widgets@1.6.2) (2021-05-31)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## [1.6.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.6.0...decap-cms-lib-widgets@1.6.1) (2021-02-10)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
# [1.6.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.5.0...decap-cms-lib-widgets@1.6.0) (2020-10-25)
|
||||
|
||||
### Features
|
||||
|
||||
- Support filters for template strings [#3677](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/3677) ([#4396](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/4396)) ([1fa108e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/1fa108ee67b7e992a4d2a61cde13df7917e103be))
|
||||
|
||||
# [1.5.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.4.0...decap-cms-lib-widgets@1.5.0) (2020-10-20)
|
||||
|
||||
### Features
|
||||
|
||||
- **widget-list:** add min max configuration ([#4394](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/4394)) ([5fdfe40](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/5fdfe40dd29e9e22c9ae7d6219bc057f7ea7280b))
|
||||
|
||||
# [1.4.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.3.5...decap-cms-lib-widgets@1.4.0) (2020-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
- **core:** Add {{dirname}} to summary and preview_path ([#4279](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/4279)) ([576e4f0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/576e4f0f1a158d6b587587c52fb288d8f6eea89f))
|
||||
|
||||
## [1.3.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.3.4...decap-cms-lib-widgets@1.3.5) (2020-09-15)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-lib-widgets
|
||||
|
||||
## 1.3.4 (2020-09-08)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([828bb16](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/828bb16415b8c22a34caa19c50c38b24ffe9ceae))
|
||||
|
||||
## 1.3.3 (2020-08-20)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([8262487](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/82624879ccbcb16610090041db28f00714d924c8))
|
||||
|
||||
## 1.3.2 (2020-07-27)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([118d50a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/118d50a7a70295f25073e564b5161aa2b9883056))
|
||||
|
||||
## [1.3.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/compare/decap-cms-lib-widgets@1.3.0...decap-cms-lib-widgets@1.3.1) (2020-07-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- relation widget performance ([#3975](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/3975)) ([c7e0fe8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/c7e0fe8492d09a3d151c608f50da844f421362ed))
|
||||
|
||||
# 1.3.0 (2020-05-04)
|
||||
|
||||
### Features
|
||||
|
||||
- **widget-relation:** string templates support ([#3659](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/issues/3659)) ([213ae86](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets/commit/213ae86b54d02f5fc79fe11113507587ed062ff2))
|
||||
9
source/admin/packages/decap-cms-lib-widgets/README.md
Normal file
9
source/admin/packages/decap-cms-lib-widgets/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Docs coming soon!
|
||||
|
||||
Decap CMS was converted from a single npm package to a "monorepo" of over 20 packages.
|
||||
We haven't created a README for this package yet, but you can:
|
||||
|
||||
1. Check out the [main readme](https://github.com/decaporg/decap-cms/#readme) or the [documentation
|
||||
site](https://www.decapcms.org) for more info.
|
||||
2. Reach out to the [community chat](https://decapcms.org/chat/) if you need help.
|
||||
3. Help out and [write the readme yourself](https://github.com/decaporg/decap-cms/edit/main/packages/decap-cms-lib-widgets/README.md)!
|
||||
30
source/admin/packages/decap-cms-lib-widgets/package.json
Normal file
30
source/admin/packages/decap-cms-lib-widgets/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "decap-cms-lib-widgets",
|
||||
"description": "Shared utilities for Decap CMS.",
|
||||
"version": "3.2.1",
|
||||
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-widgets",
|
||||
"bugs": "https://github.com/decaporg/decap-cms/issues",
|
||||
"module": "dist/esm/index.js",
|
||||
"main": "dist/decap-cms-lib-widgets.js",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"decap-cms"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"develop": "npm run build:esm -- --watch",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.10",
|
||||
"path-browserify": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"immutable": "^3.7.6",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"browser": {
|
||||
"path": "path-browserify"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
compileStringTemplate,
|
||||
expandPath,
|
||||
extractTemplateVars,
|
||||
keyToPathArray,
|
||||
parseDateFromEntry,
|
||||
} from '../stringTemplate';
|
||||
|
||||
describe('stringTemplate', () => {
|
||||
describe('keyToPathArray', () => {
|
||||
it('should return array of length 1 with simple path', () => {
|
||||
expect(keyToPathArray('category')).toEqual(['category']);
|
||||
});
|
||||
|
||||
it('should return path array for complex path', () => {
|
||||
expect(keyToPathArray('categories[0].title.subtitles[0].welcome[2]')).toEqual([
|
||||
'categories',
|
||||
'0',
|
||||
'title',
|
||||
'subtitles',
|
||||
'0',
|
||||
'welcome',
|
||||
'2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDateFromEntry', () => {
|
||||
it('should return date based on dateFieldName', () => {
|
||||
const date = new Date().toISOString();
|
||||
const dateFieldName = 'dateFieldName';
|
||||
const entry = fromJS({ data: { dateFieldName: date } });
|
||||
expect(parseDateFromEntry(entry, dateFieldName).toISOString()).toBe(date);
|
||||
});
|
||||
|
||||
it('should return undefined on empty dateFieldName', () => {
|
||||
const entry = fromJS({ data: {} });
|
||||
expect(parseDateFromEntry(entry, '')).toBeUndefined();
|
||||
expect(parseDateFromEntry(entry, null)).toBeUndefined();
|
||||
expect(parseDateFromEntry(entry, undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined on invalid date', () => {
|
||||
const entry = fromJS({ data: { date: '' } });
|
||||
const dateFieldName = 'date';
|
||||
expect(parseDateFromEntry(entry, dateFieldName)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractTemplateVars', () => {
|
||||
it('should extract template variables', () => {
|
||||
expect(extractTemplateVars('{{slug}}-hello-{{date}}-world-{{fields.id}}')).toEqual([
|
||||
'slug',
|
||||
'date',
|
||||
'fields.id',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array on no matches', () => {
|
||||
expect(extractTemplateVars('hello-world')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compileStringTemplate', () => {
|
||||
const date = new Date('2020-01-02T13:28:27.679Z');
|
||||
it('should compile year variable', () => {
|
||||
expect(compileStringTemplate('{{year}}', date)).toBe('2020');
|
||||
});
|
||||
|
||||
it('should compile month variable', () => {
|
||||
expect(compileStringTemplate('{{month}}', date)).toBe('01');
|
||||
});
|
||||
|
||||
it('should compile day variable', () => {
|
||||
expect(compileStringTemplate('{{day}}', date)).toBe('02');
|
||||
});
|
||||
|
||||
it('should compile hour variable', () => {
|
||||
expect(compileStringTemplate('{{hour}}', date)).toBe('13');
|
||||
});
|
||||
|
||||
it('should compile minute variable', () => {
|
||||
expect(compileStringTemplate('{{minute}}', date)).toBe('28');
|
||||
});
|
||||
|
||||
it('should compile second variable', () => {
|
||||
expect(compileStringTemplate('{{second}}', date)).toBe('27');
|
||||
});
|
||||
|
||||
it('should error on missing date', () => {
|
||||
expect(() => compileStringTemplate('{{year}}')).toThrowError();
|
||||
});
|
||||
|
||||
it('return compiled template', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
'{{slug}}-{{year}}-{{fields.slug}}-{{title}}-{{date}}',
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', title: 'title', date }),
|
||||
),
|
||||
).toBe('backendSlug-2020-entrySlug-title-' + date.toString());
|
||||
});
|
||||
|
||||
it('return apply processor to values', () => {
|
||||
expect(
|
||||
compileStringTemplate('{{slug}}', date, 'slug', fromJS({}), value => value.toUpperCase()),
|
||||
).toBe('SLUG');
|
||||
});
|
||||
|
||||
it('return apply filter to values', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
'{{slug | upper}}-{{title | lower}}-{{year}}',
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', title: 'Title', date }),
|
||||
),
|
||||
).toBe('BACKENDSLUG-title-2020');
|
||||
});
|
||||
|
||||
it('return apply filter to date field', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
"{{slug | upper}}-{{title | lower}}-{{published | date('MM-DD')}}-{{year}}",
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', title: 'Title', published: date, date }),
|
||||
),
|
||||
).toBe('BACKENDSLUG-title-01-02-2020');
|
||||
});
|
||||
|
||||
it('return apply filter for default value', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
"{{slug | upper}}-{{title | default('none')}}-{{subtitle | default('none')}}",
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', title: 'title', subtitle: null, published: date, date }),
|
||||
),
|
||||
).toBe('BACKENDSLUG-title-none');
|
||||
});
|
||||
|
||||
it('return apply filter for ternary', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
"{{slug | upper}}-{{starred | ternary('star️','nostar')}}-{{done | ternary('done', 'open️')}}",
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', starred: true, done: false }),
|
||||
),
|
||||
).toBe('BACKENDSLUG-star️-open️');
|
||||
});
|
||||
|
||||
it('return apply filter for truncate', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
'{{slug | truncate(6)}}',
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', starred: true, done: false }),
|
||||
),
|
||||
).toBe('backen...');
|
||||
});
|
||||
|
||||
it('return apply filter for truncate', () => {
|
||||
expect(
|
||||
compileStringTemplate(
|
||||
"{{slug | truncate(3,'***')}}",
|
||||
date,
|
||||
'backendSlug',
|
||||
fromJS({ slug: 'entrySlug', starred: true, done: false }),
|
||||
),
|
||||
).toBe('bac***');
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandPath', () => {
|
||||
it('should expand wildcard paths', () => {
|
||||
const data = {
|
||||
categories: [
|
||||
{
|
||||
name: 'category 1',
|
||||
},
|
||||
{
|
||||
name: 'category 2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(expandPath({ data, path: 'categories.*.name' })).toEqual([
|
||||
'categories.0.name',
|
||||
'categories.1.name',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle wildcard at the end of the path', () => {
|
||||
const data = {
|
||||
nested: {
|
||||
otherNested: {
|
||||
list: [
|
||||
{
|
||||
title: 'title 1',
|
||||
nestedList: [{ description: 'description 1' }, { description: 'description 2' }],
|
||||
},
|
||||
{
|
||||
title: 'title 2',
|
||||
nestedList: [{ description: 'description 2' }, { description: 'description 2' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(expandPath({ data, path: 'nested.otherNested.list.*.nestedList.*' })).toEqual([
|
||||
'nested.otherNested.list.0.nestedList.0',
|
||||
'nested.otherNested.list.0.nestedList.1',
|
||||
'nested.otherNested.list.1.nestedList.0',
|
||||
'nested.otherNested.list.1.nestedList.1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle non wildcard index', () => {
|
||||
const data = {
|
||||
categories: [
|
||||
{
|
||||
name: 'category 1',
|
||||
},
|
||||
{
|
||||
name: 'category 2',
|
||||
},
|
||||
],
|
||||
};
|
||||
const path = 'categories.0.name';
|
||||
|
||||
expect(expandPath({ data, path })).toEqual(['categories.0.name']);
|
||||
});
|
||||
});
|
||||
});
|
||||
8
source/admin/packages/decap-cms-lib-widgets/src/index.ts
Normal file
8
source/admin/packages/decap-cms-lib-widgets/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as stringTemplate from './stringTemplate';
|
||||
import * as validations from './validations';
|
||||
|
||||
export const DecapCmsLibWidgets = {
|
||||
stringTemplate,
|
||||
validations,
|
||||
};
|
||||
export { stringTemplate, validations };
|
||||
@@ -0,0 +1,250 @@
|
||||
import { Map } from 'immutable';
|
||||
import get from 'lodash/get';
|
||||
import trimEnd from 'lodash/trimEnd';
|
||||
import truncate from 'lodash/truncate';
|
||||
import dayjs from 'dayjs';
|
||||
import { basename, dirname, extname } from 'path';
|
||||
|
||||
const filters = [
|
||||
{ pattern: /^upper$/, transform: (str: string) => str.toUpperCase() },
|
||||
{
|
||||
pattern: /^lower$/,
|
||||
transform: (str: string) => str.toLowerCase(),
|
||||
},
|
||||
{
|
||||
pattern: /^date\('(.+)'\)$/,
|
||||
transform: (str: string, match: RegExpMatchArray) => dayjs(str).format(match[1]),
|
||||
},
|
||||
{
|
||||
pattern: /^default\('(.+)'\)$/,
|
||||
transform: (str: string, match: RegExpMatchArray) => (str ? str : match[1]),
|
||||
},
|
||||
{
|
||||
pattern: /^ternary\('(.*)',\s*'(.*)'\)$/,
|
||||
transform: (str: string, match: RegExpMatchArray) => (str ? match[1] : match[2]),
|
||||
},
|
||||
{
|
||||
pattern: /^truncate\(([0-9]+)(?:(?:,\s*['"])([^'"]*)(?:['"]))?\)$/,
|
||||
transform: (str: string, match: RegExpMatchArray) => {
|
||||
const omission = match[2] || '...';
|
||||
const length = parseInt(match[1]) + omission.length;
|
||||
|
||||
return truncate(str, {
|
||||
length,
|
||||
omission,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const FIELD_PREFIX = 'fields.';
|
||||
const templateContentPattern = ' *([^}{| ]+)';
|
||||
const filterPattern = '( \\| ([^}{]+?))? *';
|
||||
const templateVariablePattern = `{{${templateContentPattern}${filterPattern}}}`;
|
||||
|
||||
// prepends a Zero if the date has only 1 digit
|
||||
function formatDate(date: number) {
|
||||
return `0${date}`.slice(-2);
|
||||
}
|
||||
|
||||
export const dateParsers: Record<string, (date: Date) => string> = {
|
||||
year: (date: Date) => `${date.getUTCFullYear()}`,
|
||||
month: (date: Date) => formatDate(date.getUTCMonth() + 1),
|
||||
day: (date: Date) => formatDate(date.getUTCDate()),
|
||||
hour: (date: Date) => formatDate(date.getUTCHours()),
|
||||
minute: (date: Date) => formatDate(date.getUTCMinutes()),
|
||||
second: (date: Date) => formatDate(date.getUTCSeconds()),
|
||||
};
|
||||
|
||||
export function parseDateFromEntry(entry: Map<string, unknown>, dateFieldName?: string | null) {
|
||||
if (!dateFieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateValue = entry.getIn(['data', dateFieldName]);
|
||||
const dateDayjs = dateValue && dayjs(dateValue);
|
||||
if (dateDayjs && dateDayjs.isValid()) {
|
||||
return dateDayjs.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
export const SLUG_MISSING_REQUIRED_DATE = 'SLUG_MISSING_REQUIRED_DATE';
|
||||
|
||||
export function keyToPathArray(key?: string) {
|
||||
if (!key) {
|
||||
return [];
|
||||
}
|
||||
const parts = [];
|
||||
const separator = '';
|
||||
const chars = key.split(separator);
|
||||
|
||||
let currentChar;
|
||||
let currentStr = [];
|
||||
while ((currentChar = chars.shift())) {
|
||||
if (['[', ']', '.'].includes(currentChar)) {
|
||||
if (currentStr.length > 0) {
|
||||
parts.push(currentStr.join(separator));
|
||||
}
|
||||
currentStr = [];
|
||||
} else {
|
||||
currentStr.push(currentChar);
|
||||
}
|
||||
}
|
||||
if (currentStr.length > 0) {
|
||||
parts.push(currentStr.join(separator));
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
export function expandPath({
|
||||
data,
|
||||
path,
|
||||
paths = [],
|
||||
}: {
|
||||
data: Record<string, unknown>;
|
||||
path: string;
|
||||
paths?: string[];
|
||||
}) {
|
||||
if (path.endsWith('.*')) {
|
||||
path = path + '.';
|
||||
}
|
||||
|
||||
const sep = '.*.';
|
||||
const parts = path.split(sep);
|
||||
if (parts.length === 1) {
|
||||
paths.push(path);
|
||||
} else {
|
||||
const partialPath = parts[0];
|
||||
const value = get(data, partialPath);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((_, index) => {
|
||||
expandPath({
|
||||
data,
|
||||
path: trimEnd(`${partialPath}.${index}.${parts.slice(1).join(sep)}`, '.'),
|
||||
paths,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Allow `fields.` prefix in placeholder to override built in replacements
|
||||
// like "slug" and "year" with values from fields of the same name.
|
||||
function getExplicitFieldReplacement(key: string, data: Map<string, unknown>) {
|
||||
if (!key.startsWith(FIELD_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
const fieldName = key.slice(FIELD_PREFIX.length);
|
||||
const value = data.getIn(keyToPathArray(fieldName));
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getFilterFunction(filterStr: string) {
|
||||
if (filterStr) {
|
||||
let match: RegExpMatchArray | null = null;
|
||||
const filter = filters.find(filter => {
|
||||
match = filterStr.match(filter.pattern);
|
||||
return !!match;
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
return (str: string) => filter.transform(str, match as RegExpMatchArray);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function compileStringTemplate(
|
||||
template: string,
|
||||
date: Date | undefined | null,
|
||||
identifier = '',
|
||||
data = Map<string, unknown>(),
|
||||
processor?: (value: string) => string,
|
||||
) {
|
||||
let missingRequiredDate;
|
||||
|
||||
// Turn off date processing (support for replacements like `{{year}}`), by passing in
|
||||
// `null` as the date arg.
|
||||
const useDate = date !== null;
|
||||
|
||||
const compiledString = template.replace(
|
||||
RegExp(templateVariablePattern, 'g'),
|
||||
(_full, key: string, _part, filter: string) => {
|
||||
let replacement;
|
||||
const explicitFieldReplacement = getExplicitFieldReplacement(key, data);
|
||||
|
||||
if (explicitFieldReplacement) {
|
||||
replacement = explicitFieldReplacement;
|
||||
} else if (dateParsers[key] && !date) {
|
||||
missingRequiredDate = true;
|
||||
return '';
|
||||
} else if (dateParsers[key]) {
|
||||
replacement = dateParsers[key](date as Date);
|
||||
} else if (key === 'slug') {
|
||||
replacement = identifier;
|
||||
} else {
|
||||
replacement = data.getIn(keyToPathArray(key), '') as string;
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
return processor(replacement);
|
||||
} else {
|
||||
const filterFunction = getFilterFunction(filter);
|
||||
if (filterFunction) {
|
||||
replacement = filterFunction(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
return replacement;
|
||||
},
|
||||
);
|
||||
|
||||
if (useDate && missingRequiredDate) {
|
||||
const err = new Error();
|
||||
err.name = SLUG_MISSING_REQUIRED_DATE;
|
||||
throw err;
|
||||
} else {
|
||||
return compiledString;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractTemplateVars(template: string) {
|
||||
const regexp = RegExp(templateVariablePattern, 'g');
|
||||
const contentRegexp = RegExp(templateContentPattern, 'g');
|
||||
const matches = template.match(regexp) || [];
|
||||
return matches.map(elem => {
|
||||
const match = elem.match(contentRegexp);
|
||||
return match ? match[0] : '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends `dirname`, `filename` and `extension` to the provided `fields` map.
|
||||
* @param entryPath
|
||||
* @param fields
|
||||
* @param folder - optionally include a folder that the dirname will be relative to.
|
||||
* eg: `addFileTemplateFields('foo/bar/baz.ext', fields, 'foo')`
|
||||
* will result in: `{ dirname: 'bar', filename: 'baz', extension: 'ext' }`
|
||||
*/
|
||||
export function addFileTemplateFields(entryPath: string, fields: Map<string, string>, folder = '') {
|
||||
if (!entryPath) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
const extension = extname(entryPath);
|
||||
const filename = basename(entryPath, extension);
|
||||
const dirnameExcludingFolder = dirname(entryPath).replace(new RegExp(`^(/?)${folder}/?`), '$1');
|
||||
fields = fields.withMutations(map => {
|
||||
map.set('dirname', dirnameExcludingFolder);
|
||||
map.set('filename', filename);
|
||||
map.set('extension', extension === '' ? extension : extension.slice(1));
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import isNumber from 'lodash/isNumber';
|
||||
|
||||
import type { List } from 'immutable';
|
||||
|
||||
export function validateMinMax(
|
||||
t: (key: string, options: unknown) => string,
|
||||
fieldLabel: string,
|
||||
value?: List<unknown>,
|
||||
min?: number,
|
||||
max?: number,
|
||||
) {
|
||||
function minMaxError(messageKey: string) {
|
||||
return {
|
||||
type: 'RANGE',
|
||||
message: t(`editor.editorControlPane.widget.${messageKey}`, {
|
||||
fieldLabel,
|
||||
minCount: min,
|
||||
maxCount: max,
|
||||
count: min,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if ([min, max, value?.size].every(isNumber) && (value!.size < min! || value!.size > max!)) {
|
||||
return minMaxError(min === max ? 'rangeCountExact' : 'rangeCount');
|
||||
} else if (isNumber(min) && min > 0 && value?.size && value.size < min) {
|
||||
return minMaxError('rangeMin');
|
||||
} else if (isNumber(max) && value?.size && value.size > max) {
|
||||
return minMaxError('rangeMax');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
const { getConfig } = require('../../scripts/webpack.js');
|
||||
|
||||
module.exports = getConfig();
|
||||
Reference in New Issue
Block a user