├── deploy-test-count ├── force-deploy ├── badges └── npm-audit-badge.svg ├── .eslintignore ├── public ├── fav │ ├── favicon.ico │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json └── images │ └── apos-dark.svg ├── modules ├── asset │ ├── index.js │ └── ui │ │ └── src │ │ ├── _widget.scss │ │ ├── index.js │ │ ├── _admin.scss │ │ ├── _media.scss │ │ ├── index.scss │ │ ├── _pager.scss │ │ ├── _dark-light-switch.js │ │ ├── _locales.js │ │ ├── _global.scss │ │ ├── _header.scss │ │ ├── _mobile.js │ │ ├── _hero.scss │ │ ├── _footer.scss │ │ ├── _pr-widget.scss │ │ ├── _type.scss │ │ ├── _button.scss │ │ ├── _variables.scss │ │ ├── _card.scss │ │ ├── _price-card.scss │ │ ├── _locales.scss │ │ ├── _misc.scss │ │ ├── _article.scss │ │ ├── _nav.scss │ │ └── _rich-text.scss ├── article-widget │ ├── views │ │ └── widget.html │ ├── index.js │ └── public │ │ └── preview.svg ├── @apostrophecms │ ├── layout-widget │ │ ├── index.js │ │ ├── views │ │ │ └── widget.html │ │ └── public │ │ │ └── preview.svg │ ├── video-widget │ │ ├── index.js │ │ └── public │ │ │ └── preview.svg │ ├── image-widget │ │ ├── index.js │ │ └── public │ │ │ └── preview.svg │ ├── express │ │ └── index.js │ ├── layout-column-widget │ │ └── index.js │ ├── global │ │ └── index.js │ ├── template │ │ └── views │ │ │ ├── outerLayout.html │ │ │ └── macros │ │ │ └── icons.html │ ├── admin-bar │ │ └── index.js │ ├── page │ │ └── index.js │ ├── settings │ │ └── index.js │ ├── rich-text-widget │ │ ├── index.js │ │ └── public │ │ │ └── preview.svg │ ├── home-page │ │ ├── views │ │ │ └── page.html │ │ └── index.js │ ├── i18n │ │ ├── index.js │ │ └── i18n │ │ │ └── project │ │ │ ├── en.json │ │ │ ├── de.json │ │ │ ├── fr.json │ │ │ └── mx.json │ └── asset │ │ └── index.js ├── article-category │ ├── index.js │ └── views │ │ └── recent.html ├── github-prs-widget │ ├── views │ │ ├── widget.html │ │ └── prs.html │ ├── index.js │ └── public │ │ └── preview.svg ├── default-page │ ├── views │ │ └── page.html │ └── index.js ├── article │ ├── views │ │ └── recent.html │ ├── ui │ │ └── apos │ │ │ └── components │ │ │ ├── DemoCellRelation.vue │ │ │ └── DemoCellImage.vue │ └── index.js ├── hero-widget │ ├── views │ │ └── widget.html │ ├── public │ │ └── preview.svg │ └── index.js ├── button-widget │ ├── views │ │ └── widget.html │ ├── index.js │ └── public │ │ └── preview.svg ├── helper │ └── index.js ├── article-page │ ├── index.js │ └── views │ │ ├── fragments.html │ │ ├── show.html │ │ └── index.html ├── card-widget │ ├── views │ │ └── widget.html │ ├── index.js │ └── public │ │ └── preview.svg └── price-card-widget │ ├── views │ └── widget.html │ ├── index.js │ └── public │ └── preview.svg ├── lib ├── options.js ├── iconChoices.js ├── link.js └── area.js ├── .eslintrc ├── deployment ├── README ├── settings.staging ├── migrate ├── rsync_exclude.txt ├── stop ├── settings ├── dependencies └── start ├── views ├── link.html ├── locales.html └── layout.html ├── Dockerfile ├── .gitignore ├── README.md ├── local.example.js ├── LICENSE ├── scripts ├── sync-down └── sync-up ├── package.json └── app.js /deploy-test-count: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /force-deploy: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /badges/npm-audit-badge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /public/apos-frontend 2 | /data/temp 3 | /apos-build -------------------------------------------------------------------------------- /public/fav/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/favicon.ico -------------------------------------------------------------------------------- /modules/asset/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | ignoreNoCodeWarning: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/fav/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon.png -------------------------------------------------------------------------------- /public/fav/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/favicon-16x16.png -------------------------------------------------------------------------------- /public/fav/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/favicon-32x32.png -------------------------------------------------------------------------------- /public/fav/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/favicon-96x96.png -------------------------------------------------------------------------------- /public/fav/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/fav/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/fav/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/fav/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/fav/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/fav/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/fav/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/fav/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/fav/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-36x36.png -------------------------------------------------------------------------------- /public/fav/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-48x48.png -------------------------------------------------------------------------------- /public/fav/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-72x72.png -------------------------------------------------------------------------------- /public/fav/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-96x96.png -------------------------------------------------------------------------------- /public/fav/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/fav/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/fav/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/fav/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/fav/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/fav/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-144x144.png -------------------------------------------------------------------------------- /public/fav/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/android-icon-192x192.png -------------------------------------------------------------------------------- /public/fav/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms/public-demo/main/public/fav/apple-icon-precomposed.png -------------------------------------------------------------------------------- /modules/article-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% component "article:recent" with { limit: data.widget.limit, display: data.widget.display } %} 2 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | const aposBrandColors = [ '#ea433a', '#cc9300', '#b327bf', '#66f', '#00bf9a' ]; 2 | 3 | export { 4 | aposBrandColors 5 | }; 6 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_widget.scss: -------------------------------------------------------------------------------- 1 | .widget { 2 | margin-bottom: var(--widget-spacer); 3 | 4 | .widget { 5 | margin-bottom: calc(var(--widget-spacer) / 2); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "apostrophe" 4 | ], 5 | "globals": { 6 | "apos": true 7 | }, 8 | "rules": { 9 | "no-var": "error", 10 | "no-console": 0 11 | } 12 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/layout-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | label: 'project:layout', 4 | description: 'project:layoutDescription', 5 | previewImage: 'svg' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /deployment/README: -------------------------------------------------------------------------------- 1 | This is a deployment folder for use with Stagecoach. 2 | 3 | You don't have to use Stagecoach. 4 | 5 | It's just a neat solution for deploying node apps. 6 | 7 | See: 8 | 9 | http://github.com/punkave/stagecoach 10 | -------------------------------------------------------------------------------- /modules/@apostrophecms/video-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | label: 'project:video', 4 | description: 'project:videoDescription', 5 | previewImage: 'svg', 6 | className: 'widget demo-video' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /modules/article-category/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extend: '@apostrophecms/piece-type', 3 | options: { 4 | alias: 'category', 5 | label: 'project:articleCategory', 6 | pluralLabel: 'project:articleCategories' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /modules/@apostrophecms/image-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | label: 'project:image', 4 | description: 'project:imageDescription', 5 | previewImage: 'svg', 6 | inlineStyles: false, 7 | className: 'widget demo-image' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /views/link.html: -------------------------------------------------------------------------------- 1 | {% macro render(options) %} 2 | {{ options.label }} 7 | {% endmacro %} -------------------------------------------------------------------------------- /modules/github-prs-widget/views/widget.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{ data.widget.state | capitalize }} PRs for {{ data.widget.repo}} 4 |

5 | {% component "github-prs-widget:prs" with { widget: data.widget } %} 6 |
-------------------------------------------------------------------------------- /deployment/settings.staging: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Settings specific to the 'master' deployment target. 4 | # USER is the ssh user, SERVER is the ssh host. USER should 5 | # match the USER setting in /opt/stagecoach/settings on 6 | # the server 7 | 8 | USER=nodeapps 9 | SERVER=demo.apos.dev 10 | -------------------------------------------------------------------------------- /public/fav/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-slim 2 | 3 | # Create app directory 4 | RUN mkdir -p /app 5 | WORKDIR /app 6 | 7 | # Bundle app source 8 | COPY . /app 9 | RUN npm install 10 | 11 | # Mount persistent storage 12 | VOLUME /app/data 13 | VOLUME /app/public/uploads 14 | 15 | EXPOSE 3000 16 | CMD [ "npm", "start" ] 17 | -------------------------------------------------------------------------------- /modules/@apostrophecms/express/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | // So baseUrl can be autodetected when behind nginx 4 | trustProxy: true, 5 | session: { 6 | // If this still says `undefined`, set a real secret! 7 | secret: 'e45faf16a3e6f86a' 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /modules/@apostrophecms/layout-column-widget/index.js: -------------------------------------------------------------------------------- 1 | import { fullConfig } from '../../../lib/area.js'; 2 | 3 | export default { 4 | fields: { 5 | add: { 6 | content: { 7 | type: 'area', 8 | options: { 9 | widgets: fullConfig 10 | } 11 | } 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /modules/asset/ui/src/index.js: -------------------------------------------------------------------------------- 1 | import localToggle from './_locales.js'; 2 | import modeToggle from './_dark-light-switch.js'; 3 | import mobileMenu from './_mobile.js'; 4 | 5 | export default () => { 6 | localToggle(); 7 | modeToggle(); 8 | mobileMenu(); 9 | 10 | console.log('look at me, I am project level js'); 11 | }; 12 | -------------------------------------------------------------------------------- /deployment/migrate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run any necessary database migration tasks that should happen while the 4 | # site is paused here. 5 | # 6 | # We don't have any, 3.x policy is safe migrations only. -Tom 7 | 8 | export NODE_ENV=production 9 | 10 | # node app @apostrophecms/migration:migrate 11 | # 12 | #echo "Site migrated" 13 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_admin.scss: -------------------------------------------------------------------------------- 1 | #apos-notification .apos-notifications { 2 | transform: scale(1.5); 3 | } 4 | 5 | .apos-area-menu--expanded 6 | { 7 | 8 | .apos-modal__inner--one-third { 9 | max-width: none !important; 10 | width: 450px !important; 11 | } 12 | 13 | .apos-modal__body { 14 | padding: 20px !important; 15 | } 16 | } -------------------------------------------------------------------------------- /modules/default-page/views/page.html: -------------------------------------------------------------------------------- 1 | {# 2 | This is an example home page template. It inherits and extends a layout template 3 | that lives in the top-level views/ folder for convenience 4 | #} 5 | 6 | {% extends "layout.html" %} 7 | 8 | {% block main %} 9 |
10 | {% area data.page, 'main' %} 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /modules/@apostrophecms/global/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fields: { 3 | add: { 4 | siteTitle: { 5 | label: 'project:siteTitle', 6 | type: 'string', 7 | def: 'project:apostropheCmsSite' 8 | } 9 | }, 10 | group: { 11 | general: { 12 | label: 'project:general', 13 | fields: [ 'siteTitle', 'footerLinks' ] 14 | } 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /modules/@apostrophecms/template/views/outerLayout.html: -------------------------------------------------------------------------------- 1 | {# 2 | This file extends Apostrophe's outerLayoutBase template, which we don't recommend 3 | overriding directly. Changes in this file, if any, are usually block overrides to 4 | modify the outer chrome, outside the main content area of the page. Most of the 5 | time you should just edit `layout.html` in the top level `views/` folder. 6 | #} 7 | 8 | {% extends "outerLayoutBase.html" %} 9 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_media.scss: -------------------------------------------------------------------------------- 1 | .demo-video { 2 | width: 100%; 3 | } 4 | 5 | .demo-image { 6 | width: 100%; 7 | max-width: 100%; 8 | border-radius: 10px; 9 | 10 | .widget { 11 | margin-bottom: 0; 12 | } 13 | } 14 | 15 | .demo-image__wrapper { 16 | margin: 0; 17 | } 18 | 19 | .demo-image__caption { 20 | margin-top: 0.5rem; 21 | font-style: italic; 22 | } 23 | 24 | .widget.demo-image__wrapper .widget { 25 | margin-bottom: 0 !important; 26 | } -------------------------------------------------------------------------------- /modules/asset/ui/src/index.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | @import '_global'; 3 | @import '_type'; 4 | @import '_admin'; 5 | @import '_header'; 6 | @import '_article'; 7 | @import '_pager'; 8 | @import '_rich-text'; 9 | @import '_button'; 10 | @import '_locales'; 11 | @import '_pr-widget'; 12 | @import '_footer'; 13 | @import '_nav'; 14 | @import '_hero'; 15 | @import '_card'; 16 | @import '_misc'; 17 | @import '_media'; 18 | @import '_widget'; 19 | @import '_price-card'; -------------------------------------------------------------------------------- /modules/@apostrophecms/admin-bar/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | groups: [ 4 | { 5 | name: 'media', 6 | label: 'project:media', 7 | items: [ 8 | '@apostrophecms/image', 9 | '@apostrophecms/file', 10 | '@apostrophecms/image-tag', 11 | '@apostrophecms/file-tag' 12 | ] 13 | } 14 | ], 15 | order: [ 16 | '@apostrophecms/image', 17 | 'article', 18 | 'article-category' 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /modules/article/views/recent.html: -------------------------------------------------------------------------------- 1 | {% import 'article-page:fragments.html' as fragments with context %} 2 | 3 |
9 | {% if data.articles.length %} 10 | {% for article in data.articles %} 11 | {% render fragments.excerpt(article) %} 12 | {% endfor %} 13 | {% else %} 14 |

{{ __t('project:noArticles') }}

15 | {% endif %} 16 |
17 | -------------------------------------------------------------------------------- /modules/@apostrophecms/page/index.js: -------------------------------------------------------------------------------- 1 | // This configures the @apostrophecms/pages module to add a "home" page type to the 2 | // pages menu 3 | 4 | export default { 5 | options: { 6 | types: [ 7 | { 8 | name: 'default-page', 9 | label: 'project:defaultPage' 10 | }, 11 | { 12 | name: 'article-page', 13 | label: 'project:articleIndexPage' 14 | }, 15 | { 16 | name: '@apostrophecms/home-page', 17 | label: 'project:home' 18 | } 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_pager.scss: -------------------------------------------------------------------------------- 1 | .pager { 2 | display: flex; 3 | margin: 20px 0; 4 | 5 | .pager__gap, 6 | .pager__item { 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | width: 20px; 11 | height: 20px; 12 | padding: 10px; 13 | margin: 0 5px; 14 | } 15 | 16 | .pager__item { 17 | background-color: #fff; 18 | box-shadow: 0 100px 140px -50px rgba(0,0,0,.25), 0 0 20px 0 rgba(0,0,0,.03); 19 | border-radius: 50%; 20 | 21 | *, a { 22 | color: inherit; 23 | text-decoration: none; 24 | } 25 | 26 | &.is-active { 27 | opacity: 0.5; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/settings/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | subforms: { 4 | title: { 5 | fields: [ 'title' ], 6 | protection: true, 7 | reload: true 8 | }, 9 | adminLocale: { 10 | fields: [ 'adminLocale' ] 11 | }, 12 | changePassword: { 13 | fields: [ 'password' ] 14 | } 15 | }, 16 | 17 | groups: { 18 | account: { 19 | label: 'project:account', 20 | subforms: [ 'title', 'changePassword' ] 21 | }, 22 | preferences: { 23 | label: 'project:preferences', 24 | subforms: [ 'adminLocale' ] 25 | } 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /modules/@apostrophecms/rich-text-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | label: 'project:richText', 4 | description: 'project:richTextDescription', 5 | previewImage: 'svg', 6 | className: 'widget demo-rte', 7 | imageStyles: [ 8 | { 9 | value: 'image-full', 10 | label: 'project:fullWidth' 11 | }, 12 | { 13 | value: 'image-center', 14 | label: 'project:center' 15 | }, 16 | { 17 | value: 'image-float-left', 18 | label: 'project:left' 19 | }, 20 | { 21 | value: 'image-float-right', 22 | label: 'project:right' 23 | } 24 | ] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /deployment/rsync_exclude.txt: -------------------------------------------------------------------------------- 1 | # List files and folders that shouldn't be deployed (such as data folders and runtime status files) here. 2 | # In our projects .git and .gitignore are good candidates, also 'data' which contains persistent files 3 | # that are *not* part of deployment. A good place for things like data/port, data/pid, and any 4 | # sqlite databases or static web content you may need 5 | data 6 | temp 7 | public/uploads 8 | public/apos-frontend 9 | .git 10 | .gitignore 11 | # We don't deploy these anymore, instead we always 'npm install' to ensure 12 | # that any compiled C++ modules are built for the right architecture 13 | node_modules 14 | # so staging is never old 15 | package-lock.json 16 | -------------------------------------------------------------------------------- /modules/@apostrophecms/home-page/views/page.html: -------------------------------------------------------------------------------- 1 | {# 2 | This is an example home page template. It inherits and extends a layout template 3 | that lives in the top-level views/ folder for convenience 4 | #} 5 | 6 | {% extends "layout.html" %} 7 | 8 | {# Home page is full width #} 9 | {% block pageTitle %} 10 |

11 | {{ __t('project:homeTitle') }} 12 |

13 | {% endblock %} 14 | {% block subnav %}{% endblock %} 15 | 16 | {% block main %} 17 |
18 |
19 |
20 | {% area data.page, 'main' %} 21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /modules/hero-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'link.html' as link %} 2 | {% set widget = data.widget %} 3 | 4 |
5 |
6 | {% area widget, 'content' %} 7 |
8 | {% if widget.links.length %} 9 |
10 | {% for item in widget.links %} 11 | {% set path = apos.helper.linkPath(item) %} 12 | {% set class = 'button ' + item.style %} 13 | {{ link.render({ 14 | label: item.linkText, 15 | path: path, 16 | target: item.linkTarget, 17 | class: class 18 | })}} 19 | {% endfor %} 20 |
21 | {% endif %} 22 |
23 | -------------------------------------------------------------------------------- /modules/article/ui/apos/components/DemoCellRelation.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */.DS_Store 2 | # ignore vim swapfiles 3 | *.swp 4 | /storybook-static 5 | /public/apos-frontend 6 | /apos-build 7 | /apos-frontend 8 | /locales 9 | npm-debug.log 10 | /data 11 | /public/uploads 12 | /public/apos-minified 13 | /data/temp/uploadfs 14 | /modules/asset/ui/public/site.js 15 | /modules/asset/ui/public/site.css 16 | node_modules 17 | # This folder is created on the fly and contains symlinks updated at startup (we'll come up with a Windows solution that actually copies things) 18 | /public/modules 19 | # We don't commit CSS, only LESS 20 | /public/css/*.css 21 | /public/css/*.map 22 | # Don't commit masters generated on the fly at startup, these import all the rest 23 | /public/css/master-*.less 24 | /.jshintrc 25 | /public/js/_site-compiled.js 26 | -------------------------------------------------------------------------------- /modules/button-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'link.html' as link %} 2 | {% set widget = data.widget %} 3 | {% set path = apos.helper.linkPath(widget) %} 4 | {% set class = '' %} 5 | {% if widget.block %} 6 | {% set class = class + ' button-widget--block-' + widget.block %} 7 | {% endif %} 8 | {% if widget.alignment %} 9 | {% set class = class + ' button-widget--alignment-' + widget.alignment %} 10 | {% endif %} 11 | 12 |
13 | {{ link.render({ 14 | label: widget.linkText, 15 | path: path, 16 | target: widget.linkTarget, 17 | class: 'button' 18 | })}} 19 |
20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apostrophe 3 demo and test project 2 | 3 | ## Get started 4 | 5 | 1. Install dependencies with `npm install`. 6 | 2. Add your first user with `node app @apostrophecms/user:add {MY_USERNAME} admin`. 7 | 8 | ## Running the project 9 | 10 | Run `npm run dev` to build the Apostrophe UI and start the site up. Remember, this is during alpha development, so we're all in "dev mode." 11 | 12 | ## Analyzing bundle size 13 | 14 | It is possible to analyze the size of the admin UI webpack bundle: 15 | 16 | ``` 17 | APOS_BUNDLE_ANALYZER=1 node app @apostrophecms/asset:build 18 | ``` 19 | 20 | This will display a visualization in your browser. Bear in mind that the admin UI bundle is only ever present for logged-in users, generally those with editing privileges and not the general public. 21 | -------------------------------------------------------------------------------- /modules/helper/index.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | export default { 4 | options: { 5 | alias: 'helper' 6 | }, 7 | init(self) { 8 | self.addHelpers({ 9 | linkPath: (link) => { 10 | if (!link) { 11 | return; 12 | } 13 | let path; 14 | if (link.linkType === 'page' && link._linkPage && link._linkPage[0]) { 15 | path = link._linkPage[0]._url; 16 | } else if (link.linkType === 'file' && link._linkFile && link._linkFile[0]) { 17 | path = link._linkFile[0]._url; 18 | } else if (link.linkType === 'custom') { 19 | path = link.linkUrl; 20 | } 21 | return path; 22 | }, 23 | formatDate: (date) => { 24 | return dayjs(date).format('MMMM D, YYYY'); 25 | } 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /modules/article-category/views/recent.html: -------------------------------------------------------------------------------- 1 | {% if data.articles.length %} 2 | {% for article in data.articles %} 3 |
4 | {% if not article._url %} 5 |

6 | 7 | {{ __t('project:needArticleIndex') }} 8 | 9 |

10 | {% endif %} 11 |

{{ article.title }}

12 |
13 | {% if not apos.area.isEmpty(article, 'blurb') %} 14 |

{% area article, 'blurb' %}

15 | {% else %} 16 |

{{ __t('project:noSummary') }}

17 | {% endif %} 18 |
19 |
20 | {% endfor %} 21 | {% else %} 22 |

{{ __t('project:noArticles') }}

23 | {% endif %} 24 | -------------------------------------------------------------------------------- /modules/article-page/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extend: '@apostrophecms/piece-page-type', 3 | options: { 4 | label: 'project:articleIndexPage', 5 | pluralLabel: 'project:articleIndexPages' 6 | }, 7 | fields: { 8 | add: { 9 | intro: { 10 | label: 'project:articleIntro', 11 | type: 'area', 12 | options: { 13 | limit: 1, 14 | widgets: { 15 | '@apostrophecms/rich-text': {} 16 | } 17 | } 18 | } 19 | } 20 | }, 21 | methods(self) { 22 | return { 23 | async beforeIndex(req) { 24 | req.data._categories = await self.apos.category.find(req).sort({ createdAt: -1 }).toArray(); 25 | } 26 | }; 27 | } 28 | // Infers from its name that it will display an index of articles, 29 | // as well as serving subpages for them 30 | }; 31 | -------------------------------------------------------------------------------- /modules/default-page/index.js: -------------------------------------------------------------------------------- 1 | import { fullConfigExpandedGroups } from '../../lib/area.js'; 2 | 3 | export default { 4 | extend: '@apostrophecms/page-type', 5 | options: { 6 | label: 'project:defaultPage', 7 | pluralLabel: 'project:defaultPages' 8 | }, 9 | fields: { 10 | add: { 11 | main: { 12 | label: 'project:main', 13 | type: 'area', 14 | options: { 15 | expanded: true, 16 | groups: fullConfigExpandedGroups 17 | }, 18 | def: [ 19 | 'hero', 20 | '@apostrophecms/layout', 21 | '@apostrophecms/rich-text' 22 | ] 23 | } 24 | }, 25 | group: { 26 | basics: { 27 | label: 'project:basics', 28 | fields: [ 29 | 'title', 30 | 'main' 31 | ] 32 | } 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /modules/@apostrophecms/layout-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% area data.widget, 'columns' with { 2 | aposStyle: '--grid-columns: ' + (data.options.columns or data.manager.options.columns) + ';' + 3 | ' --grid-gap: ' + (data.options.gap or data.manager.options.gap or '0') + ';' + 4 | ' --grid-rows: auto;' + 5 | ' --mobile-grid-rows: auto;' + 6 | ' --tablet-grid-rows: auto;' + 7 | ' --justify-items: ' + (data.options.defaultCellHorizontalAlignment or data.manager.options.defaultCellHorizontalAlignment or 'stretch') + ';' + 8 | ' --align-items: ' + (data.options.defaultCellVerticalAlignment or data.manager.options.defaultCellVerticalAlignment or 'stretch') + ';', 9 | aposClassName: 'layout-widget widget', 10 | aposParentOptions: data.options | merge({ widgetId: data.widget._id }), 11 | aposAttrs: { 12 | 'tablet-auto': true, 13 | 'mobile-auto': true 14 | } 15 | } %} 16 | -------------------------------------------------------------------------------- /local.example.js: -------------------------------------------------------------------------------- 1 | // Settings specific to this server. Change the URL 2 | // if you are deploying in production. Then copy to 3 | // data/local.js. That folder is shared by all 4 | // deployments in our stagecoach recipe 5 | 6 | export default { 7 | modules: { 8 | '@apostrophecms/assets': { 9 | // Set to true for full CSS and JS minify, on staging and production servers 10 | // minify: true 11 | }, 12 | // If these are your db settings then you don't need to be explicit. If not 13 | // you can uncomment this and get more specific. 14 | '@apostrophecms/db': { 15 | // uri: 'mongodb://localhost:27017/@apostrophecms/sandbox' 16 | // There is legacy support for host, port, name, user and password options, 17 | // but this is not necessary. They can all go in the uri option like this: 18 | // mongodb://user:password@host:port/dbname 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /lib/iconChoices.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | label: 'project:iconClock', 4 | value: 'clock' 5 | }, 6 | { 7 | label: 'project:iconBuilding', 8 | value: 'building' 9 | }, 10 | { 11 | label: 'project:iconLandmark', 12 | value: 'landmark' 13 | }, 14 | { 15 | label: 'project:iconCpu', 16 | value: 'cpu' 17 | }, 18 | { 19 | label: 'project:iconTrophy', 20 | value: 'trophy' 21 | }, 22 | { 23 | label: 'project:iconEarth', 24 | value: 'earth' 25 | }, 26 | { 27 | label: 'project:iconPeople', 28 | value: 'people' 29 | }, 30 | { 31 | label: 'project:iconChart', 32 | value: 'chart' 33 | }, 34 | { 35 | label: 'project:iconRocket', 36 | value: 'rocket' 37 | }, 38 | { 39 | label: 'project:iconPackage', 40 | value: 'package' 41 | }, 42 | { 43 | label: 'project:iconEditor', 44 | value: 'editor' 45 | } 46 | ]; 47 | -------------------------------------------------------------------------------- /modules/article/ui/apos/components/DemoCellImage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | -------------------------------------------------------------------------------- /public/fav/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /modules/asset/ui/src/_dark-light-switch.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | const APOS_LIGHT_DARK_KEY = 'apostrophe-demo-visual-preference'; 3 | 4 | apos.util.onReadyAndRefresh(() => { 5 | setInitial(); 6 | const toggle = document.querySelector('[data-mode-switch] input'); 7 | 8 | if (!toggle) { 9 | return; 10 | } 11 | 12 | toggle.addEventListener('change', toggleMode); 13 | 14 | function toggleMode () { 15 | document.body.classList.toggle('dark'); 16 | const pref = document.body.classList.contains('dark') ? 'dark' : 'light'; 17 | localStorage.setItem(APOS_LIGHT_DARK_KEY, pref); 18 | } 19 | }); 20 | 21 | function setInitial() { 22 | const pref = localStorage.getItem(APOS_LIGHT_DARK_KEY); 23 | if (pref === 'dark') { 24 | document.querySelector('[data-mode-switch] input').checked = true; 25 | document.body.classList.toggle('dark'); 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /deployment/stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Shut the site down, for instance by tweaking a .htaccess file to display 4 | # a 'please wait' notice, or stopping a node server 5 | 6 | export NODE_ENV=production 7 | 8 | if [ ! -f "app.js" ]; then 9 | echo "I don't see app.js in the current directory." 10 | exit 1 11 | fi 12 | 13 | # Stop the node app via 'forever'. You'll get a harmless warning if the app 14 | # was not already running. Use `pwd` to make sure we have a full path, 15 | # forever is otherwise easily confused and will stop every server with 16 | # the same filename 17 | forever stop `pwd`/app.js && echo "Site stopped" 18 | 19 | # Stop the app without 'forever'. We recommend using 'forever' for node apps, 20 | # but this may be your best bet for non-node apps 21 | # 22 | # if [ -f "data/pid" ]; then 23 | # kill `cat data/pid` 24 | # rm data/pid 25 | # echo "Site stopped" 26 | # else 27 | # echo "Site was not running" 28 | # fi 29 | 30 | -------------------------------------------------------------------------------- /modules/article-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extend: '@apostrophecms/widget-type', 3 | options: { 4 | name: 'article', 5 | label: 'project:articleRecentArticles', 6 | description: 'project:articleWidgetDescription', 7 | icon: 'text-subject', 8 | previewImage: 'svg' 9 | }, 10 | fields: { 11 | add: { 12 | limit: { 13 | type: 'integer', 14 | label: 'project:limit', 15 | def: 5 16 | }, 17 | display: { 18 | type: 'select', 19 | label: 'project:articleOrientation', 20 | def: 'vertical', 21 | choices: [ 22 | { 23 | label: 'project:vertical', 24 | value: 'vertical' 25 | }, 26 | { 27 | label: 'project:horizontal', 28 | value: 'horizontal' 29 | } 30 | ] 31 | } 32 | } 33 | }, 34 | icons: { 35 | 'text-subject': 'TextSubject' 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /deployment/settings: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Settings shared by all targets (staging, production, etc). Usually the 4 | # shortname of the project (which is also the hostname for the frontend 5 | # proxy server used for staging sites) and the directory name. For our 6 | # web apps that use sc-proxy we make sure each is a subdirectory 7 | # of /opt/stagecoach/apps 8 | 9 | # Should match the repo name = short name = everything name! 10 | PROJECT=public-demo 11 | 12 | DIR=/opt/stagecoach/apps/$PROJECT 13 | 14 | # Adjust the PATH environment variable on the remote host. Here's an example 15 | # for deploying to MacPorts 16 | #ADJUST_PATH='export PATH=/opt/local/bin:$PATH' 17 | 18 | # ... But you probably won't need to on real servers. I just find it handy for 19 | # testing parts of stagecoach locally on a Mac. : is an acceptable "no-op" (do-nothing) statement 20 | ADJUST_PATH=':' 21 | 22 | # ssh port. Sensible people leave this set to 22 but it's common to do the 23 | # "security by obscurity" thing alas 24 | SSH_PORT=22 25 | -------------------------------------------------------------------------------- /modules/button-widget/index.js: -------------------------------------------------------------------------------- 1 | import linkConfig from '../../lib/link.js'; 2 | 3 | export default { 4 | extend: '@apostrophecms/widget-type', 5 | options: { 6 | label: 'project:button', 7 | icon: 'cursor-default-click-icon', 8 | previewImage: 'svg', 9 | description: 'project:buttonDescription' 10 | }, 11 | fields: { 12 | add: { 13 | ...linkConfig.link, 14 | block: { 15 | type: 'boolean', 16 | label: 'project:fullWidth', 17 | def: false 18 | }, 19 | alignment: { 20 | type: 'select', 21 | label: 'project:buttonAlignment', 22 | choices: [ 23 | { 24 | label: 'project:left', 25 | value: 'left', 26 | def: true 27 | }, 28 | { 29 | label: 'project:center', 30 | value: 'center' 31 | }, 32 | { 33 | label: 'project:right', 34 | value: 'right' 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /modules/github-prs-widget/views/prs.html: -------------------------------------------------------------------------------- 1 | {% if not data.response %} 2 | Something isn't right :( 3 | {% elif data.response.message %} 4 |

{{ data.response.message }}

5 | {% else %} 6 |
    7 | {% for item in data.response %} 8 |
  1. 9 |

    10 | {{ item.title }} 11 |

    12 | 13 | item.user.login 14 | 15 | 16 |
    17 |

    Opened on {{ item.created_at | date('MMMM D YYYY') }}

    18 |

    Number {{ item.number }}

    19 |
    20 |
  2. 21 | {% endfor %} 22 |
23 | {% endif %} 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 P'unk Avenue LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /modules/card-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'link.html' as link %} 2 | {% import '@apostrophecms/template:macros/icons.html' as icons with context %} 3 | {% set widget = data.widget %} 4 | 5 |
10 | {% if widget.icon %} 11 |
12 | {{ icons[widget.icon]() }} 13 |
14 | {% endif %} 15 |
16 |
17 | {{ widget.title }} 18 |
19 |
20 | {{ widget.content }} 21 |
22 |
23 | {% if widget.linkText %} 24 | 34 | {% endif %} 35 |
36 | 37 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_locales.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | apos.util.onReadyAndRefresh(() => { 3 | const locales = document.querySelector('[data-locales]'); 4 | const toggler = document.querySelector('[data-locales-toggle]'); 5 | const localeList = document.querySelector('[data-locales-list]'); 6 | 7 | if (!toggler || !localeList) { 8 | return; 9 | } 10 | 11 | toggler.addEventListener('click', toggleLocales); 12 | 13 | window.addEventListener('click', clickOutside); 14 | 15 | function toggleLocales () { 16 | const expanded = toggler.getAttribute('aria-expanded') === 'true' || false; 17 | 18 | toggler.setAttribute('aria-expanded', !expanded); 19 | localeList.hidden = expanded; 20 | } 21 | 22 | function clickOutside ({ target }) { 23 | const expanded = toggler.getAttribute('aria-expanded') === 'true' || false; 24 | 25 | if (expanded && !locales.contains(target)) { 26 | toggler.setAttribute('aria-expanded', false); 27 | localeList.hidden = true; 28 | } 29 | } 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_global.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--default-color); 3 | background-color: var(--background-color); 4 | font-family: var(--default-font); 5 | padding: 0; 6 | margin: 0; 7 | } 8 | 9 | a { 10 | color: var(--nav-color); 11 | } 12 | 13 | main { 14 | display: flex; 15 | flex-direction: column; 16 | flex-basis: 100%; 17 | flex: 1; 18 | } 19 | 20 | .layout { 21 | display: flex; 22 | flex-direction: column; 23 | flex-wrap: wrap; 24 | max-width: var(--max-layout-width); 25 | margin-left: auto; 26 | margin-right: auto; 27 | padding: 0 20px; 28 | 29 | @media (max-width: 680px) { 30 | display: block; 31 | } 32 | 33 | @container (max-width: 680px) { 34 | display: block; 35 | } 36 | } 37 | 38 | .site-home { 39 | display: inline-block; 40 | } 41 | 42 | .site-logo { 43 | display: block; 44 | width: 180px; 45 | } 46 | 47 | .placeholder { 48 | opacity: 0.5; 49 | margin: 40px 0; 50 | font-style: italic; 51 | } 52 | 53 | .general-content .demo-rte { 54 | margin-right: auto; 55 | margin-left: auto; 56 | 57 | @media screen and (max-width: 600px) { 58 | overflow: scroll; 59 | } 60 | } -------------------------------------------------------------------------------- /modules/asset/ui/src/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | flex-direction: column; 6 | height: 62px; 7 | margin: 20px 0 40px 0; 8 | } 9 | 10 | .breadcrumb { 11 | display: flex; 12 | justify-content: center; 13 | margin-bottom: 1rem; 14 | 15 | @media (max-width: 680px) { 16 | flex-direction: column; 17 | text-align: center; 18 | } 19 | 20 | @container (max-width: 680px) { 21 | flex-direction: column; 22 | text-align: center; 23 | } 24 | } 25 | 26 | .breadcrumb a { 27 | font-size: .8em; 28 | display: inline-block; 29 | text-decoration: none; 30 | &:hover { 31 | text-decoration: underline; 32 | } 33 | &:after { 34 | margin: 0 8px; 35 | content: '/'; 36 | display: inline-block; 37 | text-decoration: none; 38 | color: black; 39 | 40 | @media (max-width: 680px) { 41 | content: none; 42 | } 43 | 44 | @container (max-width: 680px) { 45 | content: none; 46 | } 47 | } 48 | &:last-child:after { 49 | content: '' 50 | } 51 | } 52 | 53 | .current-page { 54 | color: inherit; 55 | text-decoration: none; 56 | } 57 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_mobile.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | apos.util.onReadyAndRefresh(() => { 3 | const trigger = document.querySelector('[data-mobile-trigger]'); 4 | const nav = document.querySelector('[data-mobile-nav]'); 5 | const closeTrigger = document.querySelector('[data-mobile-close-trigger]'); 6 | 7 | trigger.addEventListener('click', toggleMenu); 8 | closeTrigger.addEventListener('click', handleClose); 9 | 10 | function toggleMenu () { 11 | const state = nav.dataset.mobileNav; 12 | if (state === 'hidden') { 13 | nav.dataset.mobileNav = 'visible'; 14 | document.body.addEventListener('keydown', handleClose); 15 | } 16 | nav.setAttribute('aria-hidden', nav.getAttribute('aria-hidden') === 'true' ? 'false' : 'true'); 17 | nav.classList.toggle('active'); 18 | } 19 | 20 | function handleClose(e) { 21 | if (!e.key || e.key === 'Escape') { 22 | nav.classList.remove('active'); 23 | nav.dataset.mobileNav = 'hidden'; 24 | nav.setAttribute('aria-hidden', 'true'); 25 | document.body.removeEventListener('keydown', handleClose); 26 | } 27 | } 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_hero.scss: -------------------------------------------------------------------------------- 1 | .hero-widget__content { 2 | max-width: 66vw; 3 | margin: 0 auto; 4 | 5 | .demo-rte, .tiptap { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | text-align: center; 10 | } 11 | 12 | .demo-rte { 13 | margin-bottom: 1.5rem; 14 | } 15 | 16 | h2, h3, h4, p { 17 | text-align: center; 18 | } 19 | 20 | h2, h3, h4 { 21 | margin-bottom: 1.5rem; 22 | } 23 | 24 | p { 25 | font-size: 1.2rem; 26 | color: color-mix(in srgb, var(--default-color), transparent 20%); 27 | margin-top: 0; 28 | margin-bottom: 1rem; 29 | line-height: 1.6; 30 | max-width: 580px; 31 | 32 | &:last-of-type { 33 | margin-bottom: 0; 34 | } 35 | } 36 | 37 | .button { 38 | padding: 15px 30px; 39 | font-weight: 600; 40 | border-radius: 50px; 41 | text-decoration: none; 42 | transition: all 0.3s ease; 43 | font-size: 1rem; 44 | } 45 | 46 | @container (max-width: 800px) { 47 | max-width: 80vw; 48 | } 49 | 50 | @media (max-width: 800px) { 51 | max-width: 80vw; 52 | } 53 | } 54 | 55 | .hero-widget__buttons { 56 | display: flex; 57 | justify-content: center; 58 | gap: 1rem; 59 | } -------------------------------------------------------------------------------- /modules/asset/ui/src/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | margin-top: 5rem; 3 | background-color: var(--surface-color); 4 | padding: 3rem 0; 5 | border-top: 1px solid var(--nav-dropdown-background-color); 6 | } 7 | 8 | .footer__logo { 9 | svg { 10 | max-width: 200px; 11 | height: auto 12 | } 13 | } 14 | 15 | .footer__section { 16 | max-width: 1140px; 17 | margin: 0 auto; 18 | } 19 | 20 | .footer__top { 21 | display: grid; 22 | grid-template-columns: 1fr 1fr 1fr 1fr; 23 | grid-template-rows: 1fr; 24 | gap: 8px 10px; 25 | grid-template-areas: 26 | ". . . ."; 27 | } 28 | 29 | .footer__column--logo { 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: center; 33 | } 34 | 35 | .footer__column { 36 | text-align: center; 37 | ul { 38 | display: flex; 39 | flex-direction: column; 40 | gap: 10px; 41 | list-style: none; 42 | margin: 0; 43 | padding: 0; 44 | } 45 | } 46 | 47 | .footer__column ul a { 48 | text-decoration: none; 49 | font-weight: 500; 50 | font-size: 0.875rem; 51 | 52 | &:hover { 53 | text-decoration: underline; 54 | } 55 | } 56 | 57 | 58 | @media only screen and (max-width: 800px) { 59 | .footer__section { 60 | grid-template-columns: 1fr; 61 | grid-template-areas: "." 62 | } 63 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/i18n/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | locales: { 4 | en: { 5 | label: 'English', 6 | flag: 'US' 7 | }, 8 | fr: { 9 | label: 'French', 10 | prefix: '/fr' 11 | }, 12 | mx: { 13 | label: 'Spanish (MX)', 14 | flag: 'MX', 15 | prefix: '/mx' 16 | }, 17 | de: { 18 | label: 'German', 19 | prefix: '/de' 20 | } 21 | }, 22 | adminLocales: [ 23 | { 24 | label: 'French', 25 | value: 'fr' 26 | }, 27 | { 28 | label: 'English', 29 | value: 'en' 30 | }, 31 | { 32 | label: 'Spanish (MX)', 33 | value: 'mx' 34 | }, 35 | { 36 | label: 'German', 37 | value: 'de' 38 | } 39 | ] 40 | }, 41 | i18n: { 42 | project: { 43 | browser: true 44 | } 45 | }, 46 | extendHandlers(self) { 47 | return { 48 | '@apostrophecms/page:beforeSend': { 49 | async addLocalizations(_super, req) { 50 | await _super(req); 51 | req.data.localizations?.forEach(locale => { 52 | locale.flag = self.locales[locale.locale].flag || locale.locale.toUpperCase(); 53 | }); 54 | } 55 | } 56 | }; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_pr-widget.scss: -------------------------------------------------------------------------------- 1 | .gh-pr-widget { 2 | border: 10px solid black; 3 | border-radius: 20px; 4 | padding: 1.5rem; 5 | 6 | p { 7 | margin: 0; 8 | } 9 | } 10 | 11 | .gh-pr-widget__title { 12 | width: 100%; 13 | font-size: 32px; 14 | text-align: left; 15 | letter-spacing: 0px; 16 | line-height: 1.3; 17 | margin-bottom: 30px; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | } 22 | 23 | .gh-pr-widget__subtitle { 24 | font-size: 14px; 25 | line-height: 1.3; 26 | a { 27 | color: black; 28 | text-decoration: none; 29 | &:hover { 30 | color: #6516dd; 31 | text-decoration: underline; 32 | } 33 | } 34 | } 35 | 36 | .gh-pr-widget__items { 37 | padding-left: 20px; 38 | } 39 | 40 | .gh-pr-widget__item { 41 | margin-bottom: 20px; 42 | } 43 | 44 | .gh-pr-widget__details { 45 | display: flex; 46 | align-items: center; 47 | color: #6516dd; 48 | text-decoration: none; 49 | } 50 | 51 | .gh-pr-widget__avatar { 52 | margin-right: 10px; 53 | border-radius: 50%; 54 | height: 20px; 55 | width: 20px; 56 | } 57 | 58 | .gh-pr-widget__subdetails { 59 | display: flex; 60 | font-size: 85%; 61 | color: dimgrey; 62 | margin-top: 10px; 63 | } 64 | 65 | p.gh-pr-widget__subdetail { 66 | margin-right: 10px; 67 | } -------------------------------------------------------------------------------- /modules/asset/ui/src/_type.scss: -------------------------------------------------------------------------------- 1 | 2 | h1 { 3 | font-family: var(--heading-font); 4 | margin: 0; 5 | font-size: 80px; 6 | font-weight: 700; 7 | line-height: 1.1; 8 | letter-spacing: -3px; 9 | text-align: center; 10 | @media only screen and (max-width: 800px) { 11 | font-size: 50px; 12 | letter-spacing: -1px; 13 | } 14 | @media only screen and (max-width: 600px) { 15 | font-size: 40px; 16 | } 17 | 18 | @container (max-width: 800px) { 19 | font-size: 50px; 20 | letter-spacing: -1px; 21 | } 22 | @container (max-width: 600px) { 23 | font-size: 40px; 24 | } 25 | } 26 | 27 | h1, h2, h3, h4 { 28 | &:not([class^="apos"]) { 29 | font-family: var(--heading-font); 30 | } 31 | } 32 | 33 | .meta { 34 | display: inline-block; 35 | background-color: color-mix(in srgb, var(--accent-color), transparent 85%); 36 | color: color-mix(in srgb, var(--default-color), transparent 20%); 37 | padding: 0.5rem 1.25rem; 38 | border-radius: 4px; 39 | font-size: 0.75rem; 40 | font-weight: 700; 41 | letter-spacing: 1px; 42 | margin-bottom: 1.5rem; 43 | text-transform: uppercase; 44 | } 45 | 46 | .page-title-wrapper { 47 | margin-bottom: 80px; 48 | } 49 | 50 | .page-title { 51 | font-size: 50px; 52 | font-weight: 600; 53 | letter-spacing: -1px; 54 | line-height: 0.9; 55 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/home-page/index.js: -------------------------------------------------------------------------------- 1 | import { fullConfigExpandedGroups } from '../../../lib/area.js'; 2 | export default { 3 | options: { 4 | label: 'project:homePage', 5 | pluralLabel: 'project:homePages' 6 | }, 7 | fields: { 8 | add: { 9 | main: { 10 | label: 'project:main', 11 | type: 'area', 12 | options: { 13 | expanded: true, 14 | groups: fullConfigExpandedGroups 15 | } 16 | } 17 | }, 18 | group: { 19 | basics: { 20 | label: 'project:basics', 21 | fields: [ 22 | 'title', 23 | 'main' 24 | ] 25 | } 26 | } 27 | }, 28 | init(self) { 29 | self.apos.migration.add('simpleHomePage', async () => { 30 | await self.apos.migration.eachDoc({ 31 | type: '@apostrophecms/home-page' 32 | }, 5, async (doc) => { 33 | const rt = doc.cta.items[0]; 34 | if (rt) { 35 | rt.content = rt.content.replace(/

/g, '

'); 36 | rt.content = rt.content.replace(/<\/p>/g, '

'); 37 | } 38 | doc.main.items = [...doc.cta.items, ...doc.ctaLinks.items, ...doc.main.items]; 39 | delete doc.cta; 40 | delete doc.ctaLinks; 41 | self.apos.doc.db.replaceOne({ _id: doc._id }, doc); 42 | }); 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /modules/hero-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | hero 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scripts/sync-down: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TARGET="$1" 4 | if [ -z "$TARGET" ]; then 5 | echo "Usage: ./scripts/sync-down production" 6 | echo "(or as appropriate)" 7 | exit 1 8 | fi 9 | 10 | source deployment/settings || exit 1 11 | source "deployment/settings.$TARGET" || exit 1 12 | 13 | #Enter the Mongo DB name (should be same locally and remotely). 14 | dbName=$PROJECT 15 | 16 | #Enter the Project name (should be what you called it for stagecoach). 17 | projectName=$PROJECT 18 | 19 | #Enter the SSH username/url for the remote server. 20 | remoteSSH="-p $SSH_PORT $USER@$SERVER" 21 | rsyncTransport="ssh -p $SSH_PORT" 22 | rsyncDestination="$USER@$SERVER" 23 | 24 | echo "Syncing MongoDB" 25 | ssh $remoteSSH mongodump -d $dbName -o /tmp/mongodump.$dbName && 26 | rsync -av -e "$rsyncTransport" $rsyncDestination:/tmp/mongodump.$dbName/ /tmp/mongodump.$dbName && 27 | ssh $remoteSSH rm -rf /tmp/mongodump.$dbName && 28 | # noIndexRestore increases compatibility between 3.x and 2.x, 29 | # and Apostrophe will recreate the indexes correctly at startup 30 | mongorestore --noIndexRestore --drop -d $dbName /tmp/mongodump.$dbName/$dbName && 31 | echo "Syncing Files" && 32 | rsync -av --delete -e "$rsyncTransport" $rsyncDestination:/opt/stagecoach/apps/$projectName/uploads/ ./public/uploads && 33 | echo "Synced down from $TARGET" 34 | echo "YOU MUST RESTART THE SITE LOCALLY TO REBUILD THE MONGODB INDEXES." 35 | -------------------------------------------------------------------------------- /lib/link.js: -------------------------------------------------------------------------------- 1 | const link = { 2 | linkText: { 3 | label: 'project:linkText', 4 | type: 'string' 5 | }, 6 | linkType: { 7 | label: 'project:linkType', 8 | type: 'select', 9 | choices: [ 10 | { 11 | label: 'project:page', 12 | value: 'page' 13 | }, 14 | { 15 | label: 'project:file', 16 | value: 'file' 17 | }, 18 | { 19 | label: 'project:customUrl', 20 | value: 'custom' 21 | } 22 | ] 23 | }, 24 | _linkPage: { 25 | label: 'project:pageToLink', 26 | type: 'relationship', 27 | withType: '@apostrophecms/page', 28 | max: 1, 29 | builders: { 30 | project: { 31 | title: 1, 32 | _url: 1 33 | } 34 | }, 35 | if: { 36 | linkType: 'page' 37 | } 38 | }, 39 | _linkFile: { 40 | label: 'project:fileToLink', 41 | type: 'relationship', 42 | withType: '@apostrophecms/file', 43 | max: 1, 44 | if: { 45 | linkType: 'file' 46 | } 47 | }, 48 | linkUrl: { 49 | label: 'project:urlForCustom', 50 | type: 'url', 51 | if: { 52 | linkType: 'custom' 53 | } 54 | }, 55 | linkTarget: { 56 | label: 'project:openNewBrowserTab', 57 | type: 'checkboxes', 58 | choices: [ 59 | { 60 | label: 'project:openNewTab', 61 | value: '_blank' 62 | } 63 | ] 64 | } 65 | }; 66 | 67 | export default { link }; 68 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | display: inline-block; 3 | text-decoration: none; 4 | text-align: center; 5 | color: black; 6 | font-size: 1.1rem; 7 | font-weight: 500; 8 | border-radius: 50px; 9 | padding: 15px 30px; 10 | text-decoration: none; 11 | transition: background-color 250ms ease 0s; 12 | 13 | background: var(--accent-color); 14 | color: var(--contrast-color); 15 | border: 2px solid var(--accent-color); 16 | 17 | &:hover { 18 | background: color-mix(in srgb, var(--accent-color), black 10%); 19 | border-color: color-mix(in srgb, var(--accent-color), black 10%); 20 | transform: translateY(-2px); 21 | } 22 | } 23 | 24 | .button.outline { 25 | background: transparent; 26 | color: var(--accent-color); 27 | border: 2px solid var(--accent-color); 28 | 29 | &:hover { 30 | background: var(--accent-color); 31 | color: var(--contrast-color); 32 | transform: translateY(-2px); 33 | } 34 | } 35 | 36 | .button--small { 37 | display: inline-flex; 38 | justify-content: center; 39 | align-items: center; 40 | height: 44px; 41 | padding: 0 16px 0 20px; 42 | border-radius: 12px; 43 | font-size: .875rem; 44 | line-height: 1rem; 45 | } 46 | 47 | .button-widget { 48 | display: flex; 49 | } 50 | 51 | .button-widget--alignment-left { justify-content: flex-start; } 52 | .button-widget--alignment-center { justify-content: center; } 53 | .button-widget--alignment-right { justify-content: flex-end; } 54 | .button-widget--block-true .button { 55 | display: block; 56 | width: 100%; 57 | } 58 | 59 | .container-widget .button-widget { margin-bottom: 10px; } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "public-demo", 3 | "version": "1.0.0", 4 | "description": "Apostrophe Public Demo Site", 5 | "main": "app.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node app", 9 | "dev": "APOS_DEV=1 nodemon --delay 1000ms -x \"nodemon app.js\"", 10 | "staging-deploy": "echo \"ℹ️ Clearing module links and installed modules to deploy.\" && rm package-lock.json && rm -r node_modules && npm i && sc-deploy staging", 11 | "analyze-bundle": "APOS_BUNDLE_ANALYZER=1 node app @apostrophecms/asset:build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "" 16 | }, 17 | "dependencies": { 18 | "@apostrophecms/import-export": "^2.4.1", 19 | "@apostrophecms/seo": "^1.2.3", 20 | "@apostrophecms/vite": "^1.1.0", 21 | "apostrophe": "^4.24.1" 22 | }, 23 | "author": "Apostrophe Technologies, Inc", 24 | "license": "MIT", 25 | "nodemonConfig": { 26 | "verbose": true, 27 | "watch": [ 28 | "./app.js", 29 | "./modules/**/*", 30 | "./lib/**/*.js", 31 | "./views/**/*.html", 32 | "./src/**/*", 33 | "./node_modules/apostrophe/modules/@apostrophecms/**/*" 34 | ], 35 | "ignoreRoot": [ 36 | ".git" 37 | ], 38 | "ignore": [ 39 | "lib/modules/*/public/js/*.js", 40 | "locales/*.json", 41 | "public/uploads", 42 | "public/apos-frontend", 43 | "modules/asset/ui/public", 44 | "data" 45 | ], 46 | "ext": "json, js, html, scss, vue" 47 | }, 48 | "devDependencies": { 49 | "eslint": "^7.32.0", 50 | "eslint-config-apostrophe": "^3.4.0", 51 | "eslint-plugin-node": "^11.1.0", 52 | "nodemon": "^2.0.12" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /deployment/dependencies: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Also a good place to ensure any data folders 4 | # that are *not* supposed to be replaced on every deployment exist 5 | # and create a symlink to them from the latest deployment directory. 6 | 7 | # The real 'data' folder is shared. It lives two levels up and one over 8 | # (we're in a deployment dir, which is a subdir of 'deployments', which 9 | # is a subdir of the project's main dir) 10 | 11 | export NODE_ENV=production 12 | 13 | HERE=`pwd` 14 | mkdir -p ../../data 15 | ln -s ../../data $HERE/data 16 | 17 | # We also have a shared uploads folder which is convenient to keep 18 | # in a separate place so we don't have to have two express.static calls 19 | 20 | mkdir -p ../../uploads 21 | ln -s ../../../uploads $HERE/public/uploads 22 | 23 | # Install any dependencies that can't just be rsynced over with 24 | # the deployment. Example: node apps have npm modules in a 25 | # node_modules folder. These may contain compiled C++ code that 26 | # won't work portably from one server to another. 27 | 28 | # This script runs after the rsync, but before the 'stop' script, 29 | # so your app is not down during the npm installation. 30 | 31 | # Make sure node_modules exists so npm doesn't go searching 32 | # up the filesystem tree 33 | mkdir -p node_modules 34 | 35 | # If there is no package.json file then we don't need npm install 36 | if [ -f './package.json' ]; then 37 | # Install npm modules 38 | # Use a suitable version of Python 39 | # export PYTHON=/usr/bin/python26 40 | npm install 41 | if [ $? -ne 0 ]; then 42 | echo "Error during npm install!" 43 | exit 1 44 | fi 45 | fi 46 | 47 | node app @apostrophecms/migration:migrate && 48 | node app @apostrophecms/asset:build 49 | -------------------------------------------------------------------------------- /modules/article-page/views/fragments.html: -------------------------------------------------------------------------------- 1 | {% fragment excerpt(article) %} 2 | {% set attachment = apos.image.first(article._image) %} 3 | {% set url = apos.attachment.url(attachment, { size: size }) %} 4 |
5 | {% if url %} 6 | 7 | {{ attachment.title }} 8 | 9 | {% endif %} 10 |
11 | {% if not article._url %} 12 |

13 | {{ __t('project:needArticleIndex') }} 14 |

15 | {% endif %} 16 | {% if article._categories.length %} 17 |
18 | {% for category in article._categories %} 19 | {{ category.title }} 20 | {% endfor %} 21 |
22 | {% endif %} 23 | {% if article._author.length %} 24 | 27 | {% endif %} 28 | 31 |

{{ article.title }}

32 |
33 | {% if not apos.area.isEmpty(article, 'blurb') %} 34 | {% area article, 'blurb' %} 35 | {% else %} 36 |

{{ __t('project:noSummary') }}

37 | {% endif %} 38 |
39 |
40 |
41 | {% endfragment %} -------------------------------------------------------------------------------- /modules/asset/ui/src/_variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --nav-color: #4a4b64; 3 | --nav-hover-color: #0b1ae9; 4 | --nav-mobile-background-color: #ffffff; 5 | --nav-dropdown-background-color: #ffffff; 6 | --nav-dropdown-color: #4a4b64; 7 | --nav-dropdown-hover-color: #0b1ae9; 8 | --medium-color: #afafaf; 9 | --faint-color: #e8e8e8; 10 | --default-color: #4a4b64; 11 | --heading-color: #242859; 12 | --accent-color: #0b1ae9; 13 | --surface-color: #efefef; 14 | --background-color: #ffffff; 15 | --contrast-color: #ffffff; 16 | --mobile-nav-bg: #242859F0; 17 | --default-font: "Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 18 | --heading-font: "Quicksand", sans-serif; 19 | --nav-font: "Poppins", sans-serif; 20 | --widget-spacer: 50px; 21 | --brand-red: #ea433a; 22 | --brand-mustard: #cc9300; 23 | --brand-blue: #66f; 24 | --brand-purple: #b327bf; 25 | --brand-seafoam: #00bf9a; 26 | --link-color: rgb(101, 22, 221); 27 | --max-layout-width: 1100px; 28 | } 29 | 30 | @media screen and (max-width: 800px) { 31 | body { 32 | --widget-spacer: 30px; 33 | } 34 | } 35 | 36 | .dark { 37 | --nav-color: #c8c6e3; 38 | --nav-hover-color: #ffffff; 39 | --nav-mobile-background-color: #03051f; 40 | --nav-dropdown-background-color: #1f2240; 41 | --nav-dropdown-color: #c8c6e3; 42 | --nav-dropdown-hover-color: #ffffff; 43 | --background-color: #05071e; 44 | --medium-color: #696871; 45 | --faint-color: #303034; 46 | --default-color: #e8e7f7; 47 | --heading-color: #ffffff; 48 | --accent-color: #524dd3; 49 | --surface-color: #131428; 50 | --contrast-color: #ffffff; 51 | } -------------------------------------------------------------------------------- /modules/article-page/views/show.html: -------------------------------------------------------------------------------- 1 | {% import 'fragments.html' as fragments with context %} 2 | 3 | {% extends "layout.html" %} 4 | {% set article = data.piece %} 5 | {% set attachment = apos.image.first(article._image) %} 6 | {% set url = apos.attachment.url(attachment, { size: size }) %} 7 | 8 | {% block pageTitle %} 9 |
10 |
11 | {% set title = data.page.title %} 12 | {% if data.piece %} 13 | {% set title = data.piece.title %} 14 | {% endif %} 15 |

{{ title }}

16 |
17 |
18 | {% if article._author.length %} 19 | 25 | {% endif %} 26 | 29 |
30 | {% if article._categories.length %} 31 |
32 | {% for category in article._categories %} 33 | {{ category.title }} 34 | {% endfor %} 35 |
36 | {% endif %} 37 |
38 | {% endblock %} 39 | 40 | {% block main %} 41 |
42 | {% if url %} 43 | {{ article.title }} 44 | {% endif %} 45 | {% area article, 'main' %} 46 |
47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /scripts/sync-up: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET="$1" 4 | if [ -z "$TARGET" ]; then 5 | echo "Usage: ./scripts/sync-up production" 6 | echo "(or as appropriate)" 7 | echo 8 | echo "THIS WILL CLOBBER EVERYTHING ON THE" 9 | echo "TARGET SITE. MAKE SURE THAT IS WHAT" 10 | echo "YOU WANT!" 11 | exit 1 12 | fi 13 | 14 | read -p "THIS WILL CRUSH THE SITE'S CONTENT ON $TARGET. Are you sure? " -n 1 -r 15 | echo 16 | if [[ ! $REPLY =~ ^[Yy]$ ]] 17 | then 18 | exit 1 19 | fi 20 | 21 | source deployment/settings || exit 1 22 | source "deployment/settings.$TARGET" || exit 1 23 | 24 | #Enter the Mongo DB name (should be same locally and remotely). 25 | dbName=$PROJECT 26 | 27 | #Enter the Project name (should be what you called it for stagecoach). 28 | projectName=$PROJECT 29 | 30 | #Enter the SSH username/url for the remote server. 31 | remoteSSH="-p $SSH_PORT $USER@$SERVER" 32 | rsyncTransport="ssh -p $SSH_PORT" 33 | rsyncDestination="$USER@$SERVER" 34 | 35 | echo "Syncing MongoDB" 36 | mongodump -d $dbName -o /tmp/mongodump.$dbName && 37 | echo rsync -av -e "$rsyncTransport" /tmp/mongodump.$dbName/ $rsyncDestination:/tmp/mongodump.$dbName && 38 | rsync -av -e "$rsyncTransport" /tmp/mongodump.$dbName/ $rsyncDestination:/tmp/mongodump.$dbName && 39 | rm -rf /tmp/mongodump.$dbName && 40 | # noIndexRestore increases compatibility between 3.x and 2.x, 41 | # and Apostrophe will recreate the indexes correctly at startup 42 | ssh $remoteSSH mongorestore --noIndexRestore --drop -d $dbName /tmp/mongodump.$dbName/$dbName && 43 | echo "Syncing Files" && 44 | rsync -av --delete -e "$rsyncTransport" ./public/uploads/ $rsyncDestination:/opt/stagecoach/apps/$projectName/uploads && 45 | echo "Synced up to $TARGET" 46 | echo "YOU MUST RESTART THE SITE ON $TARGET TO REBUILD THE MONGODB INDEXES." 47 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_card.scss: -------------------------------------------------------------------------------- 1 | .card-widget { 2 | display: flex; 3 | align-items: center; 4 | gap: 16px; 5 | padding: 20px; 6 | background: var(--surface-color); 7 | border-radius: 12px; 8 | box-shadow: 0 8px 25px color-mix(in srgb, var(--default-color), transparent 92%); 9 | transition: all 0.3s ease; 10 | 11 | &:hover:not(.card-widget--bg-false) { 12 | transform: translateY(-5px); 13 | box-shadow: 0 15px 40px color-mix(in srgb, var(--default-color), transparent 88%); 14 | } 15 | 16 | &--bg-false { 17 | background: transparent; 18 | box-shadow: none; 19 | } 20 | 21 | &--orientation-vertical { 22 | flex-direction: column; 23 | align-content: center; 24 | justify-content: center; 25 | gap: 1.5rem; 26 | text-align: center; 27 | padding-top: 2rem; 28 | padding-bottom: 2rem; 29 | 30 | .card__content { 31 | gap: 1rem; 32 | } 33 | 34 | .card__icon { 35 | width: 64px; 36 | min-width: 64px; 37 | height: 64px; 38 | } 39 | 40 | .card__icon svg { 41 | width: 32px; 42 | height: auto; 43 | } 44 | } 45 | } 46 | 47 | .card__content { 48 | display: flex; 49 | flex-direction: column; 50 | gap: 0.5rem; 51 | } 52 | 53 | .card__icon { 54 | width: 48px; 55 | min-width: 48px; 56 | height: 48px; 57 | background: color-mix(in srgb, var(--accent-color), transparent 90%); 58 | border-radius: 10px; 59 | display: flex; 60 | align-items: center; 61 | justify-content: center; 62 | 63 | svg { 64 | color: var(--accent-color); 65 | } 66 | } 67 | 68 | .card__title { 69 | font-size: 1.5rem; 70 | font-weight: 600; 71 | color: var(--heading-color); 72 | line-height: 1; 73 | } 74 | 75 | .card__text { 76 | font-size: 1rem; 77 | color: color-mix(in srgb, var(--default-color), transparent 40%); 78 | font-weight: 400; 79 | line-height: 1.3; 80 | } -------------------------------------------------------------------------------- /modules/price-card-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'link.html' as link %} 2 | {% import '@apostrophecms/template:macros/icons.html' as icons with context %} 3 | {% set widget = data.widget %} 4 | 5 | 54 | 55 | -------------------------------------------------------------------------------- /modules/card-widget/index.js: -------------------------------------------------------------------------------- 1 | import linkConfig from '../../lib/link.js'; 2 | import iconChoices from '../../lib/iconChoices.js'; 3 | import { klona } from 'klona'; 4 | 5 | const localLinkConfig = klona(linkConfig.link); 6 | 7 | localLinkConfig.linkText.if = 8 | localLinkConfig.linkType.if = 9 | localLinkConfig.linkTarget.if = { 10 | orientation: 'vertical' 11 | }; 12 | 13 | export default { 14 | extend: '@apostrophecms/widget-type', 15 | options: { 16 | label: 'project:card', 17 | icon: 'link-icon', 18 | previewImage: 'svg', 19 | description: 'project:cardAdd' 20 | }, 21 | fields: { 22 | add: { 23 | title: { 24 | label: 'project:title', 25 | type: 'string' 26 | }, 27 | content: { 28 | label: 'project:content', 29 | type: 'string' 30 | }, 31 | icon: { 32 | label: 'project:icon', 33 | type: 'select', 34 | choices: iconChoices 35 | }, 36 | bg: { 37 | label: 'project:cardBackground', 38 | type: 'boolean', 39 | def: true 40 | }, 41 | orientation: { 42 | label: 'project:orientation', 43 | type: 'select', 44 | choices: [ 45 | { 46 | value: 'horizontal', 47 | label: 'project:horizontal' 48 | }, 49 | { 50 | value: 'vertical', 51 | label: 'project:vertical' 52 | } 53 | ], 54 | def: 'horizontal' 55 | }, 56 | ...localLinkConfig, 57 | style: { 58 | type: 'select', 59 | label: 'project:linkStyle', 60 | if: { 61 | orientation: 'vertical' 62 | }, 63 | choices: [ 64 | { 65 | label: 'project:primary', 66 | value: 'primary', 67 | def: true 68 | }, 69 | { 70 | label: 'project:outline', 71 | value: 'outline' 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /modules/button-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | button 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /views/locales.html: -------------------------------------------------------------------------------- 1 |
2 | 20 | 36 |
37 | -------------------------------------------------------------------------------- /modules/hero-widget/index.js: -------------------------------------------------------------------------------- 1 | import linkConfig from '../../lib/link.js'; 2 | 3 | export default { 4 | extend: '@apostrophecms/widget-type', 5 | options: { 6 | label: 'project:hero', 7 | icon: 'link-icon', 8 | previewImage: 'svg', 9 | description: 'project:heroDescription' 10 | }, 11 | fields: { 12 | add: { 13 | content: { 14 | label: 'project:textContent', 15 | help: 'project:textContentHelp', 16 | type: 'area', 17 | max: 1, 18 | options: { 19 | widgets: { 20 | '@apostrophecms/rich-text': { 21 | toolbar: [ 22 | 'styles', 23 | 'bold', 24 | 'italic' 25 | ], 26 | styles: [ 27 | { 28 | tag: 'p', 29 | label: 'project:rtParagraph' 30 | }, 31 | { 32 | tag: 'h2', 33 | label: 'project:rtH2' 34 | }, 35 | { 36 | tag: 'h3', 37 | label: 'project:rtH3' 38 | }, 39 | { 40 | tag: 'h4', 41 | label: 'project:rtH4' 42 | } 43 | ] 44 | } 45 | } 46 | } 47 | }, 48 | links: { 49 | label: 'project:buttonLinks', 50 | help: 'project:buttonLinksDescription', 51 | type: 'array', 52 | titleField: 'linkText', 53 | fields: { 54 | add: { 55 | ...linkConfig.link, 56 | style: { 57 | type: 'select', 58 | label: 'project:style', 59 | choices: [ 60 | { 61 | label: 'project:primary', 62 | value: 'primary', 63 | def: true 64 | }, 65 | { 66 | label: 'project:outline', 67 | value: 'outline' 68 | } 69 | ] 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /modules/github-prs-widget/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extend: '@apostrophecms/widget-type', 3 | options: { 4 | name: 'githubPrs', 5 | label: 'project:ghPrLabel', 6 | previewImage: 'svg', 7 | icon: 'github', 8 | description: 'project:ghPrDescription' 9 | }, 10 | components(self) { 11 | return { 12 | async prs(req, data) { 13 | const w = data.widget; 14 | const token = self.options.token; 15 | const options = token 16 | ? { 17 | headers: { 18 | Authorization: `token ${token}` 19 | } 20 | } 21 | : {}; 22 | let body = {}; 23 | try { 24 | body = await self.apos.http.get( 25 | `https://api.github.com/repos/${w.repo}/pulls?state=${w.state}&per_page=${w.limit}`, 26 | options 27 | ); 28 | } catch (error) { 29 | if (error.status === 403 && !token) { 30 | body.message = 'Rate limit exceeded, see README for providing a GitHub API token'; 31 | } else { 32 | body.message = 'Something went wrong :('; 33 | } 34 | } 35 | return { 36 | response: body, 37 | widget: w 38 | }; 39 | } 40 | }; 41 | }, 42 | fields: { 43 | add: { 44 | repo: { 45 | type: 'string', 46 | label: 'project:repo', 47 | def: 'apostrophecms/apostrophe', 48 | help: 'project:formattedLike', 49 | required: true 50 | }, 51 | limit: { 52 | type: 'integer', 53 | label: 'project:limit', 54 | def: 5, 55 | required: true, 56 | max: 100, 57 | min: 1 58 | }, 59 | state: { 60 | type: 'select', 61 | label: 'project:state', 62 | required: true, 63 | choices: [ 64 | { 65 | label: 'project:open', 66 | value: 'open', 67 | def: true 68 | }, 69 | { 70 | label: 'project:closed', 71 | value: 'closed' 72 | } 73 | ] 74 | } 75 | } 76 | }, 77 | icons: { 78 | github: 'Github' 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import apostrophe from 'apostrophe'; 2 | 3 | apostrophe({ 4 | root: import.meta, 5 | shortName: 'public-demo', 6 | 7 | // The baseUrl should be overridden in environment variables for other environments. 8 | baseUrl: 'http://localhost:3000', 9 | 10 | // See lib/modules for basic project-level configuration of our modules 11 | // responsible for serving static assets, managing page templates and 12 | // configuring user accounts. 13 | 14 | modules: { 15 | 16 | // Apostrophe module configuration 17 | 18 | // Note: most configuration occurs in the respective 19 | // modules' directories. See lib/@apostrophecms/assets/index.js for an example. 20 | 21 | // However any modules that are not present by default in Apostrophe must at 22 | // least have a minimal configuration here to turn them on: `moduleName: {}` 23 | '@apostrophecms/vite': {}, 24 | 25 | // Manages apostrophe's overall asset pipeline 26 | '@apostrophecms/asset': { 27 | // When not in production, refresh the page on restart 28 | // options: { 29 | // // refreshOnRestart: true, 30 | // hmr: 'apos' 31 | // } 32 | }, 33 | 34 | // Manage page and piece SEO metadata 35 | '@apostrophecms/seo': {}, 36 | 37 | // A home for our own project-specific javascript and SASS assets 38 | asset: {}, 39 | 40 | // Template helpers 41 | helper: {}, 42 | 43 | // Widgets 44 | '@apostrophecms/rich-text-widget': {}, 45 | '@apostrophecms/image-widget': {}, 46 | '@apostrophecms/video-widget': {}, 47 | 'button-widget': {}, 48 | 'github-prs-widget': {}, 49 | 'hero-widget': {}, 50 | 'card-widget': {}, 51 | 'price-card-widget': {}, 52 | 53 | // A page type for ordinary pages 54 | 'default-page': {}, 55 | 56 | // A piece type for articles 57 | article: {}, 58 | 59 | // Tease an article on any page 60 | 'article-widget': {}, 61 | 62 | // Paginated index of articles, and with pages for individual articles 63 | 'article-page': {}, 64 | 65 | // A piece type for categorizing articles 66 | 'article-category': {}, 67 | 68 | // Import and export content 69 | '@apostrophecms/import-export': {} 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /modules/article-page/views/index.html: -------------------------------------------------------------------------------- 1 | {% import '@apostrophecms/pager:macros.html' as pager with context %} 2 | {% import 'fragments.html' as fragments with context %} 3 | 4 | {% extends "layout.html" %} 5 | 6 | {% block pageTitle %} 7 |
8 |
9 | {% set title = data.page.title %} 10 | {% if data.piece %} 11 | {% set title = data.piece.title %} 12 | {% endif %} 13 |

{{ title }}

14 | 34 |
35 |
36 | {% endblock %} 37 | 38 | {% block main %} 39 |
40 |
41 | {% area data.page, 'intro' %} 42 |
43 | 44 | {# Give first two excerpts a different treatment #} 45 |
46 | {% for article in data.pieces %} 47 | {% if loop.index === 1 or loop.index === 2 %} 48 | {% render fragments.excerpt(article) %} 49 | {% endif %} 50 | {% endfor %} 51 |
52 | 53 | {# Do the rest normally #} 54 |
55 | {% for article in data.pieces %} 56 | {% if loop.index !== 1 and loop.index !== 2 %} 57 | {% render fragments.excerpt(article) %} 58 | {% endif %} 59 | {% endfor %} 60 |
61 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 62 | 63 |
64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /modules/price-card-widget/index.js: -------------------------------------------------------------------------------- 1 | import linkConfig from '../../lib/link.js'; 2 | import iconChoices from '../../lib/iconChoices.js'; 3 | import { klona } from 'klona'; 4 | const localLinkConfig = klona(linkConfig.link); 5 | const localIcons = klona(iconChoices); 6 | 7 | export default { 8 | extend: '@apostrophecms/widget-type', 9 | icons: { 10 | 'card-bulleted-icon': 'CardBulleted' 11 | }, 12 | options: { 13 | label: 'project:pricingCard', 14 | icon: 'card-bulleted-icon', 15 | previewImage: 'svg', 16 | description: 'project:pricingCardDescription' 17 | }, 18 | fields: { 19 | add: { 20 | title: { 21 | label: 'project:title', 22 | type: 'string' 23 | }, 24 | content: { 25 | label: 'project:content', 26 | type: 'string' 27 | }, 28 | badge: { 29 | label: 'project:addBadge', 30 | type: 'boolean', 31 | def: false 32 | }, 33 | badgeIcon: { 34 | if: { 35 | badge: true 36 | }, 37 | label: 'project:icon', 38 | type: 'select', 39 | choices: localIcons 40 | }, 41 | badgeLabel: { 42 | if: { 43 | badge: true 44 | }, 45 | label: 'project:badgeLabel', 46 | type: 'string' 47 | }, 48 | featured: { 49 | label: 'project:specialBackground', 50 | type: 'boolean', 51 | def: false 52 | }, 53 | priceText: { 54 | label: 'project:priceText', 55 | help: 'project:priceTextHelp', 56 | type: 'string' 57 | }, 58 | priceTextUnit: { 59 | label: 'project:priceUnit', 60 | help: 'project:priceUnitHelp', 61 | type: 'string' 62 | }, 63 | priceDetail: { 64 | label: 'project:priceDetail', 65 | type: 'string' 66 | }, 67 | features: { 68 | type: 'array', 69 | inline: true, 70 | label: 'project:listOfFeature', 71 | help: 'project:listOfFeatureHelp', 72 | fields: { 73 | add: { 74 | item: { 75 | type: 'string', 76 | label: 'project:feature' 77 | } 78 | } 79 | } 80 | }, 81 | ...localLinkConfig 82 | } 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_price-card.scss: -------------------------------------------------------------------------------- 1 | .price-card-widget { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | padding: 20px; 6 | background: var(--surface-color); 7 | border-radius: 12px; 8 | box-shadow: 0 8px 25px color-mix(in srgb, var(--default-color), transparent 92%); 9 | transition: all 0.3s ease; 10 | } 11 | 12 | .price-card-widget--featured-true { 13 | background-color: var(--default-color); 14 | color: var(--contrast-color); 15 | 16 | .price-card__chip { 17 | background-color: var(--contrast-color); 18 | } 19 | 20 | .price-card__button { 21 | background-color: var(--contrast-color); 22 | color: var(--default-color); 23 | } 24 | } 25 | 26 | .price-card__badge { 27 | display: flex; 28 | justify-content: flex-end; 29 | } 30 | 31 | .price-card__chip { 32 | display: flex; 33 | align-items: center; 34 | gap: 5px; 35 | } 36 | 37 | .price-card__badge-label { 38 | font-size: 0.65rem; 39 | } 40 | 41 | .price-card__badge-icon { 42 | width: 16px; 43 | 44 | svg { 45 | max-width: 100%; 46 | height: auto; 47 | } 48 | } 49 | 50 | .price-card__title, 51 | .price-card__price-text { 52 | font-size: 2rem; 53 | font-weight: 500; 54 | letter-spacing: 0; 55 | } 56 | 57 | .price-card__title { 58 | margin: 0 0 1rem; 59 | } 60 | 61 | .price-card__price-detail { 62 | opacity: 0.75; 63 | font-size: 0.875rem; 64 | } 65 | 66 | .price-card__content { 67 | margin: 0 0 1rem; 68 | } 69 | 70 | .price-card__price { 71 | display: flex; 72 | align-items: baseline; 73 | gap: 2px; 74 | } 75 | 76 | .price-card__features { 77 | display: flex; 78 | flex-direction: column; 79 | margin: 1rem 0 4rem; 80 | padding: 1.5rem 0 0; 81 | border-top: 1px solid var(--medium-color); 82 | list-style-type: none; 83 | gap: 0.45rem; 84 | 85 | li { 86 | &:before { 87 | content: '✓'; 88 | margin-right: 0.5rem; 89 | } 90 | } 91 | } 92 | 93 | .price-card__features, 94 | .price-card__content, 95 | .price-card__price-detail 96 | { 97 | font-size: 0.875rem; 98 | line-height: 1.3; 99 | } 100 | 101 | .price-card__button { 102 | background-color: var(--default-color); 103 | color: var(--contrast-color); 104 | border: none; 105 | } -------------------------------------------------------------------------------- /deployment/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make the site live again, for instance by tweaking a .htaccess file 4 | # or starting a node server. In this example we also set up a 5 | # data/port file so that sc-proxy.js can figure out what port 6 | # to forward traffic to for this site. The idea is that every 7 | # folder in /var/webapps represents a separate project with a separate 8 | # node process, each listening on a specific port, and they all 9 | # need traffic forwarded from a reverse proxy server on port 80 10 | 11 | # Useful for debugging 12 | #set -x verbose 13 | 14 | # Express should not reveal information on errors, 15 | # also optimizes Express performance 16 | export NODE_ENV=production 17 | 18 | if [ ! -f "app.js" ]; then 19 | echo "I don't see app.js in the current directory." 20 | exit 1 21 | fi 22 | 23 | # Assign a port number if we don't yet have one 24 | 25 | if [ -f "data/port" ]; then 26 | PORT=`cat data/port` 27 | else 28 | # No port set yet for this site. Scan and sort the existing port numbers if any, 29 | # grab the highest existing one 30 | PORT=`cat ../../../*/data/port 2>/dev/null | sort -n | tail -1` 31 | if [ "$PORT" == "" ]; then 32 | echo "First app ever, assigning port 3000" 33 | PORT=3000 34 | else 35 | # Bash is much nicer than sh! We can do math without tears! 36 | let PORT+=1 37 | fi 38 | echo $PORT > data/port 39 | echo "First startup, chose port $PORT for this site" 40 | fi 41 | 42 | # Run the app via 'forever' so that it restarts automatically if it fails 43 | # Use `pwd` to make sure we have a full path, forever is otherwise easily confused 44 | # and will stop every server with the same filename 45 | 46 | # Use a "for" loop. A classic single-port file will do the 47 | # right thing, but so will a file with multiple port numbers 48 | # for load balancing across multiple cores 49 | for port in $PORT 50 | do 51 | export PORT=$port 52 | forever --minUptime=1000 --spinSleepTime=10000 -o data/console.log -e data/error.log start `pwd`/app.js && echo "Site started" 53 | done 54 | 55 | # Run the app without 'forever'. Record the process id so 'stop' can kill it later. 56 | # We recommend installing 'forever' instead for node apps. For non-node apps this code 57 | # may be helpful 58 | # 59 | # node app.js >> data/console.log 2>&1 & 60 | # PID=$! 61 | # echo $PID > data/pid 62 | # 63 | #echo "Site started" 64 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_locales.scss: -------------------------------------------------------------------------------- 1 | .locales { 2 | position: relative; 3 | } 4 | 5 | .locales__toggler__active-label { 6 | position: relative; 7 | top: 2px; 8 | } 9 | 10 | .locales__toggler__flag { 11 | position: relative; 12 | display: inline-flex; 13 | align-content: center; 14 | justify-content: center; 15 | top: 1px; 16 | border-radius: 50%; 17 | background-size: 160%; 18 | overflow: hidden; 19 | width: 24px; 20 | height: 24px; 21 | background-repeat: no-repeat; 22 | background-position: center; 23 | box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; 24 | } 25 | 26 | .locales__toggler__chevron { 27 | position: relative; 28 | top: 3px; 29 | width: 13px; 30 | height: auto; 31 | left: -2px; 32 | } 33 | 34 | .locales__toggler__text { 35 | display: flex; 36 | align-items: center; 37 | gap: 0.45rem; 38 | font-size: 0.875rem; 39 | 40 | &:hover { 41 | color: var(--nav-hover-color); 42 | } 43 | } 44 | 45 | .locales__toggler { 46 | all: unset; 47 | cursor: pointer; 48 | 49 | &[aria-expanded="true"] { 50 | border-radius: 10px 10px 0px; 51 | } 52 | } 53 | 54 | .locales__list { 55 | z-index: 2; 56 | position: absolute; 57 | flex-direction: column; 58 | gap: 0.5rem; 59 | top: 100%; 60 | background: var(--nav-dropdown-background-color); 61 | transition: 0.3s; 62 | border-radius: 4px; 63 | box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.1); 64 | list-style: none; 65 | padding: 1rem 1.75rem; 66 | white-space: nowrap; 67 | font-size: 0.875rem; 68 | } 69 | 70 | [aria-expanded="true"] + .locales__list { 71 | display: flex; 72 | } 73 | 74 | .locales__item { 75 | margin-bottom: 5px; 76 | 77 | a { 78 | display: flex; 79 | align-items: center; 80 | gap: 0.5rem; 81 | } 82 | a, 83 | a:visited { 84 | text-decoration: none; 85 | } 86 | 87 | a:active, 88 | a:hover { 89 | text-decoration: underline; 90 | } 91 | } 92 | 93 | .locales__toggler__check { 94 | position: relative; 95 | z-index: 2; 96 | width: 18px; 97 | } 98 | 99 | .current .locales__toggler__flag::after { 100 | content: ''; 101 | z-index: 1; 102 | position: absolute; 103 | top: 0; 104 | left: 0; 105 | width: 100%; 106 | height: 100%; 107 | background-color: var(--contrast-color); 108 | opacity: 0.75; 109 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/video-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | video 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/area.js: -------------------------------------------------------------------------------- 1 | const defaultRtConfig = { 2 | toolbar: [ 3 | 'styles', 4 | 'bold', 5 | 'italic', 6 | 'strike', 7 | 'link', 8 | 'bulletList', 9 | 'orderedList', 10 | 'blockquote', 11 | 'alignLeft', 12 | 'alignCenter', 13 | 'alignRight', 14 | 'codeBlock', 15 | 'undo', 16 | 'redo' 17 | ], 18 | insert: [ 'table', 'image' ], 19 | styles: [ 20 | // you may also use a `class` property with these 21 | { 22 | tag: 'p', 23 | label: 'project:rtParagraph' 24 | }, 25 | { 26 | class: 'large', 27 | tag: 'p', 28 | label: 'project:rtParagraphLarge' 29 | }, 30 | { 31 | tag: 'h2', 32 | label: 'project:rtH2' 33 | }, 34 | { 35 | tag: 'h3', 36 | label: 'project:rtH3' 37 | }, 38 | { 39 | tag: 'h4', 40 | label: 'project:rtH4' 41 | }, 42 | { 43 | tag: 'h5', 44 | label: 'project:rtH5' 45 | }, 46 | { 47 | tag: 'h6', 48 | label: 'project:rtMetaH6', 49 | class: 'meta' 50 | }, 51 | { 52 | tag: 'span', 53 | class: 'highlight-red', 54 | label: 'project:highlightRed' 55 | }, 56 | { 57 | tag: 'span', 58 | class: 'highlight-seafoam', 59 | label: 'project:rtHighlightSeafoam' 60 | }, 61 | { 62 | tag: 'span', 63 | class: 'highlight-blue', 64 | label: 'project:rtHighlightBlue' 65 | }, 66 | { 67 | tag: 'span', 68 | class: 'highlight-mustard', 69 | label: 'project:rtHighlightMustard' 70 | }, 71 | { 72 | tag: 'span', 73 | class: 'highlight-purple', 74 | label: 'project:rtHighlightPurple' 75 | } 76 | ] 77 | }; 78 | 79 | const basicConfig = { 80 | '@apostrophecms/image': {}, 81 | '@apostrophecms/video': {}, 82 | '@apostrophecms/rich-text': defaultRtConfig, 83 | '@apostrophecms/layout': {}, 84 | button: {}, 85 | 'github-prs': {} 86 | }; 87 | 88 | const fullConfig = Object.assign({ 89 | '@apostrophecms/layout': {}, 90 | article: {}, 91 | card: {}, 92 | 'price-card': {} 93 | }, basicConfig); 94 | 95 | const fullConfigExpandedGroups = { 96 | layout: { 97 | label: 'project:layoutTools', 98 | widgets: { 99 | '@apostrophecms/layout': {} 100 | }, 101 | columns: 1 102 | }, 103 | media: { 104 | label: 'project:media', 105 | widgets: { 106 | '@apostrophecms/image': {}, 107 | '@apostrophecms/video': {} 108 | }, 109 | columns: 2 110 | }, 111 | elements: { 112 | label: 'project:elements', 113 | widgets: { 114 | hero: {}, 115 | button: {}, 116 | 'github-prs': {}, 117 | '@apostrophecms/rich-text': defaultRtConfig, 118 | article: {}, 119 | 'price-card': {} 120 | }, 121 | columns: 3 122 | } 123 | }; 124 | 125 | export { 126 | basicConfig, 127 | fullConfig, 128 | fullConfigExpandedGroups 129 | }; 130 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_misc.scss: -------------------------------------------------------------------------------- 1 | .chip { 2 | font-size: 0.75rem; 3 | font-weight: 600; 4 | text-transform: uppercase; 5 | letter-spacing: 0.5px; 6 | color: var(--accent-color); 7 | background: linear-gradient(135deg, color-mix(in srgb, var(--accent-color), transparent 90%), color-mix(in srgb, var(--accent-color), transparent 85%)); 8 | padding: 6px 12px; 9 | border-radius: 20px; 10 | text-decoration: none; 11 | } 12 | 13 | a.chip:hover { 14 | filter: brightness(4); 15 | } 16 | 17 | .mode-switch { 18 | position: fixed; 19 | bottom: 20px; 20 | left: 20px; 21 | transform: scale(0.2); 22 | transform-origin: bottom left; 23 | 24 | .toggle-checkbox { 25 | position: absolute; 26 | opacity: 0; 27 | cursor: pointer; 28 | height: 0; 29 | width: 0; 30 | } 31 | 32 | .toggle-checkbox:checked ~ .toggle-slot { 33 | background-color: #374151; 34 | } 35 | .toggle-slot { 36 | position: relative; 37 | height: 10em; 38 | width: 20em; 39 | border: 5px solid #e4e7ec; 40 | border-radius: 10em; 41 | background-color: white; 42 | box-shadow: 0px 10px 25px #e4e7ec; 43 | transition: background-color 250ms; 44 | } 45 | 46 | .toggle-checkbox:checked ~ .toggle-slot .sun-icon-wrapper { 47 | opacity: 0; 48 | transform: translate(3em, 2em) rotate(0deg); 49 | } 50 | .sun-icon-wrapper { 51 | position: absolute; 52 | height: 6em; 53 | width: 6em; 54 | opacity: 1; 55 | transform: translate(2em, 2em) rotate(15deg); 56 | transform-origin: 50% 50%; 57 | transition: opacity 150ms, transform 500ms cubic-bezier(.26,2,.46,.71); 58 | } 59 | 60 | .sun-icon { 61 | position: absolute; 62 | height: 6em; 63 | width: 6em; 64 | color: #ffbb52; 65 | } 66 | 67 | .toggle-checkbox:checked ~ .toggle-slot .toggle-button { 68 | background-color: #485367; 69 | box-shadow: inset 0px 0px 0px 0.75em white; 70 | transform: translate(1.75em, 1.75em); 71 | } 72 | .toggle-button { 73 | transform: translate(11.75em, 1.75em); 74 | position: absolute; 75 | height: 6.5em; 76 | width: 6.5em; 77 | border-radius: 50%; 78 | background-color: #ffeccf; 79 | box-shadow: inset 0px 0px 0px 0.75em #ffbb52; 80 | transition: background-color 250ms, border-color 250ms, transform 500ms cubic-bezier(.26,2,.46,.71); 81 | } 82 | 83 | .toggle-checkbox:checked ~ .toggle-slot .moon-icon-wrapper { 84 | opacity: 1; 85 | transform: translate(12em, 2em) rotate(-15deg); 86 | } 87 | .moon-icon-wrapper { 88 | position: absolute; 89 | height: 6em; 90 | width: 6em; 91 | opacity: 0; 92 | transform: translate(11em, 2em) rotate(0deg); 93 | transform-origin: 50% 50%; 94 | transition: opacity 150ms, transform 500ms cubic-bezier(.26,2.5,.46,.71); 95 | } 96 | 97 | .moon-icon { 98 | position: absolute; 99 | height: 6em; 100 | width: 6em; 101 | color: white; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /modules/article-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Articles 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /modules/article/index.js: -------------------------------------------------------------------------------- 1 | import { fullConfig } from '../../lib/area.js'; 2 | 3 | export default { 4 | extend: '@apostrophecms/piece-type', 5 | options: { 6 | label: 'project:article', 7 | pluralLabel: 'project:articles', 8 | sort: { 9 | publishedDate: -1, 10 | createdAt: -1 11 | } 12 | }, 13 | fields: { 14 | add: { 15 | blurb: { 16 | type: 'area', 17 | label: 'project:articleBlurb', 18 | help: 'project:articleBlurbHelp', 19 | options: { 20 | max: 1, 21 | widgets: { 22 | '@apostrophecms/rich-text': { 23 | toolbar: [ 'bold', 'italic' ] 24 | } 25 | } 26 | } 27 | }, 28 | publishedDate: { 29 | label: 'project:articlePublishedDate', 30 | type: 'date' 31 | }, 32 | _categories: { 33 | label: 'project:articleCategories', 34 | type: 'relationship', 35 | withType: 'article-category' 36 | }, 37 | _author: { 38 | label: 'project:articleAuthor', 39 | type: 'relationship', 40 | withType: '@apostrophecms/user', 41 | max: 1 42 | }, 43 | _image: { 44 | label: 'project:articleFeaturedImage', 45 | type: 'relationship', 46 | withType: '@apostrophecms/image', 47 | aspectRatio: [ 2, 1 ] 48 | }, 49 | main: { 50 | label: 'project:content', 51 | type: 'area', 52 | options: { 53 | widgets: fullConfig 54 | } 55 | } 56 | }, 57 | group: { 58 | basics: { 59 | label: 'project:basics', 60 | fields: [ 61 | 'title', 62 | 'blurb', 63 | 'publishedDate' 64 | ] 65 | }, 66 | main: { 67 | label: 'project:content', 68 | fields: [ 69 | '_image', 70 | 'main' 71 | ] 72 | }, 73 | utility: { 74 | fields: [ 75 | 'slug', 76 | 'visibility', 77 | '_author', 78 | '_categories' 79 | ] 80 | } 81 | } 82 | }, 83 | columns: { 84 | add: { 85 | _categories: { 86 | label: 'project:articleCategoriesColumn', 87 | component: 'DemoCellRelation' 88 | }, 89 | _author: { 90 | label: 'project:articleAuthorColumn', 91 | component: 'DemoCellRelation' 92 | }, 93 | _image: { 94 | label: 'project:articleFeaturedImageColumn', 95 | component: 'DemoCellImage' 96 | } 97 | } 98 | }, 99 | components(self) { 100 | return { 101 | async recent(req, data) { 102 | return { 103 | articles: await self.find(req).limit(data.limit).sort({ createdAt: -1 }).toArray(), 104 | display: data.display 105 | }; 106 | } 107 | }; 108 | }, 109 | init(self) { 110 | // blurb used to be a string; now we've decided it should be 111 | // a rich text widget. Make the conversion with a migration 112 | self.apos.migration.add('blurb', async () => { 113 | await self.apos.migration.eachDoc({ 114 | type: 'article' 115 | }, 5, async (doc) => { 116 | if ((typeof doc.blurb) === 'string') { 117 | doc.blurb = self.apos.area.fromPlaintext(doc.blurb); 118 | return self.apos.doc.db.updateOne({ 119 | _id: doc._id 120 | }, { 121 | $set: { 122 | blurb: doc.blurb 123 | } 124 | }); 125 | } 126 | }); 127 | }); 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /modules/card-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bitmap 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Editor Experience 22 | 23 | 24 | Learn about Pages, 25 | 26 | 27 | Widgets, Pieces and 28 | 29 | 30 | other common building 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_article.scss: -------------------------------------------------------------------------------- 1 | [data-apos-area] .article-widget [data-apos-area] { 2 | padding: 0; 3 | } 4 | 5 | .article-excerpts--display-vertical { 6 | flex-direction: column; 7 | } 8 | 9 | .article-excerpts--display-horizontal { 10 | display: flex; 11 | gap: 2%; 12 | justify-content: center; 13 | 14 | @media only screen and (max-width: 800px) { 15 | flex-direction: column; 16 | } 17 | 18 | .article-excerpt { 19 | flex: 1; 20 | } 21 | } 22 | 23 | .article-excerpts--display-grid { 24 | display: grid; 25 | grid-template-columns: 1fr 1fr 1fr; 26 | grid-template-rows: 1fr; 27 | gap: 8px 10px; 28 | grid-template-areas: 29 | ". . . ."; 30 | 31 | @media only screen and (max-width: 800px) { 32 | grid-template-columns: 1fr; 33 | grid-template-areas: "."; 34 | } 35 | } 36 | 37 | .article-author { 38 | margin-bottom: 0.5rem; 39 | a { 40 | color: var(--link-color); 41 | } 42 | } 43 | 44 | .article-detail { 45 | font-size: 0.875rem; 46 | opacity: 0.75; 47 | } 48 | 49 | .article-details { 50 | display: flex; 51 | gap: 1.5rem; 52 | margin-top: 1rem; 53 | align-items: center; 54 | justify-content: center; 55 | 56 | .article-detail { 57 | margin-bottom: 0; 58 | font-size: 1rem; 59 | opacity: 1; 60 | } 61 | } 62 | 63 | .article-excerpt-content { 64 | padding: 1rem; 65 | 66 | h3 { 67 | font-size: 1.65rem; 68 | line-height: 1.1; 69 | margin: 0.75rem 0 1rem; 70 | } 71 | 72 | .article-topics { 73 | position: relative; 74 | left: -2px; 75 | } 76 | } 77 | 78 | .article-excerpt-image-container { 79 | display: block; 80 | height: 260px; 81 | } 82 | 83 | .article-excerpt { 84 | background-color: var(--surface-color); 85 | border-radius: 20px; 86 | overflow: hidden; 87 | box-shadow: 0 4px 20px color-mix(in srgb, var(--default-color), transparent 92%); 88 | margin-bottom: 30px; 89 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); 90 | border: 1px solid color-mix(in srgb, var(--default-color), transparent 92%); 91 | &:hover { 92 | transform: translateY(-8px); 93 | box-shadow: 0 20px 40px color-mix(in srgb, var(--default-color), transparent 85%); 94 | border-color: color-mix(in srgb, var(--accent-color), transparent 80%); 95 | } 96 | 97 | .demo-rte { 98 | margin-bottom: 0; 99 | } 100 | } 101 | 102 | .article-excerpt-image { 103 | max-width: 100%; 104 | object-fit: cover; 105 | height: 100%; 106 | width: 100%; 107 | } 108 | 109 | .article-title-wrapper { 110 | margin-bottom: 50px; 111 | } 112 | 113 | .article-topics { 114 | display: flex; 115 | margin: 1rem 0; 116 | gap: 5px; 117 | } 118 | 119 | .article-topics--show { 120 | justify-content: center; 121 | } 122 | 123 | .article-excerpt h3 a { 124 | text-decoration: none; 125 | &:hover { 126 | color: var(--accent-color); 127 | } 128 | } 129 | 130 | .article-topic-filters { 131 | list-style: none; 132 | display: flex; 133 | justify-content: center; 134 | gap: 1rem; 135 | margin: 1rem 0 0; 136 | padding: 0; 137 | 138 | a { 139 | text-decoration: none; 140 | 141 | &.active { 142 | color: var(--nav-hover-color); 143 | } 144 | &:hover { 145 | text-decoration: underline; 146 | color: var(--nav-hover-color); 147 | } 148 | } 149 | } 150 | 151 | .article-show { 152 | width: 100%; 153 | max-width: 900px; 154 | margin: 0 auto 50px; 155 | 156 | h1 { 157 | font-size: 5em; 158 | line-height: 1.3; 159 | margin-top: 30px; 160 | } 161 | 162 | .meta { 163 | font-size: 14px; 164 | font-weight: normal; 165 | letter-spacing: 1px; 166 | margin-bottom: 10px; 167 | } 168 | } 169 | 170 | @media only screen and (max-width: 600px) { 171 | .article-show { 172 | h1 { 173 | font-size: 3.6em; 174 | } 175 | } 176 | } 177 | 178 | .article-intro { 179 | margin-bottom: 40px; 180 | } 181 | 182 | .article-show__image { 183 | max-width: 100%; 184 | border-radius: 1rem; 185 | margin-bottom: 1.5rem; 186 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/rich-text-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rich Text 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /modules/@apostrophecms/asset/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | options: { 3 | breakpointPreviewMode: { 4 | enable: true, 5 | debug: true, 6 | resizable: false, 7 | screens: { 8 | responsive: { 9 | label: 'project:breakpointResponsive', 10 | width: '95vw', 11 | height: '800px', 12 | icon: 'monitor-icon', 13 | shortcut: true 14 | }, 15 | nestHub: { 16 | label: 'project:breakpointNestHub', 17 | width: '1024px', 18 | height: '600px', 19 | icon: 'monitor-icon', 20 | shortcut: false 21 | }, 22 | nestHubMax: { 23 | label: 'project:breakpointNestHubMax', 24 | width: '1280px', 25 | height: '800px', 26 | icon: 'monitor-icon', 27 | shortcut: false 28 | }, 29 | ipadMini: { 30 | label: 'project:breakpointIpadMini', 31 | width: '768px', 32 | height: '1024px', 33 | icon: 'tablet-icon', 34 | shortcut: true 35 | }, 36 | ipadAir: { 37 | label: 'project:breakpointIpadAir', 38 | width: '820px', 39 | height: '1180px', 40 | icon: 'tablet-icon', 41 | shortcut: false 42 | }, 43 | ipadPro: { 44 | label: 'project:breakpointIpadPro', 45 | width: '1024px', 46 | height: '1366px', 47 | icon: 'tablet-icon', 48 | shortcut: false 49 | }, 50 | surfacePro7: { 51 | label: 'project:breakpointSurfacePro7', 52 | width: '912px', 53 | height: '1368px', 54 | icon: 'tablet-icon', 55 | shortcut: false 56 | }, 57 | iphoneSE: { 58 | label: 'project:breakpointIphoneSE', 59 | width: '375px', 60 | height: '667px', 61 | icon: 'cellphone-icon', 62 | shortcut: true 63 | }, 64 | iphoneXR: { 65 | label: 'project:breakpointIphoneXR', 66 | width: '414px', 67 | height: '896px', 68 | icon: 'cellphone-icon', 69 | shortcut: false 70 | }, 71 | iphone12Pro: { 72 | label: 'project:breakpointIphone12Pro', 73 | width: '390px', 74 | height: '844px', 75 | icon: 'cellphone-icon', 76 | shortcut: false 77 | }, 78 | iphone14ProMax: { 79 | label: 'project:breakpointIphone14ProMax', 80 | width: '430px', 81 | height: '932px', 82 | icon: 'cellphone-icon', 83 | shortcut: false 84 | }, 85 | pixel7: { 86 | label: 'project:breakpointPixel7', 87 | width: '412px', 88 | height: '915px', 89 | icon: 'cellphone-icon', 90 | shortcut: false 91 | }, 92 | samsungGalaxyS8Plus: { 93 | label: 'project:breakpointSamsungGalaxyS8Plus', 94 | width: '360px', 95 | height: '740px', 96 | icon: 'cellphone-icon', 97 | shortcut: false 98 | }, 99 | samsungGalaxyS20Ultra: { 100 | label: 'project:breakpointSamsungGalaxyS20Ultra', 101 | width: '412px', 102 | height: '915px', 103 | icon: 'cellphone-icon', 104 | shortcut: false 105 | }, 106 | surfaceDuo: { 107 | label: 'project:breakpointSurfaceDuo', 108 | width: '540px', 109 | height: '720px', 110 | icon: 'cellphone-icon', 111 | shortcut: false 112 | }, 113 | galaxyZFold5Front: { 114 | label: 'project:breakpointGalaxyZFold5Front', 115 | width: '373px', 116 | height: '818px', 117 | icon: 'cellphone-icon', 118 | shortcut: false 119 | }, 120 | galaxyZFold5Inner: { 121 | label: 'project:breakpointGalaxyZFold5Inner', 122 | width: '768px', 123 | height: '1812px', 124 | icon: 'tablet-icon', 125 | shortcut: false 126 | }, 127 | samsungGalaxyA51A71: { 128 | label: 'project:breakpointSamsungGalaxyA51A71', 129 | width: '412px', 130 | height: '915px', 131 | icon: 'cellphone-icon', 132 | shortcut: false 133 | } 134 | } 135 | }, 136 | // Transform method used on media feature 137 | // Can be either: 138 | // - (mediaFeature) => { return mediaFeature; } 139 | // - null 140 | transform: null 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_nav.scss: -------------------------------------------------------------------------------- 1 | .nav-bar { 2 | font-family: var(--nav-font); 3 | z-index: 5; 4 | position: fixed; 5 | box-sizing: border-box; 6 | left: auto; 7 | right: auto; 8 | width: max-content; 9 | background: var(--surface-color); 10 | border-radius: 50px; 11 | padding: 10px 25px; 12 | box-shadow: rgba(0, 0, 0, 0.15) 0px 5px 15px 0px; 13 | align-items: center; 14 | justify-content: space-between; 15 | display: flex; 16 | max-width: 1140px; 17 | } 18 | 19 | .nav-bar h2 { 20 | margin: 0; 21 | padding: 0; 22 | 23 | a { 24 | text-decoration: none; 25 | color: var(--nav-color); 26 | 27 | &:hover { 28 | color: var(--nav-hover-color); 29 | } 30 | } 31 | } 32 | 33 | .nav-bar__end { 34 | display: flex; 35 | align-items: center; 36 | gap: 1rem; 37 | } 38 | 39 | .nav__mobile-button { 40 | all: unset; 41 | display: none; 42 | gap: 0.5rem; 43 | align-items: center; 44 | } 45 | 46 | .mobile-nav { 47 | z-index: 9999; 48 | position: fixed; 49 | display: flex; 50 | flex-direction: column; 51 | align-items: center; 52 | justify-content: center; 53 | gap: 2rem; 54 | top: 0; 55 | left: 0; 56 | width: 100%; 57 | height: 100%; 58 | box-sizing: border-box; 59 | padding: 2rem; 60 | background-color: var(--mobile-nav-bg); 61 | opacity: 0; 62 | visibility: hidden; 63 | pointer-events: none; 64 | transition: opacity 0.3s ease; 65 | 66 | &.active { 67 | opacity: 1; 68 | visibility: visible; 69 | pointer-events: auto; 70 | } 71 | 72 | .locales__item.current { 73 | outline: 1px solid var(--surface-color); 74 | padding: 0.5rem 0.75rem 0.65rem; 75 | border-radius: 10px; 76 | 77 | svg { 78 | display: none; 79 | } 80 | } 81 | 82 | .mobile-nav__close-trigger { 83 | all: unset; 84 | position: absolute; 85 | top: 1.5rem; 86 | right: 1.5rem; 87 | display: flex; 88 | gap: 0.25rem; 89 | color: var(--contrast-color); 90 | align-items: center; 91 | 92 | &:focus, &:active { 93 | outline: 1px solid var(--contrast-color); 94 | } 95 | } 96 | 97 | .locales__toggler__flag:after { 98 | display: none; 99 | } 100 | 101 | .locales__item a { 102 | color: var(--contrast-color); 103 | } 104 | 105 | .locales__toggler { 106 | display: none; 107 | } 108 | 109 | .locales__list { 110 | display: block; 111 | background: transparent; 112 | position: relative; 113 | box-shadow: none; 114 | display: flex; 115 | flex-direction: row; 116 | flex-wrap: wrap; 117 | gap: 2rem; 118 | color: white; 119 | align-items: center; 120 | justify-content: center; 121 | } 122 | } 123 | 124 | .mobile-nav__nav { 125 | display: flex; 126 | flex-direction: column; 127 | align-items: center; 128 | justify-content: center; 129 | } 130 | 131 | .mobile-nav__nav ul { 132 | display: flex; 133 | flex-direction: column; 134 | gap: 0.25rem; 135 | margin: 0; 136 | padding: 0; 137 | list-style: none; 138 | text-align: center; 139 | transform: opacity 0.4s ease; 140 | 141 | &:hover a { 142 | opacity: 0.5; 143 | } 144 | 145 | a { 146 | text-decoration: none; 147 | font-size: 3rem; 148 | color: var(--surface-color); 149 | transform: opacity 0.8s ease; 150 | 151 | &:hover { 152 | opacity: 1; 153 | } 154 | } 155 | } 156 | 157 | @media only screen and (max-width: 800px) { 158 | 159 | .nav-bar { 160 | width: 90vw; 161 | left: 5vw; 162 | } 163 | 164 | .nav__mobile-button { 165 | display: flex; 166 | } 167 | 168 | .nav, .nav-bar__end { 169 | display: none; 170 | } 171 | } 172 | 173 | .nav ul { 174 | margin: 0 40px; 175 | padding: 0; 176 | display: flex; 177 | list-style: none; 178 | align-items: center; 179 | } 180 | 181 | .nav li { 182 | position: relative; 183 | 184 | &:hover { 185 | a, .active, .active:focus { 186 | color: var(--nav-hover-color); 187 | } 188 | } 189 | } 190 | 191 | .nav a { 192 | color: var(--nav-color); 193 | padding: 18px 15px; 194 | font-size: 0.875rem; 195 | font-family: var(--nav-font); 196 | font-weight: 400; 197 | display: flex; 198 | align-items: center; 199 | justify-content: space-between; 200 | white-space: nowrap; 201 | transition: 0.3s; 202 | text-decoration: none; 203 | line-height: 0; 204 | 205 | &.active { 206 | color: var(--nav-hover-color); 207 | } 208 | } 209 | 210 | .nav-bar .callout-button { 211 | color: var(--contrast-color); 212 | background: color-mix(in srgb, var(--accent-color), transparent 15%); 213 | font-size: 14px; 214 | padding: 8px 20px; 215 | border-radius: 50px; 216 | transition: 0.3s; 217 | text-decoration: none; 218 | 219 | &:hover { 220 | text-decoration: none; 221 | } 222 | } -------------------------------------------------------------------------------- /modules/@apostrophecms/layout-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | layout 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /modules/asset/ui/src/_rich-text.scss: -------------------------------------------------------------------------------- 1 | .demo-rte { 2 | & > *:first-child, 3 | & > [contenteditable] > *:first-child { 4 | margin-top: 0; 5 | } 6 | 7 | & > *:last-child, 8 | & > [contenteditable] > *:last-child { 9 | margin-bottom: 0; 10 | } 11 | 12 | a, 13 | a:visited { 14 | color: var(--link-color); 15 | text-decoration: none; 16 | } 17 | 18 | a:active, 19 | a:hover { 20 | text-decoration: underline; 21 | } 22 | 23 | li p { 24 | padding: 0; 25 | margin: 0 0 5px; 26 | } 27 | 28 | p { 29 | margin-top: 0; 30 | -webkit-font-smoothing: antialiased; 31 | text-size-adjust: none; 32 | font-weight: 400; 33 | line-height: 1.6; 34 | transition: all 250ms ease 0s; 35 | font-size: 1rem; 36 | 37 | &.large { 38 | font-size: 1.3rem; 39 | line-height: 1.7; 40 | } 41 | } 42 | 43 | h2, 44 | h3, 45 | h4, 46 | h5 { 47 | font-family: var(--heading-font); 48 | margin-top: 0; 49 | margin-bottom: 2rem; 50 | line-height: 1.2em; 51 | } 52 | 53 | h3 { 54 | font-size: 2.8em; 55 | letter-spacing: -1px; 56 | line-height: 1em; 57 | } 58 | 59 | h4 { 60 | font-size: 1.8em; 61 | line-height: 1.2em; 62 | font-weight: 600; 63 | } 64 | 65 | h5 { 66 | font-size: 1.3em; 67 | line-height: 1.2em; 68 | font-weight: 500; 69 | } 70 | 71 | margin-bottom: var(--widget-spacer); 72 | } 73 | 74 | .apos-layout__item .demo-rte { 75 | h2, 76 | h3, 77 | h4, 78 | h5 { 79 | margin-bottom: 1rem; 80 | } 81 | } 82 | 83 | .container-widget .demo-rte { 84 | margin-bottom: 0; 85 | } 86 | 87 | .demo-rte h2, 88 | .h2 .demo-rte p { 89 | margin-bottom: 1rem; 90 | font-size: 80px; 91 | font-weight: 700; 92 | line-height: 1.1; 93 | letter-spacing: -3px; 94 | @container (max-width: 800px) { 95 | font-size: 50px; 96 | letter-spacing: -1px; 97 | } 98 | @container (max-width: 600px) { 99 | font-size: 40px; 100 | } 101 | @media (max-width: 800px) { 102 | font-size: 50px; 103 | letter-spacing: -1px; 104 | } 105 | @media (max-width: 600px) { 106 | font-size: 40px; 107 | } 108 | } 109 | 110 | .home-tite { 111 | font-size: 14px; 112 | } 113 | 114 | .home-cta.h2 p { 115 | text-align: center; 116 | } 117 | 118 | .highlight-red { 119 | color: var(--brand-red); 120 | } 121 | .highlight-mustard { 122 | color: var(--brand-mustard); 123 | } 124 | .highlight-purple { 125 | color: var(--brand-purple); 126 | } 127 | .highlight-blue { 128 | color: var(--brand-blue); 129 | } 130 | .highlight-seafoam { 131 | color: var(--brand-seafoam); 132 | } 133 | 134 | .type-mono { 135 | font-family: "Fira Code", monospace; 136 | } 137 | 138 | .type-wide-tracking { 139 | letter-spacing: 8px !important; 140 | } 141 | 142 | .type-red-underline { 143 | text-decoration: underline; 144 | text-decoration-color: var(--brand-red); 145 | } 146 | 147 | .type-seafoam-underline { 148 | text-decoration: underline; 149 | text-decoration-color: var(--brand-seafoam); 150 | } 151 | 152 | .type-rainbow-hover { 153 | background-image: linear-gradient( 154 | to right, 155 | var(--brand-red), 156 | var(--brand-mustard), 157 | var(--brand-purple), 158 | var(--brand-blue), 159 | var(--brand-seafoam) 160 | ); 161 | background-clip: text; 162 | -webkit-background-clip: text; 163 | color: transparent !important; 164 | transition: background-position 0.5s; 165 | background-size: 200% auto; 166 | background-position: 100%; 167 | background-repeat: repeat; 168 | &:hover { 169 | background-position: 0; 170 | } 171 | } 172 | 173 | // image alignment stuff 174 | .image-float-left, 175 | .image-float-right, 176 | .image-center, 177 | .image-full { 178 | img { 179 | width: 100%; 180 | } 181 | } 182 | 183 | .image-float-left, 184 | .image-float-right, 185 | .image-center { 186 | width: 50%; 187 | } 188 | 189 | .image-float-left { 190 | float: left; 191 | margin: 0 1em 1em 0; 192 | } 193 | 194 | .image-float-right { 195 | float: right; 196 | margin: 0 0 1em 1em; 197 | } 198 | 199 | .image-center { 200 | margin: auto; 201 | } 202 | 203 | .image-full { 204 | max-width: 100%; 205 | } 206 | 207 | // Table overrides 208 | 209 | .layout [data-rich-text] table:not([class]) td, 210 | .layout [data-rich-text] table:not([class]) th, 211 | .layout .tiptap table:not([class]) td, 212 | .layout .tiptap table:not([class]) th, 213 | .layout .tableWrapper table td, 214 | .layout .tableWrapper table th, 215 | .layout .apos-rich-text-table td, 216 | .layout .apos-rich-text-table th { 217 | border: 1px solid var(--faint-color); 218 | } 219 | 220 | .layout [data-rich-text] table:not([class]) th, 221 | .layout .tiptap table:not([class]) th, 222 | .layout .tableWrapper table th, 223 | .layout .apos-rich-text-table th { 224 | padding: 1rem 0; 225 | background-color: var(--faint-color); 226 | vertical-align: middle; 227 | } 228 | 229 | .layout th { 230 | 231 | h1, h2, h3, h4, h5, h6 { 232 | margin-bottom: 0; 233 | margin-top: 0; 234 | } 235 | } 236 | 237 | .ProseMirror > * + * { 238 | margin-top: 0 !important; 239 | } 240 | -------------------------------------------------------------------------------- /public/images/apos-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/@apostrophecms/i18n/i18n/project/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Account", 3 | "addBadge": "Add a badge to the card?", 4 | "apostropheCmsSite": "ApostropheCMS Site", 5 | "article": "Article", 6 | "articleAuthor": "Author", 7 | "articleAuthorColumn": "Author", 8 | "articleBlurb": "Blurb", 9 | "articleBlurbHelp": "A short summary", 10 | "articleCategories": "Article Categories", 11 | "articleCategoriesColumn": "Categories", 12 | "articleCategory": "Article Category", 13 | "articleFeaturedImage": "Featured Image", 14 | "articleFeaturedImageColumn": "Featured Image", 15 | "articleIndexPage": "Article Index Page", 16 | "articleIndexPages": "Article Index Pages", 17 | "articleIntro": "Intro", 18 | "articleOrientation": "Display orientation", 19 | "articlePublishedDate": "Published Date", 20 | "articleRecentArticles": "Recent Articles", 21 | "articleWidgetDescription": "Display a list of recent or curated articles", 22 | "articles": "Articles", 23 | "badgeLabel": "Badge Label", 24 | "basics": "Basics", 25 | "breakpointGalaxyZFold5Front": "Galaxy Z Fold 5 (Front)", 26 | "breakpointGalaxyZFold5Inner": "Galaxy Z Fold 5 (Inner)", 27 | "breakpointIpadAir": "iPad Air", 28 | "breakpointIpadMini": "iPad Mini", 29 | "breakpointIpadPro": "iPad Pro", 30 | "breakpointIphone12Pro": "iPhone 12 Pro", 31 | "breakpointIphone14ProMax": "iPhone 14 Pro Max", 32 | "breakpointIphoneSE": "iPhone SE", 33 | "breakpointIphoneXR": "iPhone XR", 34 | "breakpointNestHub": "Nest Hub", 35 | "breakpointNestHubMax": "Nest Hub Max", 36 | "breakpointPixel7": "Pixel 7", 37 | "breakpointResponsive": "Responsive", 38 | "breakpointSamsungGalaxyA51A71": "Samsung Galaxy A51/71", 39 | "breakpointSamsungGalaxyS20Ultra": "Samsung Galaxy S20 Ultra", 40 | "breakpointSamsungGalaxyS8Plus": "Samsung Galaxy S8+", 41 | "breakpointSurfaceDuo": "Surface Duo", 42 | "breakpointSurfacePro7": "Surface Pro 7", 43 | "button": "Button", 44 | "buttonAlignment": "Button Alignment", 45 | "buttonDescription": "Add a button that links to a page or URL", 46 | "buttonLinks": "Button links", 47 | "buttonLinksDescription": "Add button links to the bottom of the hero", 48 | "card": "Card", 49 | "cardAdd": "Add a card", 50 | "cardBackground": "Card has a background color?", 51 | "center": "Center", 52 | "close": "Closed", 53 | "content": "Content", 54 | "customUrl": "Custom URL", 55 | "defaultPage": "Default Page", 56 | "defaultPages": "Default Pages", 57 | "elements": "Elements", 58 | "feature": "Feature", 59 | "file": "File", 60 | "fileToLink": "File to link", 61 | "formattedLike": "Formatted like {ORG_NAME}/{REPO_NAME}", 62 | "fullWidth": "Full Width", 63 | "general": "General", 64 | "ghPrDescription": "Display a list of pull requests from a GitHub repository", 65 | "ghPrLabel": "GitHub Pull Requests", 66 | "hero": "Hero", 67 | "heroDescription": "Add a main hero to your page", 68 | "home": "Home", 69 | "homePage": "Home Page", 70 | "homePages": "Home Pages", 71 | "homeTitle": "Home: ApostropheCMS Demo", 72 | "horizontal": "Horizontal", 73 | "icon": "Icon", 74 | "iconBuilding": "Building", 75 | "iconChart": "Chart", 76 | "iconClock": "Clock", 77 | "iconCpu": "CPU", 78 | "iconEarth": "Earth", 79 | "iconEditor": "Editor", 80 | "iconLandmark": "Landmark", 81 | "iconPackage": "Package", 82 | "iconPeople": "People", 83 | "iconRocket": "Rocket", 84 | "iconTrophy": "Trophy", 85 | "image": "Image", 86 | "imageDescription": "Display images on your page", 87 | "layout": "Layout", 88 | "layoutDescription": "Create flexible column layouts", 89 | "layoutTools": "Layout Tools", 90 | "left": "Left", 91 | "limit": "Limit", 92 | "linkStyle": "Link Style", 93 | "linkText": "Link Text", 94 | "linkType": "Link Type", 95 | "listOfFeatures": "List of Features", 96 | "listOfFeaturesHelp": "A list of features this package includes", 97 | "main": "Main", 98 | "media": "Media", 99 | "needArticleIndex": "You will need to publish an Article Index page for article links to work.", 100 | "noArticles": "There are no articles yet. Try adding one.", 101 | "noSummary": "No summary for this article", 102 | "open": "Open", 103 | "openNewBrowserTab": "Open a new browser tab?", 104 | "openNewTab": "Open in new tab", 105 | "orientation": "Orientation", 106 | "outline": "Outline", 107 | "page": "Page", 108 | "pageToLink": "Page to link", 109 | "preferences": "Preferences", 110 | "priceDetail": "Additional price details", 111 | "priceText": "Price Text", 112 | "priceTextHelp": "Ex. $20, Custom, etc", 113 | "priceUnit": "Price Unit Interval", 114 | "priceUnitHelp": "Optional Ex. /mo, /year", 115 | "pricingCard": "Pricing Card", 116 | "pricingCardDescription": "A card that can display pricing and feature sets.", 117 | "primary": "Primary", 118 | "repo": "Repo", 119 | "richText": "Rich Text", 120 | "richTextDescription": "Add styled text to your page", 121 | "right": "Right", 122 | "rtH2": "Heading 2 (

)", 123 | "rtH3": "Heading 3 (

)", 124 | "rtH4": "Heading 4 (

)", 125 | "rtH5": "Heading 5 (

)", 126 | "rtHighlightBlue": "Blue Highlight", 127 | "rtHighlightMustard": "Mustard Highlight", 128 | "rtHighlightPurple": "Purple Highlight", 129 | "rtHighlightRed": "Red Highlight", 130 | "rtHighlightSeafoam": "Seafoam Highlight", 131 | "rtMetaH6": "Meta (
)", 132 | "rtParagraph": "Paragraph (

)", 133 | "rtParagraphLarge": "Large Paragraph (

)", 134 | "secondary": "Secondary", 135 | "siteTitle": "Site Title", 136 | "specialBackground": "A featured card has a special background", 137 | "state": "State", 138 | "style": "Style", 139 | "textContent": "Text Content", 140 | "textContentHelp": "The text content for the hero", 141 | "title": "Title", 142 | "urlForCustom": "URL for custom link", 143 | "vertical": "Vertical", 144 | "video": "Video", 145 | "videoDescription": "Add a video player from services like YouTube", 146 | "writtenBy": "Written by" 147 | } 148 | -------------------------------------------------------------------------------- /modules/price-card-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Price Cards 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 25 38 | 39 | 40 | /mo 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 99 67 | 68 | 69 | /mo 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /modules/github-prs-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | GH 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /modules/@apostrophecms/i18n/i18n/project/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Konto", 3 | "addBadge": "Badge zur Karte hinzufügen?", 4 | "apostropheCmsSite": "ApostropheCMS-Website", 5 | "article": "Artikel", 6 | "articleAuthor": "Autor", 7 | "articleAuthorColumn": "Autor", 8 | "articleBlurb": "Kurzbeschreibung", 9 | "articleBlurbHelp": "Eine kurze Zusammenfassung", 10 | "articleCategories": "Artikelkategorien", 11 | "articleCategoriesColumn": "Kategorien", 12 | "articleCategory": "Artikelkategorie", 13 | "articleFeaturedImage": "Beitragsbild", 14 | "articleFeaturedImageColumn": "Beitragsbild", 15 | "articleIndexPage": "Artikelindexseite", 16 | "articleIndexPages": "Artikelindexseiten", 17 | "articleIntro": "Einleitung", 18 | "articleOrientation": "Anzeigeausrichtung", 19 | "articlePublishedDate": "Veröffentlichungsdatum", 20 | "articleRecentArticles": "Neueste Artikel", 21 | "articleWidgetDescription": "Liste aktueller oder kuratierter Artikel anzeigen", 22 | "articles": "Artikel", 23 | "badgeLabel": "Badge-Beschriftung", 24 | "basics": "Grundlagen", 25 | "breakpointGalaxyZFold5Front": "Galaxy Z Fold 5 (Außendisplay)", 26 | "breakpointGalaxyZFold5Inner": "Galaxy Z Fold 5 (Innendisplay)", 27 | "breakpointIpadAir": "iPad Air", 28 | "breakpointIpadMini": "iPad Mini", 29 | "breakpointIpadPro": "iPad Pro", 30 | "breakpointIphone12Pro": "iPhone 12 Pro", 31 | "breakpointIphone14ProMax": "iPhone 14 Pro Max", 32 | "breakpointIphoneSE": "iPhone SE", 33 | "breakpointIphoneXR": "iPhone XR", 34 | "breakpointNestHub": "Nest Hub", 35 | "breakpointNestHubMax": "Nest Hub Max", 36 | "breakpointPixel7": "Pixel 7", 37 | "breakpointResponsive": "Responsiv", 38 | "breakpointSamsungGalaxyA51A71": "Samsung Galaxy A51/71", 39 | "breakpointSamsungGalaxyS20Ultra": "Samsung Galaxy S20 Ultra", 40 | "breakpointSamsungGalaxyS8Plus": "Samsung Galaxy S8+", 41 | "breakpointSurfaceDuo": "Surface Duo", 42 | "breakpointSurfacePro7": "Surface Pro 7", 43 | "button": "Schaltfläche", 44 | "buttonAlignment": "Ausrichtung der Schaltfläche", 45 | "buttonDescription": "Eine Schaltfläche hinzufügen, die zu einer Seite oder URL führt", 46 | "buttonLinks": "Schaltflächen-Links", 47 | "buttonLinksDescription": "Schaltflächen-Links am unteren Rand des Heros hinzufügen", 48 | "card": "Karte", 49 | "cardAdd": "Karte hinzufügen", 50 | "cardBackground": "Hat die Karte eine Hintergrundfarbe?", 51 | "center": "Zentriert", 52 | "close": "Geschlossen", 53 | "content": "Inhalt", 54 | "customUrl": "Benutzerdefinierte URL", 55 | "defaultPage": "Standardseite", 56 | "defaultPages": "Standardseiten", 57 | "elements": "Elemente", 58 | "feature": "Funktion", 59 | "file": "Datei", 60 | "fileToLink": "Zu verlinkende Datei", 61 | "formattedLike": "Formatiert wie {ORG_NAME}/{REPO_NAME}", 62 | "fullWidth": "Volle Breite", 63 | "general": "Allgemein", 64 | "ghPrDescription": "Liste von Pull Requests aus einem GitHub-Repository anzeigen", 65 | "ghPrLabel": "GitHub-Pull-Requests", 66 | "hero": "Hero", 67 | "heroDescription": "Einen zentralen Hero zur Seite hinzufügen", 68 | "home": "Startseite", 69 | "homePage": "Startseite", 70 | "homePages": "Startseiten", 71 | "homeTitle": "Startseite: ApostropheCMS-Demo", 72 | "horizontal": "Horizontal", 73 | "icon": "Symbol", 74 | "iconBuilding": "Gebäude", 75 | "iconChart": "Diagramm", 76 | "iconClock": "Uhr", 77 | "iconCpu": "CPU", 78 | "iconEarth": "Erde", 79 | "iconEditor": "Editor", 80 | "iconLandmark": "Wahrzeichen", 81 | "iconPackage": "Paket", 82 | "iconPeople": "Personen", 83 | "iconRocket": "Rakete", 84 | "iconTrophy": "Trophäe", 85 | "image": "Bild", 86 | "imageDescription": "Bilder auf Ihrer Seite anzeigen", 87 | "layout": "Layout", 88 | "layoutDescription": "Flexible Spaltenlayouts erstellen", 89 | "layoutTools": "Layout-Werkzeuge", 90 | "left": "Links", 91 | "limit": "Limit", 92 | "linkStyle": "Linkstil", 93 | "linkText": "Linktext", 94 | "linkType": "Linktyp", 95 | "listOfFeatures": "Funktionsliste", 96 | "listOfFeaturesHelp": "Liste der in diesem Paket enthaltenen Funktionen", 97 | "main": "Haupt", 98 | "media": "Medien", 99 | "needArticleIndex": "Sie müssen eine Artikelindexseite veröffentlichen, damit Artikel-Links funktionieren.", 100 | "noArticles": "Es gibt noch keine Artikel. Versuchen Sie, einen hinzuzufügen.", 101 | "noSummary": "Keine Zusammenfassung für diesen Artikel", 102 | "open": "Offen", 103 | "openNewBrowserTab": "In einem neuen Browser-Tab öffnen?", 104 | "openNewTab": "In neuem Tab öffnen", 105 | "orientation": "Ausrichtung", 106 | "outline": "Kontur", 107 | "page": "Seite", 108 | "pageToLink": "Zu verlinkende Seite", 109 | "preferences": "Einstellungen", 110 | "priceDetail": "Zusätzliche Preisinformationen", 111 | "priceText": "Preiste​​xt", 112 | "priceTextHelp": "Z. B. 20 €, Individuell usw.", 113 | "priceUnit": "Preisintervall", 114 | "priceUnitHelp": "Optional z. B. /Monat, /Jahr", 115 | "pricingCard": "Preiskarte", 116 | "pricingCardDescription": "Karte zur Anzeige von Preisen und Funktionspaketen.", 117 | "primary": "Primär", 118 | "repo": "Repository", 119 | "richText": "Rich-Text", 120 | "richTextDescription": "Formatierten Text zur Seite hinzufügen", 121 | "right": "Rechts", 122 | "rtH2": "Überschrift 2 (

)", 123 | "rtH3": "Überschrift 3 (

)", 124 | "rtH4": "Überschrift 4 (

)", 125 | "rtH5": "Überschrift 5 (

)", 126 | "rtHighlightBlue": "Blau hervorgehoben", 127 | "rtHighlightMustard": "Senfgelb hervorgehoben", 128 | "rtHighlightPurple": "Violett hervorgehoben", 129 | "rtHighlightRed": "Rot hervorgehoben", 130 | "rtHighlightSeafoam": "Türkis hervorgehoben", 131 | "rtMetaH6": "Meta (
)", 132 | "rtParagraph": "Absatz (

)", 133 | "rtParagraphLarge": "Großer Absatz (

)", 134 | "secondary": "Sekundär", 135 | "siteTitle": "Seitentitel", 136 | "specialBackground": "Eine hervorgehobene Karte hat einen speziellen Hintergrund", 137 | "state": "Status", 138 | "style": "Stil", 139 | "textContent": "Textinhalt", 140 | "textContentHelp": "Der Textinhalt für den Hero", 141 | "title": "Titel", 142 | "urlForCustom": "URL für benutzerdefinierten Link", 143 | "vertical": "Vertikal", 144 | "video": "Video", 145 | "videoDescription": "Einen Videoplayer von Diensten wie YouTube hinzufügen", 146 | "writtenBy": "Verfasst von" 147 | } 148 | -------------------------------------------------------------------------------- /modules/@apostrophecms/i18n/i18n/project/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Compte", 3 | "addBadge": "Ajouter un badge à la carte ?", 4 | "apostropheCmsSite": "Site ApostropheCMS", 5 | "article": "Article", 6 | "articleAuthor": "Auteur", 7 | "articleAuthorColumn": "Auteur", 8 | "articleBlurb": "Résumé", 9 | "articleBlurbHelp": "Un court résumé", 10 | "articleCategories": "Catégories d’articles", 11 | "articleCategoriesColumn": "Catégories", 12 | "articleCategory": "Catégorie d’article", 13 | "articleFeaturedImage": "Image mise en avant", 14 | "articleFeaturedImageColumn": "Image mise en avant", 15 | "articleIndexPage": "Page d’index des articles", 16 | "articleIndexPages": "Pages d’index des articles", 17 | "articleIntro": "Introduction", 18 | "articleOrientation": "Orientation d’affichage", 19 | "articlePublishedDate": "Date de publication", 20 | "articleRecentArticles": "Articles récents", 21 | "articleWidgetDescription": "Afficher une liste d’articles récents ou sélectionnés", 22 | "articles": "Articles", 23 | "badgeLabel": "Libellé du badge", 24 | "basics": "Bases", 25 | "breakpointGalaxyZFold5Front": "Galaxy Z Fold 5 (écran externe)", 26 | "breakpointGalaxyZFold5Inner": "Galaxy Z Fold 5 (écran interne)", 27 | "breakpointIpadAir": "iPad Air", 28 | "breakpointIpadMini": "iPad Mini", 29 | "breakpointIpadPro": "iPad Pro", 30 | "breakpointIphone12Pro": "iPhone 12 Pro", 31 | "breakpointIphone14ProMax": "iPhone 14 Pro Max", 32 | "breakpointIphoneSE": "iPhone SE", 33 | "breakpointIphoneXR": "iPhone XR", 34 | "breakpointNestHub": "Nest Hub", 35 | "breakpointNestHubMax": "Nest Hub Max", 36 | "breakpointPixel7": "Pixel 7", 37 | "breakpointResponsive": "Adaptatif", 38 | "breakpointSamsungGalaxyA51A71": "Samsung Galaxy A51/71", 39 | "breakpointSamsungGalaxyS20Ultra": "Samsung Galaxy S20 Ultra", 40 | "breakpointSamsungGalaxyS8Plus": "Samsung Galaxy S8+", 41 | "breakpointSurfaceDuo": "Surface Duo", 42 | "breakpointSurfacePro7": "Surface Pro 7", 43 | "button": "Bouton", 44 | "buttonAlignment": "Alignement du bouton", 45 | "buttonDescription": "Ajouter un bouton qui pointe vers une page ou une URL", 46 | "buttonLinks": "Liens de bouton", 47 | "buttonLinksDescription": "Ajouter des liens de bouton en bas du hero", 48 | "card": "Carte", 49 | "cardAdd": "Ajouter une carte", 50 | "cardBackground": "La carte a-t-elle une couleur de fond ?", 51 | "center": "Centré", 52 | "close": "Fermé", 53 | "content": "Contenu", 54 | "customUrl": "URL personnalisée", 55 | "defaultPage": "Page par défaut", 56 | "defaultPages": "Pages par défaut", 57 | "elements": "Éléments", 58 | "feature": "Fonctionnalité", 59 | "file": "Fichier", 60 | "fileToLink": "Fichier à lier", 61 | "formattedLike": "Formaté comme {ORG_NAME}/{REPO_NAME}", 62 | "fullWidth": "Pleine largeur", 63 | "general": "Général", 64 | "ghPrDescription": "Afficher une liste de pull requests d’un dépôt GitHub", 65 | "ghPrLabel": "Pull requests GitHub", 66 | "hero": "Hero", 67 | "heroDescription": "Ajouter un hero principal à votre page", 68 | "home": "Accueil", 69 | "homePage": "Page d’accueil", 70 | "homePages": "Pages d’accueil", 71 | "homeTitle": "Accueil : Démo ApostropheCMS", 72 | "horizontal": "Horizontal", 73 | "icon": "Icône", 74 | "iconBuilding": "Bâtiment", 75 | "iconChart": "Graphique", 76 | "iconClock": "Horloge", 77 | "iconCpu": "Processeur", 78 | "iconEarth": "Terre", 79 | "iconEditor": "Éditeur", 80 | "iconLandmark": "Monument", 81 | "iconPackage": "Paquet", 82 | "iconPeople": "Personnes", 83 | "iconRocket": "Fusée", 84 | "iconTrophy": "Trophée", 85 | "image": "Image", 86 | "imageDescription": "Afficher des images sur votre page", 87 | "layout": "Mise en page", 88 | "layoutDescription": "Créer des mises en page à colonnes flexibles", 89 | "layoutTools": "Outils de mise en page", 90 | "left": "Gauche", 91 | "limit": "Limite", 92 | "linkStyle": "Style de lien", 93 | "linkText": "Texte du lien", 94 | "linkType": "Type de lien", 95 | "listOfFeatures": "Liste des fonctionnalités", 96 | "listOfFeaturesHelp": "Liste des fonctionnalités incluses dans ce package", 97 | "main": "Principal", 98 | "media": "Média", 99 | "needArticleIndex": "Vous devez publier une page d’index des articles pour que les liens fonctionnent.", 100 | "noArticles": "Il n’y a pas encore d’articles. Essayez d’en ajouter un.", 101 | "noSummary": "Aucun résumé pour cet article", 102 | "open": "Ouvert", 103 | "openNewBrowserTab": "Ouvrir dans un nouvel onglet du navigateur ?", 104 | "openNewTab": "Ouvrir dans un nouvel onglet", 105 | "orientation": "Orientation", 106 | "outline": "Contour", 107 | "page": "Page", 108 | "pageToLink": "Page à lier", 109 | "preferences": "Préférences", 110 | "priceDetail": "Détails de prix supplémentaires", 111 | "priceText": "Texte du prix", 112 | "priceTextHelp": "Ex. : 20 €, Personnalisé, etc.", 113 | "priceUnit": "Intervalle de prix", 114 | "priceUnitHelp": "Optionnel ex. : /mois, /an", 115 | "pricingCard": "Carte tarifaire", 116 | "pricingCardDescription": "Carte pouvant afficher des tarifs et des ensembles de fonctionnalités.", 117 | "primary": "Principal", 118 | "repo": "Dépôt", 119 | "richText": "Texte enrichi", 120 | "richTextDescription": "Ajouter du texte stylisé à votre page", 121 | "right": "Droite", 122 | "rtH2": "Titre 2 (

)", 123 | "rtH3": "Titre 3 (

)", 124 | "rtH4": "Titre 4 (

)", 125 | "rtH5": "Titre 5 (

)", 126 | "rtHighlightBlue": "Surlignage bleu", 127 | "rtHighlightMustard": "Surlignage moutarde", 128 | "rtHighlightPurple": "Surlignage violet", 129 | "rtHighlightRed": "Surlignage rouge", 130 | "rtHighlightSeafoam": "Surlignage vert d’eau", 131 | "rtMetaH6": "Méta (
)", 132 | "rtParagraph": "Paragraphe (

)", 133 | "rtParagraphLarge": "Grand paragraphe (

)", 134 | "secondary": "Secondaire", 135 | "siteTitle": "Titre du site", 136 | "specialBackground": "Une carte mise en avant a un arrière-plan spécial", 137 | "state": "État", 138 | "style": "Style", 139 | "textContent": "Contenu texte", 140 | "textContentHelp": "Le contenu texte du hero", 141 | "title": "Titre", 142 | "urlForCustom": "URL du lien personnalisé", 143 | "vertical": "Vertical", 144 | "video": "Vidéo", 145 | "videoDescription": "Ajouter un lecteur vidéo depuis des services comme YouTube", 146 | "writtenBy": "Écrit par" 147 | } 148 | -------------------------------------------------------------------------------- /modules/@apostrophecms/i18n/i18n/project/mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": "Cuenta", 3 | "addBadge": "¿Agregar una insignia a la tarjeta?", 4 | "apostropheCmsSite": "Sitio ApostropheCMS", 5 | "article": "Artículo", 6 | "articleAuthor": "Autor", 7 | "articleAuthorColumn": "Autor", 8 | "articleBlurb": "Resumen", 9 | "articleBlurbHelp": "Un resumen breve", 10 | "articleCategories": "Categorías de artículos", 11 | "articleCategoriesColumn": "Categorías", 12 | "articleCategory": "Categoría de artículo", 13 | "articleFeaturedImage": "Imagen destacada", 14 | "articleFeaturedImageColumn": "Imagen destacada", 15 | "articleIndexPage": "Página índice de artículos", 16 | "articleIndexPages": "Páginas índice de artículos", 17 | "articleIntro": "Introducción", 18 | "articleOrientation": "Orientación de visualización", 19 | "articlePublishedDate": "Fecha de publicación", 20 | "articleRecentArticles": "Artículos recientes", 21 | "articleWidgetDescription": "Mostrar una lista de artículos recientes o seleccionados", 22 | "articles": "Artículos", 23 | "badgeLabel": "Etiqueta de la insignia", 24 | "basics": "Básico", 25 | "breakpointGalaxyZFold5Front": "Galaxy Z Fold 5 (pantalla externa)", 26 | "breakpointGalaxyZFold5Inner": "Galaxy Z Fold 5 (pantalla interna)", 27 | "breakpointIpadAir": "iPad Air", 28 | "breakpointIpadMini": "iPad Mini", 29 | "breakpointIpadPro": "iPad Pro", 30 | "breakpointIphone12Pro": "iPhone 12 Pro", 31 | "breakpointIphone14ProMax": "iPhone 14 Pro Max", 32 | "breakpointIphoneSE": "iPhone SE", 33 | "breakpointIphoneXR": "iPhone XR", 34 | "breakpointNestHub": "Nest Hub", 35 | "breakpointNestHubMax": "Nest Hub Max", 36 | "breakpointPixel7": "Pixel 7", 37 | "breakpointResponsive": "Responsivo", 38 | "breakpointSamsungGalaxyA51A71": "Samsung Galaxy A51/71", 39 | "breakpointSamsungGalaxyS20Ultra": "Samsung Galaxy S20 Ultra", 40 | "breakpointSamsungGalaxyS8Plus": "Samsung Galaxy S8+", 41 | "breakpointSurfaceDuo": "Surface Duo", 42 | "breakpointSurfacePro7": "Surface Pro 7", 43 | "button": "Botón", 44 | "buttonAlignment": "Alineación del botón", 45 | "buttonDescription": "Agregar un botón que enlace a una página o URL", 46 | "buttonLinks": "Enlaces del botón", 47 | "buttonLinksDescription": "Agregar enlaces de botón en la parte inferior del hero", 48 | "card": "Tarjeta", 49 | "cardAdd": "Agregar una tarjeta", 50 | "cardBackground": "¿La tarjeta tiene un color de fondo?", 51 | "center": "Centrado", 52 | "close": "Cerrado", 53 | "content": "Contenido", 54 | "customUrl": "URL personalizada", 55 | "defaultPage": "Página predeterminada", 56 | "defaultPages": "Páginas predeterminadas", 57 | "elements": "Elementos", 58 | "feature": "Funcionalidad", 59 | "file": "Archivo", 60 | "fileToLink": "Archivo a enlazar", 61 | "formattedLike": "Con formato como {ORG_NAME}/{REPO_NAME}", 62 | "fullWidth": "Ancho completo", 63 | "general": "General", 64 | "ghPrDescription": "Mostrar una lista de pull requests de un repositorio de GitHub", 65 | "ghPrLabel": "Pull requests de GitHub", 66 | "hero": "Hero", 67 | "heroDescription": "Agregar un hero principal a tu página", 68 | "home": "Inicio", 69 | "homePage": "Página de inicio", 70 | "homePages": "Páginas de inicio", 71 | "homeTitle": "Inicio: Demo de ApostropheCMS", 72 | "horizontal": "Horizontal", 73 | "icon": "Ícono", 74 | "iconBuilding": "Edificio", 75 | "iconChart": "Gráfica", 76 | "iconClock": "Reloj", 77 | "iconCpu": "CPU", 78 | "iconEarth": "Tierra", 79 | "iconEditor": "Editor", 80 | "iconLandmark": "Monumento", 81 | "iconPackage": "Paquete", 82 | "iconPeople": "Personas", 83 | "iconRocket": "Cohete", 84 | "iconTrophy": "Trofeo", 85 | "image": "Imagen", 86 | "imageDescription": "Mostrar imágenes en tu página", 87 | "layout": "Diseño", 88 | "layoutDescription": "Crear diseños de columnas flexibles", 89 | "layoutTools": "Herramientas de diseño", 90 | "left": "Izquierda", 91 | "limit": "Límite", 92 | "linkStyle": "Estilo del enlace", 93 | "linkText": "Texto del enlace", 94 | "linkType": "Tipo de enlace", 95 | "listOfFeatures": "Lista de funcionalidades", 96 | "listOfFeaturesHelp": "Lista de funcionalidades incluidas en este paquete", 97 | "main": "Principal", 98 | "media": "Medios", 99 | "needArticleIndex": "Debes publicar una página índice de artículos para que los enlaces funcionen.", 100 | "noArticles": "Aún no hay artículos. Intenta agregar uno.", 101 | "noSummary": "No hay resumen para este artículo", 102 | "open": "Abierto", 103 | "openNewBrowserTab": "¿Abrir en una nueva pestaña del navegador?", 104 | "openNewTab": "Abrir en una nueva pestaña", 105 | "orientation": "Orientación", 106 | "outline": "Contorno", 107 | "page": "Página", 108 | "pageToLink": "Página a enlazar", 109 | "preferences": "Preferencias", 110 | "priceDetail": "Detalles adicionales del precio", 111 | "priceText": "Texto del precio", 112 | "priceTextHelp": "Ej.: $20, Personalizado, etc.", 113 | "priceUnit": "Intervalo de precio", 114 | "priceUnitHelp": "Opcional ej.: /mes, /año", 115 | "pricingCard": "Tarjeta de precios", 116 | "pricingCardDescription": "Tarjeta que puede mostrar precios y conjuntos de funcionalidades.", 117 | "primary": "Principal", 118 | "repo": "Repositorio", 119 | "richText": "Texto enriquecido", 120 | "richTextDescription": "Agregar texto con estilo a tu página", 121 | "right": "Derecha", 122 | "rtH2": "Encabezado 2 (

)", 123 | "rtH3": "Encabezado 3 (

)", 124 | "rtH4": "Encabezado 4 (

)", 125 | "rtH5": "Encabezado 5 (

)", 126 | "rtHighlightBlue": "Resaltado azul", 127 | "rtHighlightMustard": "Resaltado mostaza", 128 | "rtHighlightPurple": "Resaltado morado", 129 | "rtHighlightRed": "Resaltado rojo", 130 | "rtHighlightSeafoam": "Resaltado verde agua", 131 | "rtMetaH6": "Meta (
)", 132 | "rtParagraph": "Párrafo (

)", 133 | "rtParagraphLarge": "Párrafo grande (

)", 134 | "secondary": "Secundario", 135 | "siteTitle": "Título del sitio", 136 | "specialBackground": "Una tarjeta destacada tiene un fondo especial", 137 | "state": "Estado", 138 | "style": "Estilo", 139 | "textContent": "Contenido de texto", 140 | "textContentHelp": "El contenido de texto del hero", 141 | "title": "Título", 142 | "urlForCustom": "URL del enlace personalizado", 143 | "vertical": "Vertical", 144 | "video": "Video", 145 | "videoDescription": "Agregar un reproductor de video desde servicios como YouTube", 146 | "writtenBy": "Escrito por" 147 | } 148 | -------------------------------------------------------------------------------- /modules/@apostrophecms/template/views/macros/icons.html: -------------------------------------------------------------------------------- 1 | {% macro clock() %} 2 | 3 | {% endmacro %} 4 | 5 | {% macro building() %} 6 | 7 | {% endmacro %} 8 | 9 | {% macro landmark() %} 10 | 11 | {% endmacro %} 12 | 13 | {% macro cpu() %} 14 | 15 | {% endmacro %} 16 | 17 | {% macro trophy() %} 18 | 19 | {% endmacro %} 20 | 21 | {% macro people() %} 22 | 23 | {% endmacro %} 24 | 25 | {% macro earth() %} 26 | 27 | {% endmacro %} 28 | 29 | {% macro chart() %} 30 | 31 | {% endmacro %} 32 | 33 | {% macro smartphone() %} 34 | 35 | {% endmacro %} 36 | 37 | {% macro network() %} 38 | 39 | {% endmacro %} 40 | 41 | {% macro rocket() %} 42 | 43 | {% endmacro %} 44 | 45 | {% macro package() %} 46 | 47 | {% endmacro %} 48 | 49 | {% macro editor() %} 50 | 51 | {% endmacro %} 52 | -------------------------------------------------------------------------------- /modules/@apostrophecms/image-widget/public/preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /views/layout.html: -------------------------------------------------------------------------------- 1 | {# Automatically extends the right outer layout and also handles AJAX situations #} 2 | {% extends data.outerLayout %} 3 | {% import 'link.html' as link %} 4 | {% import "locales.html" as locales %} 5 | 6 | {# {% block bodyClass %}dark{% endblock %} #} 7 | 8 | {% set title = data.piece.title or data.page.title %} 9 | {% block title %} 10 | {{ title }} - {{ data.global.siteTitle or 'ApostropheCMS Demo'}} 11 | {% if not title %} 12 | {{ apos.log('Looks like you forgot to override the title block in a template that does not have access to an Apostrophe page or piece.') }} 13 | {% endif %} 14 | {% endblock %} 15 | 16 | {% block extraHead %} 17 | {# 18 | This block outputs its contents in the HTML document's . 19 | It is a good place to put extra