diff --git a/dev-test/config.yml b/dev-test/config.yml index 6eac43f98db9..856c95a1cc85 100644 --- a/dev-test/config.yml +++ b/dev-test/config.yml @@ -1,284 +1,1229 @@ +local_backend: true + backend: - name: test-repo - -site_url: https://example.com - -publish_mode: editorial_workflow -media_folder: assets/uploads - -collections: # A list of collections the CMS should be able to edit - - name: 'posts' # Used in routes, ie.: /admin/collections/:slug/edit - label: 'Posts' # Used in the UI - label_singular: 'Post' # Used in the UI, ie: "New Post" - description: > - The description is a great place for tone setting, high level information, and editing - guidelines that are specific to a collection. - folder: '_posts' - slug: '{{year}}-{{month}}-{{day}}-{{slug}}' - summary: '{{title}} -- {{year}}/{{month}}/{{day}}' - create: true # Allow users to create new documents in this collection - editor: - visualEditing: true - view_filters: - - label: Posts With Index - field: title - pattern: 'This is post #' - - label: Posts Without Index - field: title - pattern: front matter post - - label: Drafts - field: draft - pattern: true - view_groups: - - label: Year - field: date - pattern: \d{4} - - label: Drafts - field: draft - fields: # The fields each document in this collection have - - { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' } - - { label: 'Draft', name: 'draft', widget: 'boolean', default: false } - - { - label: 'Publish Date', - name: 'date', - widget: 'datetime', - format: 'YYYY-MM-DD HH:mm', - default: '{{now}}', - } - - label: 'Cover Image' - name: 'image' - widget: 'image' - required: false - tagname: '' - - - { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' } - - - name: 'restaurants' # Used in routes, ie.: /admin/collections/:slug/edit - label: 'Restaurants' # Used in the UI - label_singular: 'Restaurant' # Used in the UI, ie: "New Post" - description: > - Restaurants is an entry type used for testing galleries, relations and other widgets. - The tests must be written in such way that adding new fields does not affect previous flows. - folder: '_restaurants' - slug: '{{year}}-{{month}}-{{day}}-{{slug}}' - summary: '{{title}} -- {{year}}/{{month}}/{{day}}' - create: true # Allow users to create new documents in this collection + name: git-gateway + branch: __BRANCH__ + squash_merges: true + +display_url: https://moc-www.netlify.app/ +logo_url: https://res.cloudinary.com/celje/image/upload/v1731056244/logo.svg +media_folder: static/media +public_folder: /media +# publish_mode: editorial_workflow +editor: + preview: false + +media_library: + name: cloudinary + config: + cloud_name: celje + api_key: 827242637598592 + +i18n: + structure: multiple_files + locales: [sl, en] + +slug: + encoding: ascii + clean_accents: true + +aliases: + - &VISIBLE_IN_CMS {name: visibleInCMS, widget: hidden, default: true} + - &LINK_TITLE {name: linkTitle, label: link title, widget: string, i18n: true, required: false, hint: 'Shown on links to this page. If empty, page title will be used'} + - &TITLE {name: title, label: title, widget: string, i18n: true} + - &TITLE_OPTIONAL {name: title, label: title, widget: string, i18n: true, required: false} + - &DRAFT {name: draft, widget: boolean, default: false, i18n: true, required: false} + - &WEIGHT {name: weight, label: Weight, widget: number, required: false, i18n: duplicate} + - &KEYWORDS {name: keywords, widget: list, required: false, i18n: true, label_singular: keyword, summary: '{{keyword}}', hint: used for determining related content, field: {name: keyword, widget: string, i18n: true}} + - &HIDE_FROM_RELATED {name: hideFromRelated, label: hide from related, widget: boolean, i18n: duplicate, required: false, hint: 'if checked, this page will not be shown as related content on other pages'} + - &URL {name: url, label: URL, widget: string, i18n: true, required: false, pattern: ['^[a-z0-9-/]+$', 'Enter a valid relative URL'], hint: 'Enter a valid relative URL. Do not use http links. Example: /en/current/news.'} + - &SLUG {name: slug, label: Slug, widget: string, i18n: true, required: false, pattern: ['^[a-z0-9-]+$', 'Enter a valid slug']} + - &SHORT_DESCRIPTION {name: shortDescription, label: short description, widget: text, i18n: true, pattern: ['^[\s\S]{1,140}$', "Max 140 characters"], required: false} + - &DESCRIPTION {name: description, label: description, widget: text, i18n: true, required: false} + - &IS_HIDDEN {name: isHidden, label: is hidden, widget: boolean, i18n: true, required: false} + - &PUBLISH_DATE {name: publishDate, label: publish date, widget: datetime, time_format: false, i18n: duplicate, required: false, default: '{{now}}'} + - &DATE {name: date, widget: datetime, i18n: duplicate, time_format: false} + - &DEFAULT_EDITOR_COMPONENTS [accordions, button, content-left-right, file-link, highlight-box, media-module] + - &BODY {name: body, label: body, widget: markdown, i18n: true, editor_components: *DEFAULT_EDITOR_COMPONENTS} + - &BODY_OPTIONAL {name: body, label: body, widget: markdown, i18n: true, required: false, editor_components: *DEFAULT_EDITOR_COMPONENTS} + - &HEADER_SEARCH {name: headerSearch, label: header search, widget: object, summary: '{{fields.title}}', collapsed: true, i18n: true, required: false, fields: [ + {name: isOpen, label: is initially open, widget: boolean, i18n: duplicate, required: false}, + {name: searchFor, label: search for, widget: string, i18n: true, required: false}, + {name: placeholder, widget: string, i18n: true, required: false}, + ]} + - &ALL_ITEMS_TITLE {name: allItemsTitle, label: all items title, widget: string, i18n: true, required: false} + - &IS_EXPOSED {name: isExposed, label: Is exposed, widget: boolean, i18n: duplicate, required: false} + - &IS_EXCLUDED_FROM_RELATED {name: isExcludedFromRelated, label: is excluded from related, widget: boolean, i18n: true, required: false, default: false} + - &RELATED_ITEMS {name: relatedCta, label: related cta, widget: object, summary: '{{fields.title}}', collapsed: true, i18n: true, required: false, fields: [ + *TITLE_OPTIONAL, + {name: allItems, label: all items cta, widget: string, i18n: true, required: false}, + {name: nameColumn, label: name column, widget: string, i18n: true, required: false}, + ]} + + - &IMAGE_FILE {name: image, widget: image, i18n: duplicate} + - &IMAGE_OBJECT_FIELDS [ + {name: src, widget: image, i18n: duplicate, required: false}, + {name: alt, widget: string, i18n: true, required: false}, + {name: title, widget: string, i18n: true, required: false}, + ] + - &IMAGE_OBJECT {name: image, widget: object, i18n: true, collapsed: true, summary: '{{fields.src}}', fields: *IMAGE_OBJECT_FIELDS} + - &IMAGE_OBJECT_COLLAPSED {name: image, widget: object, i18n: true, collapsed: true, summary: '{{fields.src}}', fields: *IMAGE_OBJECT_FIELDS} + + - &BUTTON_FIELDS [{name: text, widget: string, i18n: true}, {name: href, widget: string, i18n: true}] + - &BUTTON_FIELDS_OPTIONAL [ + {name: text, widget: string, i18n: true, required: false}, + {name: href, widget: string, i18n: true, required: false}, + ] + - &BUTTON {name: button, widget: object, i18n: true, fields: *BUTTON_FIELDS_OPTIONAL} + - &PAGE_HERO {name: pageHero, label: Page hero, widget: object, collapsed: true, summary: '{{fields.title}}', fields: [ + {name: title, widget: string, i18n: true, required: false, hint: 'if empty, page title will be used'}, + {name: description, widget: string, i18n: true, required: false, hint: 'if empty, page description will be used'}, + *BUTTON, + {label: Image, name: image, widget: object, fields: *IMAGE_OBJECT_FIELDS, required: false, hint: 'if empty, page image will be used'}, + {name: variant, widget: select, i18n: duplicate, required: false, options: [ + {label: normal image (default), value: default}, + {label: no image, value: no-image}, + {label: large image, value: image-lg}, + {label: extra large image, value: image-xl}, + ]}, + ]} + - &USEFUL_INFO {name: usefulInfo, label: useful info, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *IS_HIDDEN, + {name: title, widget: string, i18n: true, required: false}, + {name: text, widget: markdown, minimal: true, required: false, editor_components: []}, + *BUTTON, + {name: linksTitle, label: links title, widget: string, i18n: true, required: false}, + {name: links, widget: list, label_singular: link, i18n: true, fields: *BUTTON_FIELDS}, + ]} + - &CONTENT_LEFT_RIGHT {name: contentLeftRight, label: Content left-right, widget: object, i18n: true, fields: [ + {name: items, widget: list, label_singular: item, i18n: true, fields: [ + *TITLE, + {name: body, widget: markdown, i18n: true, editor_components: [button, file-link, media-module]}, + ]}, + ]} + - &GALLERY {name: gallery, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + {name: items, widget: list, label_singular: item, i18n: true, fields: *IMAGE_OBJECT_FIELDS}, + ]} + - &MAP {name: map, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE_OPTIONAL, + *DESCRIPTION, + *BUTTON, + {name: src, widget: string, i18n: duplicate, required: false}, + ]} + - &COLOR {name: color, label: color, widget: select, i18n: duplicate, options: [ + {label: primary dark blue, value: primary-dark-blue}, + {label: primary blue, value: primary-blue}, + {label: primary yellow, value: primary-yellow}, + {label: primary red, value: primary-red}, + {label: primary pink, value: primary-pink}, + {label: primary light green, value: primary-light-green}, + {label: primary green, value: primary-green}, + {label: primary mint, value: primary-mint}, + {label: secondary dark blue, value: secondary-dark-blue}, + {label: secondary blue, value: secondary-blue}, + {label: secondary yellow, value: secondary-yellow}, + {label: secondary red, value: secondary-red}, + {label: secondary pink, value: secondary-pink}, + {label: secondary light green, value: secondary-light-green}, + {label: secondary green, value: secondary-green}, + {label: secondary mint, value: secondary-mint}, + {label: tertiary dark blue, value: tertiary-dark-blue}, + {label: tertiary blue, value: tertiary-blue}, + {label: tertiary yellow, value: tertiary-yellow}, + {label: tertiary red, value: tertiary-red}, + {label: tertiary pink, value: tertiary-pink}, + {label: tertiary light green, value: tertiary-light-green}, + {label: tertiary green, value: tertiary-green}, + {label: tertiary mint, value: tertiary-mint}, + {label: white, value: white}, + {label: grey 100, value: grey-100}, + {label: grey 400, value: grey-400}, + {label: grey 600, value: grey-600}, + {label: grey 700, value: grey-700}, + {label: grey 800, value: grey-800}, + {label: black, value: black}, + ]} + - &HAS_NEWSLETTER {name: hasNewsletter, label: show newsletter module, widget: boolean, i18n: duplicate, required: false} + + - &THEME_COLOR {name: themeColor, label: theme color, widget: select, required: false, i18n: duplicate, options: ['blue', 'dark-blue', 'yellow', 'red', 'pink', 'light-green', 'green', 'mint']} + - &THEME_COLOR_CHILDREN {name: cascade, label: theme of subpages, widget: object, summary: '{{fields.title}}', collapsed: true, hint: 'all subpages will have this theme, except if otherwise defined on a specific page', fields: [ + {name: params, widget: object, fields: [*THEME_COLOR]}, + ]} + - &BACKGROUND {name: background, widget: select, i18n: duplicate, required: false, options: [ + {label: no background, value: 'no-bg'}, + {label: grey, value: grey-100}, + {label: secondary theme color, value: secondary}, + {label: tertiary theme color, value: tertiary}, + ]} + - &DISTRICTS_RELATION {name: districts, widget: relation, collection: districts, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true} + - &AREAS_RELATION {name: areas, widget: relation, collection: taxonomies, file: areas, i18n: duplicate, required: false, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true} + - &CONTENT_AREAS_RELATION {name: content_areas, widget: relation, collection: taxonomies, file: content_areas, i18n: duplicate, required: false, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true} + - &TARGET_GROUPS_RELATION {name: target_groups, label: target groups, widget: relation, collection: taxonomies, file: target_groups, i18n: duplicate, required: false, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true} + - &FUND_TYPES_RELATION {name: fund_types, label: fund types, widget: relation, collection: taxonomies, file: fund_types, i18n: duplicate, required: false, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true} + - &THEMES_RELATION {name: themes, widget: relation, collection: taxonomies, file: themes, i18n: duplicate, required: false, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true} + - &CATEGORIES_RELATION {name: categories, widget: relation, collection: categories, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true} + + - &TAXO_FILE_FIELDS [ + {name: items, widget: list, label_singular: item, i18n: true, fields: [ + {name: title, widget: string, i18n: true}, + {name: id, widget: string, i18n: duplicate}, + ]}, + ] + + - &TAXONOMIES {name: taxonomies, widget: list, label_singular: taxonomy, i18n: true, fields: [ + {name: id, widget: select, options: [tags, categories, districts, areas, content_areas, themes, target_groups, fund_types, prices, venue, years, periods, publish_date, institution, self_government, planning, communal_service, real_estate, public_finance, social_service, development], i18n: duplicate}, + {name: label, widget: string, i18n: true, required: false}, + {name: isVisible, label: is visible, widget: boolean, i18n: duplicate, required: false}, + {name: isExpanded, label: is expanded, widget: boolean, i18n: duplicate, required: false}, + ]} + + - &CAROUSEL {name: carousel, widget: list, label_singular: item, summary: '{{fields.title}}', min: 0, max: 5, i18n: true, required: false, fields: [ + {name: pages, widget: list, label_singular: page, min: 1, max: 1, i18n: true, types: [ + {name: novice, widget: object, field: { + name: relation, label: '', widget: relation, collection: novice, i18n: true, value_field: '{{dirname}}/{{slug}}', search_fields: [title], display_fields: [title] + }}, + {name: dogodki, widget: object, field: { + name: relation, label: '', widget: relation, collection: dogodki, i18n: true, value_field: '{{dirname}}/{{slug}}', search_fields: [title], display_fields: [title] + }}, + {name: pages, label: other, widget: object, field: { + name: relation, label: '', widget: relation, collection: pages, i18n: true, value_field: '{{dirname}}/{{slug}}', search_fields: [title], display_fields: [title] + }}, + ]}, + *IMAGE_OBJECT_COLLAPSED, + ]} + + - "E {name: quote, widget: object, i18n: true, collapsed: true, summary: '{{fields.author}}', fields: [ + {name: text, widget: text, i18n: true}, + {name: author, widget: string, i18n: true}, + *IMAGE_OBJECT, + ]} + + - &FAQ {name: faq, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE_OPTIONAL, + *DESCRIPTION, + {name: items, widget: list, label_singular: item, i18n: true, fields: [ + *TITLE, + {name: content, widget: markdown, i18n: true, editor_components: *DEFAULT_EDITOR_COMPONENTS}, + ]}, + ]} + + - &MEDIA_MODULE { name: mediaModule, widget: object, summary: '{{fields.media}}', label: Media module, i18n: true, fields: [ + { name: title, widget: string, i18n: true, required: false }, + { name: media, widget: file, required: false, i18n: true, media_library: { allow_multiple: false }, hint: 'Image or video. External video must allow to be embedded in iframe, example: https://www.youtube.com/embed/3XHRg571FPw?si=j7KXUC2W417KSqmw' }, + { name: alt, widget: string, i18n: true, hint: 'Alternative text', required: false }, + { name: size, widget: select, options: [small, medium, large], i18n: true, required: false }, + {name: caption, widget: text, i18n: true, required: false}, + ]} + + - &RELATED_LINKS { name: relatedLinks, widget: object, label: Related links, summary: '{{fields.title}}', collapsed: true, i18n: true, fields: [ + *TITLE_OPTIONAL, + *IS_HIDDEN, + { name: items, widget: list, label_singular: item, i18n: true, fields: [ + *TITLE, + *DESCRIPTION, + { name: href, widget: string, i18n: true }, + ]}, + ]} + + - &LINKS_WITH_ICONS_FIELDS [ + *TITLE, + *BACKGROUND, + {name: items, widget: list, label_singular: item, min: 3, max: 6, i18n: true, fields: [ + *TITLE, + {name: text, widget: text, i18n: true}, + *BUTTON, + *IMAGE_OBJECT, + ]}, + ] + + - &EXPOSURE_LIST_FIELDS [ + *TITLE, + *BACKGROUND, + {name: items, widget: list, label_singular: item, min: 1, max: 4, i18n: true, fields: [ + *TITLE, + {name: text, widget: text, i18n: true}, + *BUTTON, + *IMAGE_OBJECT, + ]}, + ] + + - &TEXT_IMAGE_FIELDS [ + *TITLE, + {name: isReverse, label: is reverse, widget: boolean, i18n: duplicate, required: false, hint: 'if checked, image will be on the right side'}, + {name: content, widget: markdown, minimal: true, i18n: true, editor_components: *DEFAULT_EDITOR_COMPONENTS}, + *BUTTON, + *IMAGE_OBJECT, + ] + + - &EVENT_TEXT_IMAGE_FIELDS [ + *TITLE, + *IS_HIDDEN, + {name: content, widget: markdown, minimal: true, i18n: true, editor_components: *DEFAULT_EDITOR_COMPONENTS}, + *BUTTON, + *IMAGE_OBJECT, + ] + + - &CARD_LIST_FIELDS [ + *TITLE, + *BACKGROUND, + *DESCRIPTION, + *BUTTON, + {name: source, widget: select, i18n: true, required: false, options: ['/aktualno/novice', '/aktualno/dogodki', '/aktualno/projekti', '/aktualno/razpisi']}, + {name: items, widget: list, label_singular: item, i18n: true, required: false, fields: [ + *TITLE, + *DESCRIPTION, + *IMAGE_OBJECT, + {name: link, widget: string, i18n: true, required: false}, + ]}, + ] + + - &CARD_LIST_TEXT_FIELDS [ + *TITLE, + *BACKGROUND, + {name: rowsOrSlider, label: rows or slider, widget: select, i18n: true, default: carousel, options: ['rows', 'slider'], required: false}, + *DESCRIPTION, + *BUTTON, + {name: source, widget: select, i18n: true, required: false, options: ['/aktualno/novice', '/aktualno/dogodki', '/aktualno/projekti', '/aktualno/razpisi']}, + {name: items, widget: list, label_singular: item, i18n: true, required: false, fields: [ + *TITLE, + *DESCRIPTION, + *IMAGE_OBJECT, + {name: link, widget: string, i18n: true, required: false}, + ]}, + ] + + - &DEPARTMENT_LINK {name: departmentLink, label: About module, widget: object, summary: '{{fields.department}}', fields: [ + {name: department, label: Department, widget: relation, collection: departments, value_field: '{{slug}}', search_fields: ["title"], display_fields: ["title"]} + ]} + + - &ICONS_WITH_NUMBERS {name: iconsWithNumbers, label: Icons with numbers, widget: object, summary: '{{fields.title}}', collapsed: true, i18n: true, fields: [ + *TITLE_OPTIONAL, + *BACKGROUND, + {name: items, widget: list, label_singular: item, i18n: true, min: 1, max: 4, required: false, fields: [ + *TITLE, + *DESCRIPTION, + ]}, + ]} + + - &MODULES {name: modules, widget: list, i18n: true, types: [ + *CONTENT_LEFT_RIGHT, + *GALLERY, + *MEDIA_MODULE, + *MAP, + *DEPARTMENT_LINK, + *ICONS_WITH_NUMBERS, + *QUOTE, + {name: exposureList, label: Exposure list, widget: object, summary: '{{fields.title}}', i18n: true, fields: *EXPOSURE_LIST_FIELDS}, + {name: textImageList, label: text image list, widget: object, i18n: true, fields: [ + *BACKGROUND, + {name: items, widget: list, label_singular: item, i18n: true, fields: *TEXT_IMAGE_FIELDS}, + ]}, + {name: cardList, label: Card list, widget: object, summary: '{{fields.title}}', i18n: true, fields: *CARD_LIST_FIELDS}, + {name: cardListText, label: Card list with side text, widget: object, summary: '{{fields.title}}', i18n: true, fields: *CARD_LIST_TEXT_FIELDS}, + ]} + + - &HOME_FIELDS [ + *TITLE, + {name: hero, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE, + *IMAGE_OBJECT, + {name: searchFor, label: search for, hint: the first word in the input placeholder, widget: string, i18n: true}, + {name: placeholders, hint: texts that exchange in the input palceholder, widget: list, label_singular: item, i18n: true, summary: '{{name}}', field: { + name: name, widget: string, i18n: true + }}, + ]}, + {name: mainLinks, label: main links, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: *LINKS_WITH_ICONS_FIELDS}, + {name: carousel, widget: list, i18n: true, label_singular: item, fields: [ + *TITLE, + *DESCRIPTION, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true}, + *IMAGE_OBJECT, + *BUTTON, + ]}, + {name: newsList, label: news list, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE, + *IS_HIDDEN, + *BUTTON, + ]}, + {name: banners, widget: object, i18n: true, fields: [ + *BACKGROUND, + {name: items, widget: list, label_singular: item, i18n: true, fields: *TEXT_IMAGE_FIELDS}, + ]}, + {name: projectsList, label: projects list, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE, + *IS_HIDDEN, + *DESCRIPTION, + *BUTTON, + ]}, + {name: priorities, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: *EXPOSURE_LIST_FIELDS}, + {name: tendersList, label: tenders list, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE, + *IS_HIDDEN, + *IMAGE_OBJECT, + *BUTTON, + ]}, + {name: eventsList, label: events list, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE, + *IS_HIDDEN, + *BUTTON, + ]}, + {name: logos, widget: object, i18n: true, fields: [ + *TITLE, + {name: items, widget: list, label_singular: item, i18n: true, fields: [ + *TITLE, + {name: image, widget: image, i18n: true}, + {name: href, widget: string, i18n: true}, + ]}, + *BUTTON, + ]}, + ] + + - &EVENTS_LIST_FIELDS [ + *TITLE, + *DRAFT, + *SLUG, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + {name: exposed, label: exposed list, widget: object, collapsed: true, summary: '{{fields.title}}', i18n: true, fields: [ + *IS_HIDDEN, + *TITLE_OPTIONAL, + *BUTTON, + ]}, + {name: exposedEventTop, label: exposed event top, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: *EVENT_TEXT_IMAGE_FIELDS}, + {name: sportList, label: sport list, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + ]}, + {name: museumList, label: museum list, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + ]}, + {name: childrenList, label: children list, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + ]}, + {name: exposedEventMiddle, label: exposed event middle, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: *EVENT_TEXT_IMAGE_FIELDS}, + {name: musicList, label: music list, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + ]}, + {name: theaterList, label: theater list, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + ]}, + {name: exposedEventBottom, label: exposed event bottom, widget: object, i18n: true, collapsed: true, summary: '{{fields.title}}', fields: *EVENT_TEXT_IMAGE_FIELDS}, + *THEME_COLOR, + *HAS_NEWSLETTER, + *KEYWORDS, + ] + + - &LIST_PAGE_FIELDS [ + *TITLE, + *DRAFT, + *URL, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *USEFUL_INFO, + {name: mainLinks, label: main links, widget: object, i18n: true, summary: '{{fields.title}}', collapsed: true, fields: [ + *TITLE_OPTIONAL, + {name: useCustomLinks, label: use custom links, widget: boolean, i18n: true, required: false, hint: 'Turn this on if you need several sections (manually add them below). If off, links will be generated from child pages (custom sections will be ignored).'}, + {name: sections, label_singular: section, widget: list, i18n: true, fields: [ + *TITLE_OPTIONAL, + {name: links, label: links, widget: list, i18n: true, label_singular: link, fields: [ + *TITLE, + *DESCRIPTION, + {name: link, widget: string, i18n: true}, + ]}, + ]}, + ]}, + *ICONS_WITH_NUMBERS, + *BODY_OPTIONAL, + *MODULES, + {name: enableMapView, label: Is map view enabled, widget: boolean, i18n: duplicate, required: false}, + *MAP, + *RELATED_LINKS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *HAS_NEWSLETTER, + *HEADER_SEARCH, + *THEME_COLOR, + *THEME_COLOR_CHILDREN, + {name: exposed, widget: object, summary: '{{fields.title}}', collapsed: true, i18n: true, fields: [ + *IS_HIDDEN, + *TITLE_OPTIONAL, + ]}, + *ALL_ITEMS_TITLE, + *TAXONOMIES, + *RELATED_ITEMS, + ] + + - &SINGLE_FIELDS [ + *TITLE, + *DRAFT, + *SLUG, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *USEFUL_INFO, + *BODY, + *MODULES, + *RELATED_LINKS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *THEME_COLOR, + *VISIBLE_IN_CMS, + ] + + - &FEE_CALCULATOR_FIELDS [ + *TITLE, + *DRAFT, + *SLUG, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *USEFUL_INFO, + {name: form, widget: object, fields: [ + *TITLE, + *DESCRIPTION, + {name: size_plot, label: plot size, widget: string}, + {name: size_building, label: building size, widget: string}, + {name: building_hint, label: building hint, widget: string}, + {name: building_type, label: building type, widget: object, collapsed: true, fields: [ + {name: placeholder, widget: string}, + {name: options, widget: list, label_singular: option, summary: '{{fields.name}}', fields: [ + {name: name, widget: string}, + {name: number, widget: number}, + {name: factor, widget: number, value_type: float}, + ]}, + ]}, + {name: amenities, widget: object, collapsed: true, fields: [ + {name: legend, widget: string}, + {name: items, widget: list, label_singular: item, summary: '{{fields.name}}', fields: [ + {name: name, widget: string}, + {name: id, widget: string}, + {name: checked, widget: boolean, required: false}, + {name: cpij, widget: number, value_type: float}, + {name: ctij, widget: number, value_type: float}, + {name: prispevne_stopnje, widget: number}, + ]}, + ]}, + {name: submit, widget: string}, + {name: dp, label: delež površine parcele pri izračunu komunalnega prispevka (0,3 - 0,7), widget: number, value_type: float}, + {name: dt, label: delež neto tlorisne površine pri izračunu komunalnega prispevka (0,7 - 0,3), widget: number, value_type: float}, + ]}, + *BODY, + *MODULES, + *RELATED_LINKS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *THEME_COLOR, + ] + + - &CONTACT_FIELDS [ + *TITLE, + *IMAGE_OBJECT, + {name: contactHero, label: Contact hero, widget: object, i18n: true, fields: [ + {name: address, widget: object, i18n: true, fields: [ + *TITLE, + *SHORT_DESCRIPTION, + {name: link, widget: object, i18n: true, fields: *BUTTON_FIELDS}, + ]}, + {name: contact, widget: object, i18n: true, fields: [ + *TITLE, + {label: Email, name: email, widget: string, i18n: true, required: false}, + {label: Phone, name: phone, widget: string, i18n: true, required: false}, + ]}, + {name: meeting, widget: object, i18n: true, fields: [ + *TITLE, + {name: link, widget: object, i18n: true, fields: *BUTTON_FIELDS}, + ]}, + ]}, + {name: officeHours, label: Office hours, widget: object, i18n: true, fields: [ + *TITLE, + {name: departments, widget: list, label_singular: office, i18n: true, fields: [ + *TITLE, + {name: items, widget: list, label_singular: entry, i18n: true, fields: [ + {name: day, widget: string, i18n: true}, + {name: hours, widget: string, i18n: duplicate}, + ]}, + {name: disclaimer, widget: markdown, minimal: true, required: false, editor_components: []}, + ]}, + ]}, + *RELATED_LINKS, + *THEME_COLOR, + ] + + - &MEETING_FORM_FIELDS [ + *TITLE, + *DESCRIPTION, + {name: form, widget: object, i18n: true, fields: [ + *TITLE, + {name: area, widget: object, i18n: true, fields: [ + {name: placeholder, widget: string, i18n: true}, + {name: options, widget: list, label_singular: option, i18n: true, fields: [ + {name: value, widget: string, i18n: true}, + {name: label, widget: string, i18n: true}, + ]}, + ]}, + {name: subject, widget: string, i18n: true}, + {name: neighborhood, widget: string, i18n: true}, + {name: message, widget: string, i18n: true}, + {name: existingContact, widget: object, i18n: true, fields: [ + {name: question, widget: string, i18n: true}, + {name: answerNo, label: answer no, widget: string, i18n: true}, + {name: answerYes, label: answer yes, widget: string, i18n: true}, + {name: service, widget: object, i18n: true, fields: [ + *TITLE, + *DESCRIPTION, + {name: options, widget: list, label_singular: option, i18n: true, fields: [ + {name: value, widget: string, i18n: true}, + {name: label, widget: string, i18n: true}, + ]}, + ]}, + ]}, + {name: attachments, widget: object, i18n: true, fields: [ + *TITLE, + {name: cta, widget: string, i18n: true}, + ]}, + {name: contactInformation, label: contact information, widget: object, i18n: true, fields: [ + *TITLE, + {name: fullName, label: full name, widget: string, i18n: true}, + {name: email, widget: string, i18n: true}, + {name: phone, widget: string, i18n: true}, + {name: company, widget: string, i18n: true}, + {name: additionalPersons, label: additional persons, widget: object, i18n: true, fields: [ + *TITLE, + {name: cta, widget: string, i18n: true}, + ]}, + ]}, + {name: conditions, widget: markdown, i18n: true, editor_components: *DEFAULT_EDITOR_COMPONENTS}, + {name: submit, widget: string, i18n: true}, + {name: successMessage, label: success message, widget: object, i18n: true, fields: [ + *TITLE, + *DESCRIPTION, + ]}, + {name: formSubmitErrorText, label: form submit error text, widget: string, i18n: true}, + {name: fileSizeErrorMessage, label: file size error message, widget: string, i18n: true}, + ]}, + *THEME_COLOR, + *RELATED_LINKS, + ] + + - &NEWSLETTER_FORM_FIELDS [ + *TITLE, + *DESCRIPTION, + *IMAGE_OBJECT, + {name: form, widget: object, fields: [ + *TITLE, + {name: fullName, label: full name, widget: string}, + {name: email, widget: string}, + {name: conditions, widget: markdown, editor_components: *DEFAULT_EDITOR_COMPONENTS}, + {name: submit, widget: string}, + {name: formSubmitErrorText, label: form submit error text, widget: string}, + {name: successMessage, label: success message, widget: object, fields: [ + *TITLE, + *DESCRIPTION, + ]}, + ]}, + *THEME_COLOR, + ] + + - &TRANSLATION_VALUE_FIELD {name: other, label: value, widget: "string"} + - &TRANSLATION_FIELDS [ + { label: "Close Menu", name: "closeMenu", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Open Menu", name: "openMenu", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "searcher", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "search", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "close", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Change Language", name: "changeLanguage", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Read More", name: "readMore", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Year of Completion", name: "year-of-completion", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "value", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "all", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "This Month", name: "this-month", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "This Week", name: "this-week", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "This Weekend", name: "this-weekend", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "today", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "free", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "paid", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "useful", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Responsibility Of", name: "responsibility-of", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Department Details", name: "department-details", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "highlighted", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "related", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Publish Date", name: "publishDate", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "districts", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "funds", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Start of Project", name: "start-of-project", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Project Completion", name: "project-completion", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Application Deadline", name: "application-deadline", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Weekday and Date Format", name: "weekday-and-date-format", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "admission", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Admission Date", name: "admission-date", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Institution", name: "admission-authority", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "session", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "publication", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Published In", name: "published-in", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Published On", name: "published-on", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Publication Index", name: "publication-index", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Effective Date", name: "effective-date", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "status", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Other Details", name: "other-details", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Publication Type", name: "publication-type", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "content", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Plain Text", name: "plain-text", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Field is Required", name: "field-is-required", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "npb", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "No results title", name: "no-results-title", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "No results description", name: "no-results-desc", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Fields with an asterisk are required", name: "fields-with-an-asterisk-are-required", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Please enter valid email address", name: "please-enter-valid-email-address", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "published", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "accepted", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { label: "Valid until", name: "valid-until", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + { name: "e-application", widget: "object", fields: [*TRANSLATION_VALUE_FIELD] }, + ] + +collections: + - name: novice + label: Novice + label_singular: Novica + folder: content/aktualno/novice + create: true + i18n: true + slug: '{{slug}}' editor: - visualEditing: true - fields: # The fields each document in this collection have - - { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' } - - { label: 'Body', name: 'body', widget: 'markdown', hint: 'Main content goes here.' } - - { name: 'gallery', widget: 'image', choose_url: true, media_library: {config: {multiple: true, max_files: 999}}} - - { name: 'post', widget: relation, collection: posts, multiple: true, search_fields: [ "title" ], display_fields: [ "title" ], value_field: "{{slug}}", filters: [ {field: "draft", values: [false]} ] } - - name: authors - label: Authors - label_singular: 'Author' - widget: list - fields: - - { label: 'Name', name: 'name', widget: 'string', hint: 'First and Last' } - - { label: 'Description', name: 'description', widget: 'markdown' } - - - name: 'faq' # Used in routes, ie.: /admin/collections/:slug/edit - label: 'FAQ' # Used in the UI - folder: '_faqs' - create: true # Allow users to create new documents in this collection - fields: # The fields each document in this collection have - - { label: 'Question', name: 'title', widget: 'string', tagname: 'h1' } - - { label: 'Answer', name: 'body', widget: 'markdown' } - - - name: 'settings' - label: 'Settings' - delete: false # Prevent users from deleting documents in this collection + preview: true + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *IS_EXPOSED, + *SHORT_DESCRIPTION, + *DESCRIPTION, + *PUBLISH_DATE, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true, filters: [{field: section, values: [novice]}]}, + *DISTRICTS_RELATION, + *TARGET_GROUPS_RELATION, + *IMAGE_OBJECT, + *USEFUL_INFO, + *BODY, + *MODULES, + *HAS_NEWSLETTER, + ] + + - name: dogodki + label: Dogodki + label_singular: Dogodek + folder: content/aktualno/dogodki + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *EVENTS_LIST_FIELDS + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *IS_EXPOSED, + *SHORT_DESCRIPTION, + *DESCRIPTION, + *PUBLISH_DATE, + {name: venue, widget: string, i18n: true}, + *DATE, + {name: price, widget: select, i18n: duplicate, required: false, options: [ + {label: free, value: free}, + {label: paid, value: paid}, + ]}, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true, filters: [{field: section, values: [dogodki]}]}, + *IMAGE_OBJECT, + *USEFUL_INFO, + *BODY, + *MODULES, + *HAS_NEWSLETTER, + ] + + - name: projekti + label: Projekti + label_singular: Projekt + folder: content/aktualno/projekti + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *IS_EXPOSED, + *PUBLISH_DATE, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true, filters: [{field: section, values: [projekti]}]}, + *DISTRICTS_RELATION, + *AREAS_RELATION, + *FUND_TYPES_RELATION, + {name: yearOfCompletion, label: year of completion, widget: number, i18n: duplicate, required: false}, + {name: value, widget: number, i18n: duplicate, required: false}, + {name: funds, label: Funds, widget: string, i18n: true, required: false}, + {name: yearOfBeginning, label: Start year, widget: number, i18n: duplicate, required: false}, + *IMAGE_OBJECT, + *USEFUL_INFO, + *BODY, + *MODULES, + *HAS_NEWSLETTER, + ] + + - name: razpisi + label: Razpisi + label_singular: Razpis + folder: content/aktualno/razpisi + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: [ + *TITLE, + *DRAFT, + *URL, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *ICONS_WITH_NUMBERS, + *QUOTE, + *ALL_ITEMS_TITLE, + *TAXONOMIES, + *FAQ, + *RELATED_ITEMS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *HEADER_SEARCH, + *THEME_COLOR, + *THEME_COLOR_CHILDREN, + ] + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *PUBLISH_DATE, + {name: deadline, widget: datetime, i18n: duplicate, required: false, time_format: false}, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true, max: 1, filters: [{field: section, values: [projekti]}, {field: color, values: [primary-light-green, grey-400]}]}, + *AREAS_RELATION, + *CATEGORIES_RELATION, + *IS_EXCLUDED_FROM_RELATED, + *BODY, + *USEFUL_INFO, + *MODULES, + ] + + - name: vloge + label: Vloge + label_singular: Vloga + folder: content/aktualno/vloge + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: [ + *TITLE, + *DRAFT, + *URL, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *ALL_ITEMS_TITLE, + *TAXONOMIES, + *FAQ, + *RELATED_ITEMS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *HEADER_SEARCH, + *THEME_COLOR, + *THEME_COLOR_CHILDREN, + ] + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *USEFUL_INFO, + *AREAS_RELATION, + *CONTENT_AREAS_RELATION, + *IS_EXCLUDED_FROM_RELATED, + {name: application_link, label: application link, widget: string, i18n: true, pattern: ['^https?:\/\/.+$', 'Enter a valid external URL'], hint: 'Enter a valid external URL. Example: https://test.com/current/news.'}, + *BODY, + *MODULES, + ] + + - name: predpisi + label: Predpisi + label_singular: Predpis + folder: content/aktualno/predpisi + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: [ + *TITLE, + *LINK_TITLE, + *DRAFT, + *WEIGHT, + *SLUG, + *USEFUL_INFO, + {name: acceptance_date, label: Datum sprejetja, widget: datetime, i18n: duplicate, time_format: false}, + {name: institution, label: Institucija, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: institution, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: self_government, label: Lokalna samouprava, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: self_government, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: planning, label: Planiranje in urejanje prostora, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: planning, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: communal_service, label: Komunala, gospodarske javne službe in promet, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: communal_service, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: real_estate, label: Poslovni prostori in stanovanjske zadeve, najemi, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: real_estate, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: public_finance, label: Javne finance, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: public_finance, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: social_service, label: Družbene dejavnosti, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: social_service, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: development, label: Razvoj, gostinstvo, turizem, malo gospodarstvo, kmetijstvo, required: false, widget: relation, i18n: duplicate, collection: taxonomies, file: development, value_field: 'items.*.id', search_fields: ['items.*.title'], display_fields: ['items.*.title'], multiple: true}, + {name: session, widget: object, i18n: true, fields: *BUTTON_FIELDS}, + {name: published_in, label: published in, widget: object, i18n: true, fields: *BUTTON_FIELDS}, + {name: publication_index, label: publication index, widget: object, i18n: true, fields: *BUTTON_FIELDS}, + {name: publishDate, label: published on, widget: datetime, i18n: duplicate, time_format: false}, + {name: effective_date, label: effective date, widget: datetime, i18n: duplicate, time_format: false}, + {name: tags, widget: relation, collection: tags, i18n: duplicate, required: false, value_field: '{{slug}}', search_fields: [title], display_fields: [title], multiple: true, max: 1, filters: [{field: section, values: [predpisi]}]}, + {name: publication_type, label: publication type, widget: string, i18n: true}, + {name: content_location, label: content location, widget: string, i18n: true}, + {name: plain_text, label: plain text, widget: object, i18n: true, fields: [ + {name: name, widget: string, i18n: true}, + {name: src, widget: file, i18n: true, media_library: { allow_multiple: false }}, + ]}, + *IS_EXCLUDED_FROM_RELATED, + *BODY, + ] + + - name: publikacije + label: Publikacije + label_singular: publication + folder: content/aktualno/publikacije + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: [ + *TITLE, + *DRAFT, + *SLUG, + {name: issueNumber, label: issue number, widget: number, i18n: duplicate}, + *CATEGORIES_RELATION, + *IS_EXPOSED, + *DATE, + *THEMES_RELATION, + *DESCRIPTION, + *IMAGE_OBJECT, + *PAGE_HERO, + *USEFUL_INFO, + *BODY_OPTIONAL, + *MODULES, + *RELATED_LINKS, + *KEYWORDS, + *HIDE_FROM_RELATED, + *THEME_COLOR, + ] + + - name: aktualno + label: Aktualno + label_singular: Stran aktualno + folder: content/aktualno + format: yaml-frontmatter + create: true + i18n: true + filter: {field: visibleInCMS, value: true} + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS editor: preview: false - files: - - name: 'general' - label: 'Site Settings' - file: '_data/settings.json' - description: 'General Site Settings' - fields: - - { label: 'Global title', name: 'site_title', widget: 'string' } - - label: 'Post Settings' - name: posts - widget: 'object' - fields: - - { - label: 'Number of posts on frontpage', - name: front_limit, - widget: number, - min: 1, - max: 10, - } - - { label: 'Default Author', name: author, widget: string } - - { - label: 'Default Thumbnail', - name: thumb, - widget: image, - class: 'thumb', - required: false, - } - - - name: 'authors' - label: 'Authors' - file: '_data/authors.yml' - description: 'Author descriptions' - fields: - - name: authors - label: Authors - label_singular: 'Author' - widget: list - fields: - - { label: 'Name', name: 'name', widget: 'string', hint: 'First and Last' } - - { label: 'Description', name: 'description', widget: 'markdown' } - - - name: 'kitchenSink' # all the things in one entry, for documentation and quick testing - label: 'Kitchen Sink' - folder: '_sink' + nested: + depth: 10 + summary: "{{title}}" + subfolders: false + meta: {path: {widget: string, label: 'Path', index_file: '_index'}} + fields: *SINGLE_FIELDS + + - name: gospodarstvo + label: Gospodarstvo + label_singular: Stran gospodarstvo + folder: content/gospodarstvo + format: yaml-frontmatter create: true - fields: - - label: 'Related Post' - name: 'post' - widget: 'relationKitchenSinkPost' - collection: 'posts' - display_fields: ['title', 'datetime'] - search_fields: ['title', 'body'] - value_field: 'title' - - { label: 'Title', name: 'title', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true } - - { label: 'Map', name: 'map', widget: 'map' } - - { label: 'Text', name: 'text', widget: 'text', hint: 'Plain text, not markdown' } - - { label: 'Number', name: 'number', widget: 'number', hint: 'To infinity and beyond!' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - { - label: 'Select multiple', - name: 'select_multiple', - widget: 'select', - options: ['a', 'b', 'c'], - multiple: true, - } - - { label: 'Hidden', name: 'hidden', widget: 'hidden', default: 'hidden' } - - { label: 'Color', name: 'color', widget: 'color' } - - label: 'Object' - name: 'object' - widget: 'object' - collapsed: true - fields: - - label: 'Related Post' - name: 'post' - widget: 'relationKitchenSinkPost' - collection: 'posts' - search_fields: ['title', 'body'] - value_field: 'title' - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean', default: false } - - { label: 'Text', name: 'text', widget: 'text' } - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - label: 'List' - name: 'list' - widget: 'list' - fields: - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean' } - - { label: 'Text', name: 'text', widget: 'text' } - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - label: 'Object' - name: 'object' - widget: 'object' - fields: - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean' } - - { label: 'Text', name: 'text', widget: 'text' } - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - label: 'List' - name: 'list' - widget: 'list' - fields: - - label: 'Related Post' - name: 'post' - widget: 'relationKitchenSinkPost' - collection: 'posts' - search_fields: ['title', 'body'] - value_field: 'title' - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean' } - - { label: 'Text', name: 'text', widget: 'text' } - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - { label: 'Hidden', name: 'hidden', widget: 'hidden', default: 'hidden' } - - label: 'Object' - name: 'object' - widget: 'object' - fields: - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean' } - - { label: 'Text', name: 'text', widget: 'text' } - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - { - label: 'Select', - name: 'select', - widget: 'select', - options: ['a', 'b', 'c'], - } - - label: 'Typed List' - name: 'typed_list' - widget: 'list' - types: - - label: 'Type 1 Object' - name: 'type_1_object' - widget: 'object' - fields: - - { label: 'String', name: 'string', widget: 'string' } - - { label: 'Boolean', name: 'boolean', widget: 'boolean' } - - { label: 'Text', name: 'text', widget: 'text' } - - label: 'Type 2 Object' - name: 'type_2_object' - widget: 'object' - fields: - - { label: 'Number', name: 'number', widget: 'number' } - - { label: 'Select', name: 'select', widget: 'select', options: ['a', 'b', 'c'] } - - { label: 'Datetime', name: 'datetime', widget: 'datetime' } - - { label: 'Markdown', name: 'markdown', widget: 'markdown' } - - label: 'Type 3 Object' - name: 'type_3_object' - widget: 'object' - fields: - - { label: 'Image', name: 'image', widget: 'image' } - - { label: 'File', name: 'file', widget: 'file' } - - name: pages # a nested collection - label: Pages - label_singular: 'Page' - folder: _pages + i18n: true + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + nested: + depth: 10 + summary: "{{title}}" + subfolders: false + meta: {path: {widget: string, label: 'Path', index_file: '_index'}} + fields: *SINGLE_FIELDS + + - name: upravljanje + label: Upravljanje + label_singular: Stran upravljanje + folder: content/upravljanje + format: yaml-frontmatter create: true - nested: { depth: 100, subfolders: false } - fields: - - label: Title - name: title - widget: string - meta: { path: { widget: string, label: 'Path', index_file: 'index' } } + i18n: true + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + nested: + depth: 10 + summary: "{{title}}" + subfolders: false + meta: {path: {widget: string, label: 'Path', index_file: '_index'}} + fields: *SINGLE_FIELDS + + - name: za-obcane + label: Za občane + label_singular: Stran za občane + folder: content/za-obcane + format: yaml-frontmatter + create: true + i18n: true + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + nested: + depth: 10 + summary: "{{title}}" + subfolders: false + meta: {path: {widget: string, label: 'Path', index_file: '_index'}} + fields: *SINGLE_FIELDS + + - name: test + label: Test + label_singular: Stran test + folder: content/test + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: *SINGLE_FIELDS + + - name: unique-pages + label: Posebne strani + format: yaml-frontmatter + files: [ + {label: Prva stran SI, name: home-si, file: content/_index.sl.md, fields: *HOME_FIELDS}, + {label: Prva stran EN, name: home-en, file: content/_index.en.md, fields: *HOME_FIELDS}, + {label: Spored dogodkov SI, name: events-schedule-si, file: content/aktualno/dogodki/spored.sl.md, fields: *LIST_PAGE_FIELDS}, + {label: Spored dogodkov EN, name: events-schedule-en, file: content/aktualno/dogodki/spored.en.md, fields: *LIST_PAGE_FIELDS}, + {label: Kontakt SI, name: contact-si, file: content/kontakt-in-uradne-ure.sl.md, fields: *CONTACT_FIELDS}, + {label: Kontakt EN, name: contact-en, file: content/kontakt-in-uradne-ure.en.md, fields: *CONTACT_FIELDS}, + {label: Naročilo na sestanek SI, name: appointment-si, file: content/narocite-se-na-sestanek.sl.md, fields: *MEETING_FORM_FIELDS}, + {label: Naročilo na sestanek EN, name: appointment-en, file: content/narocite-se-na-sestanek.en.md, fields: *MEETING_FORM_FIELDS}, + {label: Prijava na obveščanje SI, name: newsletter-si, file: content/prijava-na-obvescanje.sl.md, fields: *NEWSLETTER_FORM_FIELDS}, + {label: Prijava na obveščanje EN, name: newsletter-en, file: content/prijava-na-obvescanje.en.md, fields: *NEWSLETTER_FORM_FIELDS}, + {label: Informativni izračun SI, name: fee-calculator-si, file: content/za-obcane/prostorsko-nacrtovanje-in-gradnja/informativni-izracun-komunalnega-prispevka.sl.md, fields: *FEE_CALCULATOR_FIELDS}, + {label: Informativni izračun EN, name: fee-calculator-en, file: content/za-obcane/prostorsko-nacrtovanje-in-gradnja/informativni-izracun-komunalnega-prispevka.en.md, fields: *FEE_CALCULATOR_FIELDS}, + ] + + - name: tags + label: Oznake + label_singular: Oznaka + folder: content/tags + create: true + i18n: true + path: '{{slug}}/_index' + sortable_fields: ['title', 'section'] + summary: "{{title}} ({{section}})" + view_groups: [ + {label: section, field: section}, + ] + fields: [ + *TITLE, + *LINK_TITLE, + *COLOR, + {name: section, label: section, widget: select, i18n: duplicate, options: ['novice', 'dogodki', 'projekti', 'predpisi']}, + ] + + - name: categories + label: Kategorije + label_singular: Kategorija + folder: content/categories + create: true + i18n: true + path: '{{slug}}/_index' + fields: [ + *TITLE, + ] + + - name: districts + label: Krajevne skupnosti in mestne četrti + label_singular: Krajevna skupnost + folder: content/upravljanje/krajevne-skupnosti + create: true + i18n: true + path: '{{slug}}/_index' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: [ + *TITLE, + ] + + - name: departments + label: Oddelki + label_singular: Oddelek + folder: content/upravljanje/mestna-uprava/oddelki + create: true + i18n: true + slug: '{{slug}}' + index_file: + pattern: _index + fields: *LIST_PAGE_FIELDS + fields: *SINGLE_FIELDS + + - name: taxonomies + label: Taksonomije + i18n: + structure: single_file + locales: [sl, en] + files: [ + {name: target_groups, label: Ciljne skupine, file: data/target_groups.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: fund_types, label: Sredstva, file: data/fund_types.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: themes, label: Teme, file: data/themes.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: areas, label: Področja, file: data/areas.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: content_areas, label: Vsebinska področja, file: data/content_areas.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: communal_service, label: Komunalne storitve, file: data/communal_service.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: social_service, label: Družbene dejavnosti, file: data/social_service.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: development, label: 'Razvoj, gostinstvo, turizem, malo gospodarstvo, kmetijstvo', file: data/development.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: institution, label: Institucija, file: data/institution.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: planning, label: Planiranje in urejanje prostora, file: data/planning.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: public_finance, label: Javne finance, file: data/public_finance.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: real_estate, label: 'Poslovni prostori in stanovanjske zadeve, najemi', file: data/real_estate.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + {name: self_government, label: Lokalna samouprava, file: data/self_government.json, i18n: true, fields: *TAXO_FILE_FIELDS}, + ] + + - name: components + label: Komponente + i18n: + structure: single_file + locales: [sl, en] + files: [ + {name: notification, label: Notification, file: data/notification.json, i18n: true, fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + {name: showOnHome, label: show on home, widget: boolean, i18n: true, required: false}, + {name: showSiteWide, label: show on the rest of the site, widget: boolean, i18n: true, required: false}, + ]}, + {name: footer, label: Footer, file: data/footer.json, i18n: true, fields: [ + {name: logo, widget: image, i18n: duplicate, required: false, hint: 'if empty, site logo will be used'}, + {name: title, widget: string, i18n: true, required: false, hint: 'if empty, site title will be used'}, + {name: address, widget: string, i18n: true, required: false, hint: 'if empty, site address will be used'}, + {name: openingHours, label: opening hours, widget: object, i18n: true, fields: [ + *TITLE, + {name: items, widget: list, label_singular: item, i18n: true, fields: [ + {name: day, widget: string, i18n: true}, + {name: hours, widget: string, i18n: true}, + ]}, + ]}, + {name: contact, widget: object, i18n: true, fields: [ + *TITLE, + {name: email, widget: string, i18n: true, required: false, hint: 'if empty, site email will be used'}, + {name: phone, widget: string, i18n: true, required: false, hint: 'if empty, site phone will be used'}, + ]}, + {name: socialLinks, label: Social links, widget: list, label_singular: link, i18n: true, fields: [ + {name: name, widget: string, i18n: true}, + {name: icon, widget: string, i18n: true}, + {name: href, widget: string, i18n: true}, + ]}, + {name: titleLinks, label: Title links, widget: list, label_singular: link, i18n: true, fields: [ + {name: title, widget: string, i18n: true}, + {name: text, widget: string, i18n: true}, + {name: href, widget: string, i18n: true}, + ]}, + {name: usefulLinks, label: Useful links, widget: object, i18n: true, fields: [ + *TITLE, + {name: items, widget: list, i18n: true, label_singular: item, fields: *BUTTON_FIELDS}, + ]}, + {name: illustration, widget: object, i18n: true, fields: *IMAGE_OBJECT_FIELDS}, + {name: copyright, widget: string, i18n: true}, + {name: bottomLinks, label: Bottom links, widget: list, label_singular: link, i18n: true, fields: *BUTTON_FIELDS}, + ]}, + {name: newsletter, label: Newsletter, file: data/newsletter.json, i18n: true, fields: [ + *TITLE, + *DESCRIPTION, + *BUTTON, + *IMAGE_OBJECT, + ]}, + {name: "404", label: 404 Page, file: data/page404.json, i18n: true, fields: [ + *PAGE_HERO, + *RELATED_LINKS, + ]}, + {name: relatedRegulations, label: Related regulations, file: data/relatedRegulations.json, i18n: true, fields: [ + *TITLE, + {name: allTendersCta, label: all tenders cta, widget: string, i18n: true}, + {name: isDesktopSlider, label: is desktop slider, widget: boolean, i18n: true, required: false}, + ]}, + ] + + - name: general + label: Splošno + files: + - label: Site settings + name: site-settings + file: hugo.toml + fields: [ + {name: title, widget: string}, + {name: params, widget: object, fields: [ + {name: description, widget: text}, + {name: image, widget: image}, + {name: logo, widget: image}, + {name: address, widget: string}, + {name: phone, widget: string}, + {name: email, widget: string}, + ]}, + ] + - label: Translations SI + name: translations-si + file: i18n/sl.toml + fields: *TRANSLATION_FIELDS + - label: Translations EN + name: translations-en + file: i18n/en.toml + fields: *TRANSLATION_FIELDS + - label: Appointment email settings + name: appointment-email-settings + file: data/appointmentEmail.json + fields: [ + {name: sender, widget: string}, + {name: recipient, widget: string}, + {name: subject, widget: string}, + ] diff --git a/packages/decap-cms-backend-github/src/API.ts b/packages/decap-cms-backend-github/src/API.ts index 244a3e228509..f57c284eaffe 100644 --- a/packages/decap-cms-backend-github/src/API.ts +++ b/packages/decap-cms-backend-github/src/API.ts @@ -1386,20 +1386,26 @@ export default class API { async updateTree( baseSha: string, - files: { path: string; sha: string | null; newPath?: string }[], + files: { path: string; sha: string | null; newPath?: string; isFolder?: boolean }[], branch = this.branch, ) { - const toMove: { from: string; to: string; sha: string }[] = []; + const toMove: { from: string; to: string; sha: string; isFolder?: boolean }[] = []; const tree = files.reduce((acc, file) => { const entry = { path: trimStart(file.path, '/'), mode: '100644', type: 'blob', sha: file.sha, + isFolder: file.isFolder, } as TreeEntry; if (file.newPath) { - toMove.push({ from: file.path, to: file.newPath, sha: file.sha as string }); + toMove.push({ + from: file.path, + to: file.newPath, + sha: file.sha as string, + isFolder: file.isFolder, + }); } else { acc.push(entry); } @@ -1407,11 +1413,14 @@ export default class API { return acc; }, [] as TreeEntry[]); - for (const { from, to, sha } of toMove) { + for (const { from, to, sha, isFolder } of toMove) { const sourceDir = dirname(from); const destDir = dirname(to); const files = await this.listFiles(sourceDir, { branch, depth: 100 }); for (const file of files) { + if (isFolder === false && file.path !== from) { + continue; + } // delete current path tree.push({ path: file.path, diff --git a/packages/decap-cms-core/index.d.ts b/packages/decap-cms-core/index.d.ts index d5efb55dd7dd..c394f47eac8c 100644 --- a/packages/decap-cms-core/index.d.ts +++ b/packages/decap-cms-core/index.d.ts @@ -192,6 +192,7 @@ declare module 'decap-cms-core' { multiple?: boolean; min?: number; max?: number; + meta?: boolean; } export interface CmsFieldRelation { @@ -332,6 +333,10 @@ declare module 'decap-cms-core' { view_filters?: ViewFilter[]; view_groups?: ViewGroup[]; i18n?: boolean | CmsI18nConfig; + index_file?: { + pattern: string; + fields?: CmsField[]; + }; /** * @deprecated Use sortable_fields instead diff --git a/packages/decap-cms-core/src/actions/config.ts b/packages/decap-cms-core/src/actions/config.ts index 66062a50a1ee..e80b4a8e6e89 100644 --- a/packages/decap-cms-core/src/actions/config.ts +++ b/packages/decap-cms-core/src/actions/config.ts @@ -21,10 +21,13 @@ import type { CmsFieldBase, CmsFieldObject, CmsFieldList, + CmsFieldSelect, + CmsFieldMeta, CmsI18nConfig, CmsPublishMode, CmsLocalBackend, State, + CmsCollectionMeta, } from '../types/redux'; export const CONFIG_REQUEST = 'CONFIG_REQUEST'; @@ -204,6 +207,35 @@ export function normalizeConfig(config: CmsConfig) { return { ...config, collections: normalizedCollections }; } +function applyMetaFieldsToCollection(collection: CmsCollection, meta: CmsCollectionMeta) { + const metaFields = [ + { + name: 'path', + meta: true, + required: true, + i18n: 'duplicate', + ...meta!.path, + } as CmsFieldMeta, + { + name: 'path_type', + meta: true, + required: true, + widget: 'select', + readonly: true, + i18n: 'duplicate', + label: 'Path type', + options: ['index', 'slug'], + default: 'slug', + } as CmsFieldBase & CmsFieldSelect, + ]; + + collection.fields = [...metaFields, ...(collection.fields || [])]; + + if (collection.index_file?.fields) { + collection.index_file.fields = [...metaFields, ...(collection.index_file.fields || [])]; + } +} + export function applyDefaults(originalConfig: CmsConfig) { return produce(originalConfig, config => { config.publish_mode = config.publish_mode || SIMPLE_PUBLISH_MODE; @@ -284,13 +316,7 @@ export function applyDefaults(originalConfig: CmsConfig) { collection.folder = trim(folder, '/'); if (meta && meta.path) { - const metaField = { - name: 'path', - meta: true, - required: true, - ...meta.path, - }; - collection.fields = [metaField, ...(collection.fields || [])]; + applyMetaFieldsToCollection(collection, meta); } } diff --git a/packages/decap-cms-core/src/actions/entries.ts b/packages/decap-cms-core/src/actions/entries.ts index e8529b7c20b0..7b6ddb9016a4 100644 --- a/packages/decap-cms-core/src/actions/entries.ts +++ b/packages/decap-cms-core/src/actions/entries.ts @@ -15,8 +15,7 @@ import { addAssets, getAsset } from './media'; import { SortDirection } from '../types/redux'; import { waitForMediaLibraryToLoad, loadMedia } from './mediaLibrary'; import { waitUntil } from './waitUntil'; -import { selectIsFetching, selectEntriesSortFields, selectEntryByPath } from '../reducers/entries'; -import { selectCustomPath } from '../reducers/entryDraft'; +import { selectIsFetching, selectEntriesSortFields } from '../reducers/entries'; import { navigateToEntry } from '../routing/history'; import { getProcessSegment } from '../lib/formatters'; import { hasI18n, duplicateDefaultI18nFields, serializeI18n, I18N, I18N_FIELD } from '../lib/i18n'; @@ -1028,17 +1027,21 @@ export function validateMetaField( return getPathError(value, 'invalidPath', t); } - const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path: value } } })); - const existingEntry = customPath - ? selectEntryByPath(state.entries, collection.get('name'), customPath) - : undefined; + console.log(collection); - const existingEntryPath = existingEntry?.get('path'); - const draftPath = state.entryDraft?.getIn(['entry', 'path']); + // update path validation - if (existingEntryPath && existingEntryPath !== draftPath) { - return getPathError(value, 'pathExists', t); - } + // const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path: value } } }), state.config); + // const existingEntry = customPath + // ? selectEntryByPath(state.entries, collection.get('name'), customPath) + // : undefined; + + // const existingEntryPath = existingEntry?.get('path'); + // const draftPath = state.entryDraft?.getIn(['entry', 'path']); + + // if (existingEntryPath && existingEntryPath !== draftPath) { + // return getPathError(value, 'pathExists', t); + // } } return { error: false }; } diff --git a/packages/decap-cms-core/src/backend.ts b/packages/decap-cms-core/src/backend.ts index 52dfb538a0f5..8c1698defc5d 100644 --- a/packages/decap-cms-core/src/backend.ts +++ b/packages/decap-cms-core/src/backend.ts @@ -298,6 +298,22 @@ function prepareMetaPath(path: string, collection: Collection) { return dir.slice(collection.get('folder')!.length + 1) || '/'; } +function isIndexFile(filePath: string, pattern: string, nested: boolean) { + const fileSlug = nested ? filePath?.split('/').pop() : filePath; + return fileSlug && new RegExp(pattern).test(fileSlug); +} + +function prepareMetaPathType(slug: string, collection: Collection) { + const indexFileConfig = collection.get('index_file'); + if ( + indexFileConfig && + isIndexFile(slug, indexFileConfig.get('pattern'), !!collection.get('nested')) + ) { + return 'index'; + } + return 'slug'; +} + function collectionDepth(collection: Collection) { let depth; depth = @@ -823,11 +839,24 @@ export class Backend { const getEntryValue = async (path: string) => { const loadedEntry = await this.implementation.getEntry(path); - let entry = createEntry(collection.get('name'), slug, loadedEntry.file.path, { + const entryPath = loadedEntry.file.path; + const path_type = prepareMetaPathType(slug, collection); + + let metaPath = entryPath; + if (path_type === 'index') { + const pathArr = dirname(entryPath).split('/').slice(0, -1); + pathArr.push(basename(entryPath)); + metaPath = pathArr.join('/'); + } + + let entry = createEntry(collection.get('name'), slug, entryPath, { raw: loadedEntry.data, label, mediaFiles: [], - meta: { path: prepareMetaPath(loadedEntry.file.path, collection) }, + meta: { + path: prepareMetaPath(metaPath, collection), + path_type, + }, }); entry = this.entryWithFormat(collection)(entry); @@ -1104,9 +1133,12 @@ export class Backend { const useWorkflow = selectUseWorkflow(config); - const customPath = selectCustomPath(collection, entryDraft); + const customPath = selectCustomPath(collection, entryDraft, config); let dataFile: DataFile; + + let isFolder = true; + if (newEntry) { if (!selectAllowNewEntries(collection)) { throw new Error('Not allowed to create new entries in this collection'); @@ -1118,6 +1150,7 @@ export class Backend { usedSlugs, customPath, ); + isFolder = prepareMetaPathType(slug, collection) === 'index'; const path = customPath || (selectEntryPath(collection, slug) as string); dataFile = { path, @@ -1128,6 +1161,7 @@ export class Backend { updateAssetProxies(assetProxies, config, collection, entryDraft, path); } else { const slug = entryDraft.getIn(['entry', 'slug']); + const isFolder = prepareMetaPathType(slug, collection) === 'index'; const path = entryDraft.getIn(['entry', 'path']); dataFile = { path, @@ -1135,6 +1169,7 @@ export class Backend { slug: customPath && !useWorkflow ? slugFromCustomPath(collection, customPath) : slug, raw: this.entryToRaw(collection, entryDraft.get('entry')), newPath: customPath === path ? undefined : customPath, + isFolder, }; } @@ -1151,6 +1186,7 @@ export class Backend { path, slug, newPath, + isFolder, ); } diff --git a/packages/decap-cms-core/src/components/Collection/CollectionTop.js b/packages/decap-cms-core/src/components/Collection/CollectionTop.js index 47d63e956d45..1e7eeaa81430 100644 --- a/packages/decap-cms-core/src/components/Collection/CollectionTop.js +++ b/packages/decap-cms-core/src/components/Collection/CollectionTop.js @@ -4,7 +4,15 @@ import React from 'react'; import styled from '@emotion/styled'; import { translate } from 'react-polyglot'; import { Link } from 'react-router-dom'; -import { components, buttons, shadows } from 'decap-cms-ui-default'; +import { + Dropdown, + DropdownItem, + StyledDropdownButton, + components, + buttons, + shadows, +} from 'decap-cms-ui-default'; +import { createHashHistory } from 'history'; const CollectionTopContainer = styled.div` ${components.cardTop}; @@ -30,6 +38,15 @@ const CollectionTopNewButton = styled(Link)` padding: 0 30px; `; +const CollectionTopDropdownButton = styled(StyledDropdownButton)` + ${buttons.button}; + ${shadows.dropDeep}; + ${buttons.default}; + ${buttons.gray}; + + padding: 0 30px 0 15px; +`; + const CollectionTopDescription = styled.p` ${components.cardTopDescription}; margin-bottom: 0; @@ -47,17 +64,43 @@ function getCollectionProps(collection) { }; } +const history = createHashHistory(); + function CollectionTop({ collection, newEntryUrl, t }) { const { collectionLabel, collectionLabelSingular, collectionDescription } = getCollectionProps( collection, t, ); + const indexFileConfig = collection.get('index_file'); + // const indexFile = get(collection.toJS(), ['meta', 'path', 'index_file']) + + function handleNew(pathType) { + const delimiter = newEntryUrl.includes('?') ? '&' : '?'; + history.push(`${newEntryUrl}${delimiter}path_type=${pathType}`) + } + return ( {collectionLabel} - {newEntryUrl ? ( + {indexFileConfig && collection.get('nested') ? ( + ( + + {t('collection.collectionTop.newButton', { + collectionLabel: collectionLabelSingular || collectionLabel, + })} + + )} + dropdownTopOverlap="30px" + dropdownWidth="160px" + dropdownPosition="left" + > + handleNew('index')} /> + handleNew('slug')} /> + + ) : newEntryUrl ? ( {t('collection.collectionTop.newButton', { collectionLabel: collectionLabelSingular || collectionLabel, diff --git a/packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js b/packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js index 01e0027d7069..cdf45d585023 100644 --- a/packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js +++ b/packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js @@ -2,7 +2,7 @@ import React from 'react'; import styled from '@emotion/styled'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; -import { colors, colorsRaw, components, lengths, zIndex } from 'decap-cms-ui-default'; +import { colors, colorsRaw, components, lengths, zIndex, Icon } from 'decap-cms-ui-default'; import { boundGetAsset } from '../../../actions/media'; import { VIEW_STYLE_LIST, VIEW_STYLE_GRID } from '../../../constants/collectionViews'; @@ -56,10 +56,14 @@ const CollectionLabel = styled.h2` const ListCardTitle = styled.h2` margin-bottom: 0; + display: flex; + justify-content: space-between; `; const CardHeading = styled.h2` margin: 0 0 2px; + display: flex; + justify-content: space-between; `; const CardBody = styled.div` @@ -95,6 +99,7 @@ function EntryCard({ image, imageField, collectionLabel, + showIndexFileIcon, viewStyle = VIEW_STYLE_LIST, getAsset, }) { @@ -103,7 +108,10 @@ function EntryCard({ {collectionLabel ? {collectionLabel} : null} - {summary} + + {summary} + {showIndexFileIcon && } + ); @@ -115,7 +123,10 @@ function EntryCard({ {collectionLabel ? {collectionLabel} : null} - {summary} + + {summary} + {showIndexFileIcon && } + {image ? : null} @@ -136,6 +147,9 @@ function mapStateToProps(state, ownProps) { const isLoadingAsset = selectIsLoadingAsset(state.medias); + const indexFileConfig = collection.get('index_file'); + const fileSlug = entry.get('slug'); + return { summary, path: `/collections/${collection.get('name')}/entries/${entry.get('slug')}`, @@ -144,6 +158,7 @@ function mapStateToProps(state, ownProps) { .get('fields') ?.find(f => f.get('name') === inferredFields.imageField && f.get('widget') === 'image'), isLoadingAsset, + showIndexFileIcon: indexFileConfig && new RegExp(indexFileConfig.get('pattern')).test(fileSlug), }; } diff --git a/packages/decap-cms-core/src/components/Editor/Editor.js b/packages/decap-cms-core/src/components/Editor/Editor.js index c9a2f0d4581b..9fb7b7b72046 100644 --- a/packages/decap-cms-core/src/components/Editor/Editor.js +++ b/packages/decap-cms-core/src/components/Editor/Editor.js @@ -417,7 +417,11 @@ function mapStateToProps(state, ownProps) { const collection = collections.get(ownProps.match.params.name); const collectionName = collection.get('name'); const newEntry = ownProps.newRecord === true; - const fields = selectFields(collection, slug); + const fields = selectFields( + collection, + slug, + new URLSearchParams(ownProps.location.search).get('path_type') === 'index', + ); const entry = newEntry ? null : selectEntry(state, collectionName, slug); const user = auth.user; const hasChanged = entryDraft.get('hasChanged'); diff --git a/packages/decap-cms-core/src/constants/configSchema.js b/packages/decap-cms-core/src/constants/configSchema.js index 369a4f09341f..d24ef6d67ce9 100644 --- a/packages/decap-cms-core/src/constants/configSchema.js +++ b/packages/decap-cms-core/src/constants/configSchema.js @@ -282,6 +282,13 @@ function getConfigSchema() { minProperties: 1, }, i18n: i18nCollection, + index_file: { + type: 'object', + properties: { + pattern: { type: 'string' }, + fields: fieldsConfig(), + }, + }, }, required: ['name', 'label'], oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }], diff --git a/packages/decap-cms-core/src/lib/i18n.ts b/packages/decap-cms-core/src/lib/i18n.ts index 75e5defaf729..05baf721b6ce 100644 --- a/packages/decap-cms-core/src/lib/i18n.ts +++ b/packages/decap-cms-core/src/lib/i18n.ts @@ -146,6 +146,7 @@ export function getI18nFiles( path: string, slug: string, newPath?: string, + isFolder?: boolean, ) { const { structure, defaultLocale, locales } = getI18nInfo(collection) as I18nInfo; @@ -169,16 +170,23 @@ export function getI18nFiles( } const dataFiles = locales - .map(locale => { + .map((locale, index) => { const dataPath = getDataPath(locale, defaultLocale); const draft = entryDraft.set('data', entryDraft.getIn(dataPath)); return { - path: getFilePath(structure, extension, path, slug, locale), + path: getFilePath( + structure, + extension, + index > 0 && newPath && isFolder !== false ? newPath : path, + slug, + locale, + ), slug, raw: draft.get('data') ? entryToRaw(draft) : '', ...(newPath && { newPath: getFilePath(structure, extension, newPath, slug, locale), }), + isFolder, }; }) .filter(dataFile => dataFile.raw); diff --git a/packages/decap-cms-core/src/reducers/collections.ts b/packages/decap-cms-core/src/reducers/collections.ts index 3efe70631913..473bd1c50944 100644 --- a/packages/decap-cms-core/src/reducers/collections.ts +++ b/packages/decap-cms-core/src/reducers/collections.ts @@ -43,6 +43,11 @@ function collections(state = defaultState, action: ConfigAction) { } } +function isIndexFile(filePath: string, pattern: string, nested: boolean) { + const fileSlug = nested ? filePath?.split('/').pop() : filePath; + return fileSlug && new RegExp(pattern).test(fileSlug); +} + const selectors = { [FOLDER]: { entryExtension(collection: Collection) { @@ -55,7 +60,16 @@ const selectors = { return ext.replace(/^\./, ''); }, - fields(collection: Collection) { + fields(collection: Collection, slug: string, index?: boolean) { + const indexFileConfig = collection.get('index_file'); + if ( + indexFileConfig && + (index || isIndexFile(slug, indexFileConfig.get('pattern'), !!collection.get('nested'))) && + indexFileConfig.has('fields') + ) { + return indexFileConfig.get('fields'); + } + return collection.get('fields'); }, entryPath(collection: Collection, slug: string) { @@ -176,8 +190,8 @@ export function selectMediaFolders(config: CmsConfig, collection: Collection, en return Set(folders).toArray(); } -export function selectFields(collection: Collection, slug: string) { - return selectors[collection.get('type')].fields(collection, slug); +export function selectFields(collection: Collection, slug: string, index?: boolean) { + return selectors[collection.get('type')].fields(collection, slug, index); } export function selectFolderEntryExtension(collection: Collection) { diff --git a/packages/decap-cms-core/src/reducers/entryDraft.js b/packages/decap-cms-core/src/reducers/entryDraft.js index e82e2603f0da..811375afc450 100644 --- a/packages/decap-cms-core/src/reducers/entryDraft.js +++ b/packages/decap-cms-core/src/reducers/entryDraft.js @@ -33,6 +33,7 @@ import { } from '../actions/editorialWorkflow'; import { selectFolderEntryExtension, selectHasMetaPath } from './collections'; import { getDataPath, duplicateI18nFields } from '../lib/i18n'; +import { slugFormatter } from '../lib/formatters'; const initialState = Map({ entry: Map(), @@ -204,15 +205,25 @@ function entryDraftReducer(state = Map(), action) { } } -export function selectCustomPath(collection, entryDraft) { +export function selectCustomPath(collection, entryDraft, config) { if (!selectHasMetaPath(collection)) { return; } const meta = entryDraft.getIn(['entry', 'meta']); const path = meta && meta.get('path'); + const pathType = meta && meta.get('path_type', 'index'); const indexFile = get(collection.toJS(), ['meta', 'path', 'index_file']); + const fileBaseName = slugFormatter(collection, entryDraft.getIn(['entry', 'data']), config.slug); const extension = selectFolderEntryExtension(collection); - const customPath = path && join(collection.get('folder'), path, `${indexFile}.${extension}`); + const customPath = + path && + join( + collection.get('folder'), + path, + pathType == 'index' + ? `${fileBaseName}/${indexFile}.${extension}` + : `${fileBaseName}.${extension}`, + ); return customPath; } diff --git a/packages/decap-cms-core/src/types/redux.ts b/packages/decap-cms-core/src/types/redux.ts index f88f5becdc76..6043f3e16d1c 100644 --- a/packages/decap-cms-core/src/types/redux.ts +++ b/packages/decap-cms-core/src/types/redux.ts @@ -207,6 +207,7 @@ export interface CmsFieldSelect { multiple?: boolean; min?: number; max?: number; + meta?: boolean; } export interface CmsFieldRelation { @@ -306,6 +307,14 @@ export interface ViewGroup { id: string; } +export interface CmsCollectionMeta { + path?: { + label: string; + widget: string; + index_file: string; + }; +} + export interface CmsCollection { name: string; label: string; @@ -328,7 +337,7 @@ export interface CmsCollection { depth: number; }; type: typeof FOLDER | typeof FILES; - meta?: { path?: { label: string; widget: string; index_file: string } }; + meta?: CmsCollectionMeta; /** * It accepts the following values: yml, yaml, toml, json, md, markdown, html @@ -349,6 +358,11 @@ export interface CmsCollection { view_groups?: ViewGroup[]; i18n?: boolean | CmsI18nConfig; + index_file?: { + pattern: string; + fields?: CmsField[]; + }; + /** * @deprecated Use sortable_fields instead */ @@ -600,6 +614,11 @@ type i18n = StaticallyTypedRecord<{ default_locale: string; }>; +type IndexFile = StaticallyTypedRecord<{ + pattern: string; + fields?: EntryFields; +}>; + export type Format = keyof typeof formatExtensions | string; type CollectionObject = { @@ -631,6 +650,7 @@ type CollectionObject = { nested?: Nested; meta?: Meta; i18n: i18n; + index_file?: IndexFile; }; export type Collection = StaticallyTypedRecord; diff --git a/packages/decap-cms-core/src/valueObjects/Entry.ts b/packages/decap-cms-core/src/valueObjects/Entry.ts index 6507215ec594..d423fb533cbc 100644 --- a/packages/decap-cms-core/src/valueObjects/Entry.ts +++ b/packages/decap-cms-core/src/valueObjects/Entry.ts @@ -13,7 +13,7 @@ interface Options { author?: string; updatedOn?: string; status?: string; - meta?: { path?: string }; + meta?: { path?: string; path_type?: string }; i18n?: { // eslint-disable-next-line @typescript-eslint/no-explicit-any [locale: string]: any; diff --git a/packages/decap-cms-lib-util/src/implementation.ts b/packages/decap-cms-lib-util/src/implementation.ts index 8b8a05cfe0a0..2983b5a72633 100644 --- a/packages/decap-cms-lib-util/src/implementation.ts +++ b/packages/decap-cms-lib-util/src/implementation.ts @@ -60,6 +60,7 @@ export type DataFile = { slug: string; raw: string; newPath?: string; + isFolder?: boolean; }; export type AssetProxy = { diff --git a/packages/decap-server/src/middlewares/localFs/index.ts b/packages/decap-server/src/middlewares/localFs/index.ts index e1c265763d10..0a0d5381a53e 100644 --- a/packages/decap-server/src/middlewares/localFs/index.ts +++ b/packages/decap-server/src/middlewares/localFs/index.ts @@ -83,6 +83,7 @@ export function localFsMiddleware({ repoPath, logger }: FsOptions) { await move( path.join(repoPath, dataFile.path), path.join(repoPath, dataFile.newPath!), + dataFile.isFolder, ); }); } diff --git a/packages/decap-server/src/middlewares/localGit/index.ts b/packages/decap-server/src/middlewares/localGit/index.ts index a8765223ef1a..7985a69a7b24 100644 --- a/packages/decap-server/src/middlewares/localGit/index.ts +++ b/packages/decap-server/src/middlewares/localGit/index.ts @@ -97,7 +97,11 @@ async function commitEntry( ); if (dataFiles.every(dataFile => dataFile.newPath)) { dataFiles.forEach(async dataFile => { - await move(path.join(repoPath, dataFile.path), path.join(repoPath, dataFile.newPath!)); + await move( + path.join(repoPath, dataFile.path), + path.join(repoPath, dataFile.newPath!), + dataFile.isFolder, + ); }); } diff --git a/packages/decap-server/src/middlewares/types.ts b/packages/decap-server/src/middlewares/types.ts index be69316d9456..a071c8eb889e 100644 --- a/packages/decap-server/src/middlewares/types.ts +++ b/packages/decap-server/src/middlewares/types.ts @@ -54,7 +54,13 @@ export type PublishUnpublishedEntryParams = { slug: string; }; -export type DataFile = { slug: string; path: string; raw: string; newPath?: string }; +export type DataFile = { + slug: string; + path: string; + raw: string; + newPath?: string; + isFolder?: boolean; +}; export type Asset = { path: string; content: string; encoding: 'base64' }; diff --git a/packages/decap-server/src/middlewares/utils/fs.ts b/packages/decap-server/src/middlewares/utils/fs.ts index a15c1e094261..0f889de6ccba 100644 --- a/packages/decap-server/src/middlewares/utils/fs.ts +++ b/packages/decap-server/src/middlewares/utils/fs.ts @@ -46,10 +46,14 @@ async function moveFile(from: string, to: string) { await fs.rename(from, to); } -export async function move(from: string, to: string) { +export async function move(from: string, to: string, isFolder?: boolean) { // move file await moveFile(from, to); + if (isFolder === false) { + return; + } + // move children const sourceDir = path.dirname(from); const destDir = path.dirname(to);