├── public ├── robots.txt └── images │ └── apos-addcontent.gif ├── deployment ├── .gitignore ├── migrate ├── README ├── settings.production.example ├── rsync_exclude.txt ├── stop ├── settings ├── dependencies └── start ├── lib └── modules │ ├── apostrophe-assets │ ├── public │ │ ├── css │ │ │ ├── objects │ │ │ │ ├── _blocks.less │ │ │ │ ├── _colors.less │ │ │ │ ├── _icon.less │ │ │ │ ├── _page.less │ │ │ │ ├── _spacing.less │ │ │ │ ├── _screen.less │ │ │ │ ├── _empty-state.less │ │ │ │ ├── _container.less │ │ │ │ ├── _backgrounds.less │ │ │ │ ├── _link.less │ │ │ │ ├── _tag.less │ │ │ │ ├── _filters.less │ │ │ │ ├── _button.less │ │ │ │ ├── _card.less │ │ │ │ ├── _type.less │ │ │ │ └── _fields.less │ │ │ ├── settings │ │ │ │ ├── _zindex.less │ │ │ │ ├── _layout.less │ │ │ │ ├── _transitions.less │ │ │ │ ├── _flourishes.less │ │ │ │ ├── _colors.less │ │ │ │ ├── _fonts.less │ │ │ │ └── _breakpoints.less │ │ │ ├── components │ │ │ │ ├── _article-page.less │ │ │ │ ├── _link-widget.less │ │ │ │ ├── _image.less │ │ │ │ ├── _artist-page.less │ │ │ │ ├── _content-widget.less │ │ │ │ ├── _definition-list.less │ │ │ │ ├── _meta-columns.less │ │ │ │ ├── _map.less │ │ │ │ ├── _header.less │ │ │ │ ├── _breadcrumbs.less │ │ │ │ ├── _area.less │ │ │ │ ├── _searchbar.less │ │ │ │ ├── _event-widget.less │ │ │ │ ├── _artworks-widget.less │ │ │ │ ├── _generic-filters.less │ │ │ │ ├── _mobile-menu.less │ │ │ │ ├── _article-widget.less │ │ │ │ ├── _filters.less │ │ │ │ ├── _random-met-artwork.less │ │ │ │ ├── _event-page.less │ │ │ │ ├── _tag-picker.less │ │ │ │ ├── _checkbox-filter.less │ │ │ │ ├── _two-panel.less │ │ │ │ ├── _peer-nav.less │ │ │ │ ├── _pagination.less │ │ │ │ ├── _location-widget.less │ │ │ │ ├── _artwork-page.less │ │ │ │ ├── _article-featured-widget.less │ │ │ │ ├── _popup.less │ │ │ │ ├── _loading.less │ │ │ │ ├── _search-page.less │ │ │ │ ├── _masthead.less │ │ │ │ ├── _artworks-cards.less │ │ │ │ ├── _generic-card.less │ │ │ │ ├── _rich-text.less │ │ │ │ ├── _marquee.less │ │ │ │ ├── _footer.less │ │ │ │ ├── _slideshow.less │ │ │ │ ├── _demo-notification.less │ │ │ │ ├── _people-page.less │ │ │ │ ├── _columns.less │ │ │ │ ├── _demo-modal.less │ │ │ │ └── _multirange.less │ │ │ ├── base │ │ │ │ └── _document.less │ │ │ ├── generic │ │ │ │ └── _reset.less │ │ │ ├── utils │ │ │ │ └── _admin.less │ │ │ ├── vendor │ │ │ │ └── _multirange.less │ │ │ └── site.less │ │ └── js │ │ │ ├── site.js │ │ │ ├── modules │ │ │ ├── mobile-menu.js │ │ │ ├── pieces-pages-search.js │ │ │ ├── logo-mask.js │ │ │ ├── bio-box.js │ │ │ └── map.js │ │ │ ├── utils.js │ │ │ ├── popups │ │ │ ├── base.js │ │ │ ├── multirange.js │ │ │ ├── checkboxes.js │ │ │ └── tags.js │ │ │ └── vendor │ │ │ └── multirange.js │ └── index.js │ ├── apostrophe-pages │ ├── views │ │ ├── pages │ │ │ ├── home.html │ │ │ └── default.html │ │ └── notFound.html │ └── index.js │ ├── apostrophe-db │ └── index.js │ ├── artists-pages │ ├── index.js │ └── views │ │ ├── index.html │ │ └── show.html │ ├── apostrophe-rich-text-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── apostrophe-images-widgets │ └── views │ │ ├── single.html │ │ ├── widget.html │ │ └── background.html │ ├── people-pages │ ├── index.js │ └── views │ │ └── index.html │ ├── events-pages │ ├── index.js │ └── views │ │ ├── show.html │ │ └── index.html │ ├── link-widgets │ ├── lib │ │ ├── tab.js │ │ └── schema.js │ ├── index.js │ └── views │ │ └── widget.html │ ├── apostrophe-express │ └── index.js │ ├── category-object-types │ └── index.js │ ├── apostrophe-widgets │ └── index.js │ ├── articles-pages │ ├── index.js │ └── views │ │ ├── index.html │ │ ├── show.html │ │ └── indexAjax.html │ ├── image-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── apostrophe-global │ ├── views │ │ ├── body-snippet.html │ │ └── head-snippet.html │ └── index.js │ ├── logo-mask-widgets │ ├── index.js │ └── views │ │ └── widget.html │ ├── apostrophe-users │ └── index.js │ ├── feature-widgets │ └── index.js │ ├── apostrophe-templates │ └── views │ │ └── outerLayout.html │ ├── random-met-artwork-widgets │ ├── views │ │ └── widget.html │ ├── index.js │ └── public │ │ └── js │ │ └── always.js │ ├── content-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── artworks-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── articles-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── articles │ └── index.js │ ├── events-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── two-panel-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── apostrophe-admin-bar │ └── index.js │ ├── locations-widgets │ ├── views │ │ └── widget.html │ ├── index.js │ └── public │ │ └── js │ │ └── always.js │ ├── apostrophe-search │ ├── index.js │ └── views │ │ ├── empty.html │ │ └── index.html │ ├── columns-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── people │ └── index.js │ ├── articles-featured-widgets │ ├── index.js │ └── views │ │ └── widget.html │ ├── default-pages │ └── index.js │ ├── artworks-pages │ ├── index.js │ └── views │ │ ├── index.html │ │ └── show.html │ ├── events │ └── index.js │ ├── slideshow-widgets │ ├── index.js │ ├── views │ │ └── widget.html │ └── public │ │ └── js │ │ └── always.js │ ├── marquee-widgets │ ├── views │ │ └── widget.html │ └── index.js │ ├── artworks │ └── lib │ │ └── cursor.js │ ├── helpers │ ├── lib │ │ └── areas.js │ └── index.js │ ├── styleguide │ ├── public │ │ └── css │ │ │ └── styleguide.less │ └── index.js │ ├── artists │ └── index.js │ └── locations │ └── index.js ├── .eslintignore ├── views ├── macros │ ├── filters │ │ ├── submit.html │ │ ├── search.html │ │ ├── tag-picker.html │ │ ├── multirange.html │ │ └── checkboxes.html │ ├── button.html │ ├── loading.html │ ├── tags.html │ ├── breadcrumbs.html │ ├── definitionList.html │ ├── peer-nav.html │ ├── searchbar.html │ ├── map.html │ ├── image.html │ ├── artist-cards.html │ ├── artwork-cards.html │ ├── masthead.html │ └── generic-cards.html ├── header.html ├── analytics.html ├── footer.html ├── demo │ ├── notification.html │ └── modal.html ├── navigation.html └── layout.html ├── .editorconfig ├── Dockerfile ├── .dockerignore ├── .gitignore ├── local.example.js ├── LICENSE ├── .eslintrc ├── scripts ├── sync-down └── sync-up └── package.json /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /modules/ -------------------------------------------------------------------------------- /deployment/.gitignore: -------------------------------------------------------------------------------- 1 | # settings 2 | # settings.production 3 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_blocks.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_zindex.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /public 2 | /node_modules 3 | /**/vendor/**/*.js 4 | 5 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_article-page.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_layout.less: -------------------------------------------------------------------------------- 1 | @layout-max-width: 144rem; -------------------------------------------------------------------------------- /lib/modules/apostrophe-pages/views/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "pages/default.html" %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_transitions.less: -------------------------------------------------------------------------------- 1 | @transition-ease-in-out: all 0.2s ease-in-out; -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_colors.less: -------------------------------------------------------------------------------- 1 | .o-color-brand-primary { color: @color-brand-primary; } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_flourishes.less: -------------------------------------------------------------------------------- 1 | @shadow: 2px 5px 10px -3px @color-brand-primary; -------------------------------------------------------------------------------- /lib/modules/apostrophe-db/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | connect: { 3 | useUnifiedTopology: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /lib/modules/artists-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Artist Page', 3 | name: 'artist-pages', 4 | addFields: [] 5 | }; 6 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_icon.less: -------------------------------------------------------------------------------- 1 | .o-icon { 2 | position: relative; 3 | } 4 | 5 | .o-icon__description { 6 | 7 | } -------------------------------------------------------------------------------- /public/images/apos-addcontent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apostrophecms-legacy/apostrophe-open-museum/HEAD/public/images/apos-addcontent.gif -------------------------------------------------------------------------------- /lib/modules/apostrophe-rich-text-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 |
{{ data.widget.content | safe }}
-------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_page.less: -------------------------------------------------------------------------------- 1 | .o-page { 2 | display: flex; 3 | @media @breakpoint-mid { 4 | display: block; 5 | } 6 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-images-widgets/views/single.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | {{ image.render(apos.images.first(data.widget)) }} 3 | -------------------------------------------------------------------------------- /lib/modules/people-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'People Page', 3 | addFields: [], 4 | name: 'people-pages', 5 | perPage: 20 6 | }; 7 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_link-widget.less: -------------------------------------------------------------------------------- 1 | .c-link__items { 2 | display: flex; 3 | } 4 | 5 | .c-link__item { 6 | margin-right: 3rem; 7 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_spacing.less: -------------------------------------------------------------------------------- 1 | .o-mt-6 { margin-top: 6rem; } 2 | .o-mt-1 { margin-top: 1rem; } 3 | .o-mb-0 { margin-bottom: 0rem !important } -------------------------------------------------------------------------------- /lib/modules/events-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'event-pages', 3 | label: 'Event Page', 4 | piecesFilters: [ 5 | { name: 'tags' } 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /lib/modules/link-widgets/lib/tab.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'linkText', 3 | 'linkType', 4 | 'linkUrl', 5 | '_linkPage', 6 | 'linkStyle', 7 | 'linkTarget' 8 | ]; 9 | -------------------------------------------------------------------------------- /views/macros/filters/submit.html: -------------------------------------------------------------------------------- 1 | {% macro render() %} 2 | 3 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_image.less: -------------------------------------------------------------------------------- 1 | .o-image { 2 | width: 100%; 3 | } 4 | 5 | .o-image__caption { 6 | text-align: right; 7 | margin-top: 1rem; 8 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-express/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | session: { 3 | // You should set a unique, random secret as a string 4 | secret: undefined 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /views/macros/button.html: -------------------------------------------------------------------------------- 1 | {% macro render(label, url, options) %} 2 | {{ label }} 3 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_artist-page.less: -------------------------------------------------------------------------------- 1 | .c-artist-page__image img { width: 100%; } 2 | .c-artist-page__image, 3 | .c-artist-page__content { 4 | margin-bottom: 7rem; 5 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-images-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% if data.options.template %} 2 | {% extends data.options.template + '.html' %} 3 | {% else %} 4 | {% extends 'widgetBase.html' %} 5 | {% endif %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_screen.less: -------------------------------------------------------------------------------- 1 | .o-screen { 2 | position: absolute; 3 | height: 100%; 4 | width: 100%; 5 | top: 0; 6 | left: 0; 7 | background-color: @color-dark; 8 | } -------------------------------------------------------------------------------- /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 | node app apostrophe-migrations:migrate 7 | 8 | echo "Site migrated" 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /views/macros/loading.html: -------------------------------------------------------------------------------- 1 | {% macro render() %} 2 |
3 |
4 |
5 |
6 |
7 | {% endmacro %} 8 | -------------------------------------------------------------------------------- /lib/modules/category-object-types/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | searchable: false, 3 | removeFields: ['tags'], 4 | name: 'category-object-type', 5 | label: 'Category Object Type', 6 | pluralLabel: 'Category Object Types' 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 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_content-widget.less: -------------------------------------------------------------------------------- 1 | .c-content-widget { 2 | border: 1px solid @color-dark; 3 | padding: 4rem 6rem; 4 | max-width: 80rem 5 | } 6 | 7 | .c-content-widget__headline { 8 | margin-bottom: 3rem; 9 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_definition-list.less: -------------------------------------------------------------------------------- 1 | .c-definition-list { 2 | 3 | } 4 | 5 | .c-definition-list__term { 6 | margin-bottom: 0.5rem; 7 | } 8 | 9 | .c-definition-list__detail { 10 | margin-bottom: 3.5rem; 11 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_meta-columns.less: -------------------------------------------------------------------------------- 1 | .c-meta-columns { 2 | display: flex; 3 | } 4 | 5 | .c-meta-columns__left { 6 | width: 36%; 7 | margin-right: 3%; 8 | } 9 | 10 | .c-meta-columns__right { 11 | width: 60%; 12 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | construct: function (self, options) { 3 | self.getWidgetWrapperClasses = function (widget) { 4 | return [ 5 | 'o-widget-wrapper' 6 | ]; 7 | }; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /lib/modules/articles-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Article Page', 3 | name: 'article-pages', 4 | piecesFilters: [ 5 | { name: 'year' }, 6 | { name: 'month' }, 7 | { name: 'day' }, 8 | { name: 'tags' } 9 | ] 10 | }; 11 | -------------------------------------------------------------------------------- /lib/modules/image-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | 3 |
4 | {% set imgObj = apos.images.first(data.widget.image) %} 5 | {{ image.render(imgObj, { description: data.widget.caption }) }} 6 |
7 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_empty-state.less: -------------------------------------------------------------------------------- 1 | .o-empty-state { 2 | background-color: @color-light; 3 | padding: 10rem; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | border: 1px solid @color-brand-primary; 8 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/site.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // Start with your project-level client-side javascript here. 3 | // JQuery and lodash (_) are both included with Apostrophe, so no need to 4 | // worry about including them on your own. 5 | }); 6 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/modules/mobile-menu.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var $body = $('body'); 3 | var stateClass = 's-show-menu'; 4 | 5 | $body.on('click', '[data-mobile-trigger]', function () { 6 | $body.toggleClass(stateClass); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /deployment/settings.production.example: -------------------------------------------------------------------------------- 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=myuser 9 | SERVER=myserver.com 10 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_map.less: -------------------------------------------------------------------------------- 1 | .c-map { 2 | position: relative; 3 | z-index: 10; 4 | border: 1px solid @color-dark; 5 | } 6 | 7 | .c-map__details { 8 | padding: 3rem; 9 | border-bottom: 1px solid @color-dark; 10 | } 11 | 12 | .c-map__canvas { 13 | height: 24rem; 14 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-global/views/body-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_header.less: -------------------------------------------------------------------------------- 1 | .c-header { 2 | border-bottom: 3px solid @color-light; 3 | padding-left: 1rem; 4 | padding-right: 1rem; 5 | } 6 | .c-header__inner { 7 | display: flex; 8 | justify-content: space-between; 9 | align-content: center; 10 | padding: 3rem 0; 11 | } -------------------------------------------------------------------------------- /lib/modules/logo-mask-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Logo Mask', 3 | addFields: [ 4 | { 5 | name: 'image', 6 | label: 'Image', 7 | type: 'singleton', 8 | widgetType: 'apostrophe-images', 9 | options: { 10 | limit: 1 11 | } 12 | } 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_colors.less: -------------------------------------------------------------------------------- 1 | @color-dark: #282828; 2 | @color-med: #4A4A4A; 3 | @color-light: #EEEDF1; 4 | @color-white: #fff; 5 | 6 | @color-brand-primary: #524979; 7 | @color-brand-secondary: #F6EFE0; 8 | 9 | @color-brand-gradient: linear-gradient(41deg, #F474FF 0%, #6060ED 66%, #0D3E34 100%); 10 | -------------------------------------------------------------------------------- /views/macros/filters/search.html: -------------------------------------------------------------------------------- 1 | {% macro render(pluralLabel) %} 2 | 6 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-users/index.js: -------------------------------------------------------------------------------- 1 | // This configures the apostrophe-users module to add an admin-level 2 | // group by default: 3 | 4 | module.exports = { 5 | groups: [ 6 | { 7 | title: 'guest', 8 | permissions: [ ] 9 | }, 10 | { 11 | title: 'admin', 12 | permissions: [ 'admin' ] 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_breadcrumbs.less: -------------------------------------------------------------------------------- 1 | .c-breadcrumbs__items { 2 | display: flex; 3 | margin-bottom: 3rem; 4 | @media @breakpoint-medium { 5 | justify-content: center; 6 | } 7 | } 8 | 9 | .c-breadcrumbs__item { 10 | margin-right: 0.5rem; 11 | } 12 | 13 | .c-breadcrumbs__link { 14 | text-decoration: none; 15 | } -------------------------------------------------------------------------------- /lib/modules/link-widgets/index.js: -------------------------------------------------------------------------------- 1 | const schema = require('./lib/schema.js'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = { 5 | label: 'Link', 6 | addFields: [ 7 | { 8 | name: 'links', 9 | label: 'Links', 10 | type: 'array', 11 | titleField: 'linkText', 12 | schema: _.clone(schema) 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /views/header.html: -------------------------------------------------------------------------------- 1 | {% import 'navigation.html' as navigation %} 2 | 3 | {% macro render(home, currentPage, images) %} 4 |
5 | 8 |
9 | {% endmacro %} 10 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-pages/views/notFound.html: -------------------------------------------------------------------------------- 1 | {# 2 | Use this template to build out your 404 error pages. Like page templates, 3 | it inherits a global layout. 4 | #} 5 | 6 | {% extends "layout.html" %} 7 | 8 | {% block title %}404 - Page not found{% endblock %} 9 | 10 | {% block main %} 11 | We're sorry. We couldn't find the page you're looking for. 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/base/_document.less: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | box-sizing: border-box; 4 | text-rendering: optimizeLegibility; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | font-variant-numeric: lining-nums; 8 | height: auto; 9 | font-size: 10px; 10 | } 11 | 12 | *, *:before, *:after { box-sizing: inherit; } 13 | 14 | img { max-width: 100%; } -------------------------------------------------------------------------------- /lib/modules/articles-pages/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% import 'macros/masthead.html' as masthead %} 3 | 4 | {% block main %} 5 | {{ masthead.render(data.page, { class: 'o-mb-0' }) }} 6 | {{ apos.singleton(data.page, 'featured', 'articles-featured') }} 7 | 8 |
9 | {% include "indexAjax.html" %} 10 |
11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /lib/modules/feature-widgets/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | module.exports = { 4 | label: 'Feature', 5 | modifier: 'c-marquee--feature', 6 | alterFields: function (schema) { 7 | let image = _.find(schema, { name: 'image' }); 8 | image.options = { 9 | aspectRatio: [1, 0.25], 10 | minSize: [1400, 400], 11 | limit: 1, 12 | template: 'single' 13 | }; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /lib/modules/image-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Single Image', 3 | addFields: [ 4 | { 5 | name: 'image', 6 | label: 'Image', 7 | type: 'singleton', 8 | widgetType: 'apostrophe-images', 9 | options: { 10 | limit: 1 11 | } 12 | }, 13 | { 14 | name: 'caption', 15 | label: 'Caption', 16 | type: 'string' 17 | } 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_area.less: -------------------------------------------------------------------------------- 1 | .c-area { 2 | flex-grow: 1 3 | } 4 | 5 | .c-area > .apos-area > .apos-area-widgets > .o-widget-wrapper, 6 | .c-area > .apos-area > .o-widget-wrapper { 7 | margin-bottom: 7rem; 8 | &:last-child { 9 | margin-bottom: 0; 10 | } 11 | } 12 | 13 | .c-columns__column .apos-area > .apos-area-widgets > .o-widget-wrapper { 14 | margin-bottom: 3rem; 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/modules/pieces-pages-search.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('body').on('keypress', '[data-apos-ajax-context] input[name="search"]', function (e) { 3 | if (e.which === 13) { 4 | // Do not fire the first button in the form (default behavior), which would 5 | // remove the first selected tag 6 | e.preventDefault(); 7 | e.stopPropagation(); 8 | } 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_container.less: -------------------------------------------------------------------------------- 1 | .o-container { 2 | max-width: @layout-max-width; 3 | width: 100%; 4 | margin-left: auto; 5 | margin-right: auto; 6 | padding-left: 3rem; 7 | padding-right: 3rem; 8 | } 9 | 10 | .o-container--large { 11 | max-width: 118rem; 12 | } 13 | 14 | .o-container--medium { 15 | max-width: 106rem; 16 | } 17 | 18 | .o-container--narrow { 19 | max-width: 94rem; 20 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-images-widgets/views/background.html: -------------------------------------------------------------------------------- 1 | {# If we use apos.images.first to find the first image in the widget, #} 2 | {# that will also carry through any crop associated with it for us. #} 3 | {# This is safer and easier than writing data.widget._pieces[0].attachment #} 4 | 5 |
-------------------------------------------------------------------------------- /lib/modules/apostrophe-templates/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 | -------------------------------------------------------------------------------- /lib/modules/logo-mask-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% if data.widget.image.items %} 4 | {% set imageObj = data.widget.image.items[0]._pieces[0] %} 5 | {% endif %} 6 | 7 |
8 | {% if imageObj %} 9 | {{ svgs.logoMask([apos.attachments.url(apos.images.first(imageObj.item), { size: 'full' })]) }} 10 | {% else %} 11 | No image set 12 | {% endif %} 13 |
14 | -------------------------------------------------------------------------------- /views/macros/tags.html: -------------------------------------------------------------------------------- 1 | {% macro render(tags) %} 2 | 11 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-rich-text-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sanitizeHtml: { 3 | allowedClasses: { 4 | 'p': ['o-body', 'o-meta'], 5 | 'h2': ['o-section-header'], 6 | 'h3': ['o-headline'], 7 | 'h4': ['o-subheadline'] 8 | }, 9 | allowedTags: [ 10 | 'h2', 'h3', 'h4', 'p', 'a', 'ul', 'ol', 'li', 'strong', 'em', 'blockquote' 11 | ], 12 | allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'tel'] 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /views/analytics.html: -------------------------------------------------------------------------------- 1 | {% if data.global.trackingID %} 2 | 3 | 4 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-global/views/head-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_searchbar.less: -------------------------------------------------------------------------------- 1 | .c-searchbar { 2 | position: relative; 3 | max-width: 58rem; 4 | } 5 | 6 | .c-searchbar__label { 7 | position: absolute; 8 | top: -99999px; 9 | left: -99999px; 10 | } 11 | 12 | .c-searchbar__input { 13 | background-color: @color-white; 14 | width: 100%; 15 | padding: 1rem 2rem; 16 | } 17 | 18 | .c-searchbar__icon { 19 | position: absolute; 20 | z-index: 1; 21 | right: 2rem; 22 | top: 1.5rem; 23 | } -------------------------------------------------------------------------------- /views/macros/breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% macro render(page) %} 2 | 12 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/generic/_reset.less: -------------------------------------------------------------------------------- 1 | body, 2 | h1, 3 | h2, 4 | h3, 5 | h4, 6 | h5, 7 | h6, 8 | p, 9 | blockquote, 10 | pre, 11 | dl, 12 | dd, 13 | ol, 14 | ul, 15 | form, 16 | fieldset, 17 | legend, 18 | figure, 19 | table, 20 | th, 21 | td, 22 | caption, 23 | hr { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | ul { 29 | list-style: none; 30 | } 31 | 32 | button { 33 | padding: 0; 34 | border: 0; 35 | background: transparent; 36 | } 37 | 38 | address { 39 | font-style: normal; 40 | } -------------------------------------------------------------------------------- /views/macros/definitionList.html: -------------------------------------------------------------------------------- 1 | {% macro render(piece, fields) %} 2 | {% for item in fields %} 3 |
{{ item.label if item.label else item.name | capitalize }}
4 | {% if apos.helpers.isJoin(item.name) %} 5 |
{{ apos.helpers.typy(piece, item.name) }}
6 | {% else %} 7 |
{{ piece[item.name] }}
8 | {% endif %} 9 | {% endfor %} 10 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/articles-pages/views/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% import 'macros/masthead.html' as masthead %} 3 | {% set piece = data.piece %} 4 | 5 | {% block main %} 6 | {{ masthead.render(data.piece, { media: true, page: data.page, type: 'article' }) }} 7 |
8 |
9 | {{ apos.area(piece, 'body', { widgets: apos.helpers.baseWidgets }) }} 10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /views/macros/peer-nav.html: -------------------------------------------------------------------------------- 1 | {% macro render(peers, page) %} 2 | 12 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/utils/_admin.less: -------------------------------------------------------------------------------- 1 | .u-small-dropdowns { 2 | & > .apos-area > .apos-ui, 3 | & > .apos-area > .apos-area-widgets > .apos-area-widget-wrapper > .apos-ui{ 4 | .apos-dropdown-items { 5 | column-count: 3; 6 | max-width: 390px; 7 | } 8 | .apos-dropdown-item { 9 | padding: 12px 18px; 10 | font-size: 13px; 11 | break-inside: avoid; 12 | page-break-inside: avoid; 13 | } 14 | } 15 | } 16 | 17 | .leaflet-bottom, .leaflet-top { 18 | z-index: 400 !important; 19 | } -------------------------------------------------------------------------------- /views/macros/searchbar.html: -------------------------------------------------------------------------------- 1 | {% import 'apostrophe-templates:macros/svgs.html' as svgs %} 2 | 3 | {% macro render(value=undefined) %} 4 | 13 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/random-met-artwork-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/loading.html' as loading %} 2 | 3 |
4 |
5 | Loading a random work from the MET 6 | {{ loading.render() }} 7 |
8 |
9 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_event-widget.less: -------------------------------------------------------------------------------- 1 | .c-event-widget { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | 7 | .c-event-widget__header { 8 | margin-bottom: 2rem; 9 | } 10 | 11 | .c-event-widget__header { 12 | display: flex; 13 | justify-content: space-between; 14 | } 15 | 16 | .c-event-widget { 17 | .o-cards--2 { 18 | grid-template-columns: 47.5% 47.5%; 19 | max-width: 94rem; 20 | } 21 | .o-cards--1 { 22 | grid-template-columns: 100%; 23 | max-width: 80rem; 24 | } 25 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Intall dependencies and copy application 2 | FROM node:carbon as builder 3 | 4 | # Install dependencies only 5 | # Will only run npm install when dependencies changes 6 | COPY package.json package-lock.json /app/ 7 | RUN cd /app/ && npm install 8 | 9 | # Copy app 10 | COPY . /app/ 11 | 12 | # Build final cointainer 13 | FROM node:carbon 14 | 15 | # Copy app from previous stage 16 | COPY --from=builder /app /app 17 | WORKDIR /app 18 | # Mount persistent storage 19 | VOLUME /app/data 20 | VOLUME /app/public/uploads 21 | 22 | EXPOSE 3000 23 | CMD [ "npm", "start" ] 24 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_artworks-widget.less: -------------------------------------------------------------------------------- 1 | .c-artworks-widget { 2 | border: 1px solid @color-brand-primary; 3 | padding: 4rem; 4 | @media @breakpoint-large { 5 | // margin-left: 2rem; 6 | // margin-right: 2rem; 7 | border: none; 8 | } 9 | } 10 | 11 | .c-artworks-widgets__header { 12 | display: flex; 13 | justify-content: space-between; 14 | margin-bottom: 4rem; 15 | @media @breakpoint-medium { 16 | flex-direction: column; 17 | text-align: center; 18 | .o-headline { 19 | margin-bottom: 2rem; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_fonts.less: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Merriweather:400,700|Roboto:400,400i,500,700'); 2 | 3 | @font-family-display: 'Merriweather', serif; 4 | @font-family-body: 'Roboto', sans-serif; 5 | 6 | @font-size-title: 5.5rem; // 55px; 7 | @font-size-section-header: 4.5rem; // 45px; 8 | @font-size-headline: 3.6rem; // 36px; 9 | @font-size-subheadline: 2.2rem; // 22px; 10 | @font-size-lede: 2rem; // 20px; 11 | @font-size-body: 1.6rem; // 16px 12 | @font-size-body--small: 1.4rem; // 14px 13 | @font-size-meta: 1.2rem; //12px; -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/modules/logo-mask.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var $logoMask = $('[data-logo-mask]'); 3 | var $svg = $logoMask.find('svg'); 4 | $logoMask.on('mouseenter', function (e) { 5 | this.iid = setInterval(function () { 6 | animate(); 7 | }, 525); 8 | }); 9 | 10 | $logoMask.on('mouseleave', function (e) { 11 | this.iid && clearInterval(this.iid); 12 | }); 13 | 14 | function animate () { 15 | var $temp = $svg.find('image:first').clone(); 16 | $svg.find('image:first').remove(); 17 | $svg.append($temp); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /lib/modules/artists-pages/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros/masthead.html" as masthead %} 3 | {% import "apostrophe-pager:macros.html" as pager %} 4 | {% import "macros/artist-cards.html" as cards %} 5 | 6 | {% set page = data.page %} 7 | 8 | {% block main %} 9 | {{ masthead.render(data.page) }} 10 |
11 | {{ cards.render(data.pieces) }} 12 |
13 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 14 |
15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # ignore .git and .cache folders 2 | .git 3 | .cache 4 | 5 | npm-debug.log 6 | /public/uploads 7 | /public/apos-minified 8 | /data/temp/uploadfs 9 | node_modules 10 | # 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) 11 | /public/modules 12 | # We don't commit CSS, only LESS 13 | /public/css/*.css 14 | /public/css/*.map 15 | # Don't commit masters generated on the fly at startup, these import all the rest 16 | /public/css/master-*.less 17 | .jshintrc 18 | /public/js/_site-compiled.js 19 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_generic-filters.less: -------------------------------------------------------------------------------- 1 | .c-generic-filters .c-field-search { 2 | width: 40%; 3 | #search { 4 | max-width: none; 5 | min-width: none; 6 | } 7 | } 8 | 9 | .c-generic-filters .o-field-container--tags { 10 | width: 60%; 11 | } 12 | 13 | .c-generic-filters { 14 | @media @breakpoint-mid { 15 | flex-direction: column; 16 | .o-field-container { 17 | margin-right: 0; 18 | width: 100%; 19 | margin-bottom: 3rem; 20 | } 21 | .c-filters__submit { 22 | display: block; 23 | width: 100%; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_mobile-menu.less: -------------------------------------------------------------------------------- 1 | .c-navigation__mobile-trigger { 2 | color: #00f; 3 | -webkit-appearance: none; 4 | background: 0 0; 5 | border: none; 6 | display: none; 7 | position: relative; 8 | z-index: 2; 9 | @media @breakpoint-medium { 10 | display: block; 11 | } 12 | } 13 | 14 | .c-navigation__mobile-trigger__stroke { 15 | position: relative; 16 | height: 3px; 17 | width: 30px; 18 | background-color: @color-dark; 19 | transition: all .35s cubic-bezier(0,.82,1,1.43); 20 | &:not(:last-child) { 21 | margin-bottom: 6px; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/modules/content-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | {% import 'macros/generic-cards.html' as cards %} 3 |
4 | {% if w.headline %} 5 |

{{ w.headline }}

6 | {% endif %} 7 | {{ cards.render( w._content, { 8 | class: 'o-cards--horizontal c-content-widget__items', 9 | schema: [ { class: 'o-meta', field: 'type' }, 'title', 'startDate', 'publishedDate', 'tags', { label: 'Date: ', field: 'year' } ], 10 | tagsUrl: '/search' 11 | }) }} 12 |
-------------------------------------------------------------------------------- /lib/modules/link-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_article-widget.less: -------------------------------------------------------------------------------- 1 | .c-article-widget { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | 7 | .c-article-widget__inner { 8 | 9 | } 10 | 11 | .c-article-widget__header { 12 | margin-bottom: 2rem; 13 | } 14 | 15 | .c-article-widget__header { 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | 20 | .c-article-widget { 21 | .o-cards--2 { 22 | grid-template-columns: 47.5% 47.5%; 23 | max-width: 94rem; 24 | } 25 | .o-cards--1 { 26 | grid-template-columns: 100%; 27 | max-width: 80rem; 28 | } 29 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_backgrounds.less: -------------------------------------------------------------------------------- 1 | .o-background-dark { background-color: @color-dark } 2 | .o-background-med { background-color: @color-med } 3 | .o-background-light { background-color: @color-light } 4 | .o-background-white { background-color: @color-white } 5 | 6 | .o-background-brand-primary { background-color: @color-brand-primary } 7 | .o-background-brand-secondary { 8 | background-color: @color-brand-secondary; 9 | .c-rich-text { 10 | color: @color-brand-primary 11 | } 12 | } 13 | 14 | .o-background-image { 15 | background-size: cover; 16 | background-position: center; 17 | } -------------------------------------------------------------------------------- /lib/modules/content-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Mixed Content', 3 | addFields: [ 4 | { 5 | name: 'headline', 6 | label: 'Headline', 7 | type: 'string' 8 | }, 9 | { 10 | name: '_content', 11 | label: 'Content to Link', 12 | type: 'joinByArray', 13 | withType: ['artwork', 'artist', 'article', 'event'], 14 | filters: { 15 | projection: { 16 | _url: 1, 17 | slug: 1, 18 | title: 1, 19 | type: 1, 20 | thumbnail: 1, 21 | year: 1 22 | } 23 | } 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_filters.less: -------------------------------------------------------------------------------- 1 | .c-filters { 2 | display: flex; 3 | margin-bottom: 5rem; 4 | justify-content: center 5 | } 6 | 7 | .c-filters .o-field-container { 8 | margin-right: 2rem; 9 | } 10 | 11 | .c-filters { 12 | #search { min-width: 30rem; } 13 | #objectType { min-width: 20rem; } 14 | #location { min-width: 20rem; } 15 | } 16 | 17 | .c-filters__submit { 18 | height: 4.5rem; 19 | align-self: flex-end; 20 | padding: 0rem 3rem; 21 | white-space: nowrap; 22 | font-family: @font-family-body; 23 | letter-spacing: 1px; 24 | text-transform: uppercase; 25 | font-weight: 400; 26 | } -------------------------------------------------------------------------------- /lib/modules/artworks-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | {% import 'macros/button.html' as button %} 3 | {% import 'macros/artwork-cards.html' as artworkCards %} 4 | 5 |
6 |
7 | {% if w.title %} 8 |

{{ w.title }}

9 | {% endif %} 10 | {% if w._page %} 11 | {{ button.render('View ' + w._page.title, w._page._url) }} 12 | {% endif %} 13 |
14 |
15 | {{ artworkCards.render(w._pieces) }} 16 |
17 |
-------------------------------------------------------------------------------- /views/macros/map.html: -------------------------------------------------------------------------------- 1 | {% macro render(data) %} 2 |
3 |
4 |

Location

5 | {{ data.title }}
6 | {{ data.streetName }}
7 | {{ data.city }}, {{ data.stateCode }} {{ data.zipCode }} 8 | Directions 9 |
10 | 11 |
12 |
13 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_random-met-artwork.less: -------------------------------------------------------------------------------- 1 | .c-random-met-artwork--loading .c-random-met-artwork-image { 2 | display: none; 3 | } 4 | 5 | .c-random-met-artwork--loaded .c-random-met-artwork-loading { 6 | display: none; 7 | } 8 | 9 | .c-random-met-artwork-loading 10 | { 11 | position: relative; 12 | text-align: center; 13 | background-color: #EEEEEE; 14 | border: 1px solid #d8d8d8; 15 | padding: 6rem; 16 | width: 100%; 17 | font-size: 12px; 18 | font-family: sans-serif; 19 | 20 | .c-load-bar { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 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/modules 9 | public/apos-minified 10 | .git 11 | .gitignore 12 | # We don't deploy these anymore, instead we always 'npm install' to ensure 13 | # that any compiled C++ modules are built for the right architecture 14 | node_modules 15 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_event-page.less: -------------------------------------------------------------------------------- 1 | .c-event-page { 2 | 3 | } 4 | 5 | .c-event-page__body { 6 | display: flex; 7 | justify-content: space-between; 8 | @media @breakpoint-small { 9 | flex-direction: column; 10 | } 11 | } 12 | 13 | .c-event-page__body-content { 14 | width: 50%; 15 | @media @breakpoint-mid { 16 | width: 55%; 17 | } 18 | @media @breakpoint-small { 19 | width: 100%; 20 | margin-bottom: 7rem; 21 | } 22 | } 23 | 24 | .c-event-page__location { 25 | width: 28%; 26 | @media @breakpoint-mid { 27 | width: 35%; 28 | } 29 | @media @breakpoint-small { 30 | width: 100%; 31 | } 32 | } -------------------------------------------------------------------------------- /lib/modules/articles-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/generic-cards.html' as cards %} 2 | {% set w = data.widget %} 3 | 4 |
5 |
6 |
7 |

{{ w.headline }}

8 | {{ w.linkText }} 9 |
10 |
11 | {{ cards.render(w._pieces, { 12 | schema: ['title', 'publishedAt', 'tags'], 13 | tagsUrl: w.linkUrl if w.linkUrl else w._linkPage._url 14 | }) }} 15 |
16 |
17 |
-------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_link.less: -------------------------------------------------------------------------------- 1 | .o-link { 2 | display: inline-block; 3 | position: relative; 4 | padding-bottom: 5px; 5 | color: @color-dark; 6 | text-decoration: none; 7 | font-size: 1.8rem; 8 | font-family: @font-family-body; 9 | &:after { 10 | transition: all 0.3s ease; 11 | display: block; 12 | position: relative; 13 | content: ''; 14 | width: 100%; 15 | height: 1px; 16 | background-color: @color-brand-primary; 17 | left: 0; 18 | } 19 | &:hover { 20 | &:after { 21 | width: ~"calc(100% + 8px)"; 22 | left: -4px; 23 | } 24 | } 25 | &:active:after { 26 | height: 3px; 27 | } 28 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/settings/_breakpoints.less: -------------------------------------------------------------------------------- 1 | // breakpoints 2 | @breakpoint-xl: ~'(min-width: 1450px)'; 3 | @breakpoint-mini: ~'(max-width: 24rem)'; 4 | @breakpoint-small: ~'(max-width: 40rem)'; 5 | @breakpoint-medium: ~'(max-width: 52rem)'; 6 | @breakpoint-mid: ~'(max-width: 64rem)'; 7 | @breakpoint-large: ~'(max-width: 80rem)'; 8 | 9 | // html { 10 | // @media @breakpoint-xl { border: 10px solid orange; } 11 | // @media @breakpoint-large { border: 10px solid red; } 12 | // @media @breakpoint-mid { border: 10px solid cyan; } 13 | // @media @breakpoint-medium { border: 10px solid gold; } 14 | // @media @breakpoint-small { border: 10px solid green; } 15 | // } 16 | -------------------------------------------------------------------------------- /lib/modules/articles/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'article', 3 | label: 'Article', 4 | pluralLabel: 'Articles', 5 | contextual: true, 6 | addFields: [ 7 | { 8 | name: 'thumbnail', 9 | label: 'Thumbnail', 10 | type: 'singleton', 11 | widgetType: 'apostrophe-images', 12 | options: { 13 | aspectRatio: [ 3, 2 ], 14 | limit: 1 15 | } 16 | } 17 | ], 18 | construct: function (self, options) { 19 | options.arrangeFields = options.arrangeFields.map(group => { 20 | if (group.name === 'basic') { 21 | group.fields.push('thumbnail'); 22 | } 23 | return group; 24 | }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /lib/modules/events-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/generic-cards.html' as cards %} 2 | {% set w = data.widget %} 3 | 4 |
5 |
6 |
7 | {% if w.headline %} 8 |

{{ w.headline }}

9 | {% endif %} 10 | {{ w.linkText }} 11 |
12 |
13 | {{ cards.render(w._pieces, { 14 | schema: ['title', 'publishedAt', 'tags'], 15 | tagsUrl: w._linkPage._url 16 | }) }} 17 |
18 |
19 |
-------------------------------------------------------------------------------- /lib/modules/two-panel-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | {% if w.config %} 3 | {% set config = w.config %} 4 | {% endif %} 5 | 6 | {% set areaSchema = apos.helpers._find(data.manager.schema, { name: 'body' }) %} 7 | {% set imageSchema = apos.helpers._find(data.manager.schema, { name: 'image' }) %} 8 | 9 |
10 |
11 | {{ apos.area(w, 'body', areaSchema.options) }} 12 |
13 |
14 | {{ apos.singleton(w, 'image', 'apostrophe-images', imageSchema.options) }} 15 |
16 |
17 | -------------------------------------------------------------------------------- /lib/modules/events-widgets/index.js: -------------------------------------------------------------------------------- 1 | const linkSchema = require('../link-widgets/lib/schema.js'); 2 | const linkTab = require('../link-widgets/lib/tab.js'); 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | label: 'Event Widget', 7 | addFields: _.clone(linkSchema).concat([ 8 | { 9 | name: 'headline', 10 | label: 'Headline', 11 | type: 'string' 12 | } 13 | ]), 14 | arrangeFields: [ 15 | { 16 | name: 'main', 17 | label: 'Main', 18 | fields: [ 'headline', '_pieces', 'by', 'limitByAll', 'tags', 'limitByTag' ] 19 | }, 20 | { 21 | name: 'link', 22 | label: 'Link', 23 | fields: linkTab 24 | } 25 | ] 26 | }; 27 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-admin-bar/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | addGroups: [ 3 | { 4 | label: 'Content', 5 | items: [ 6 | 'artists', 7 | 'artworks', 8 | 'locations', 9 | 'articles', 10 | 'events', 11 | 'people', 12 | 'apostrophe-pages' 13 | ] 14 | }, 15 | { 16 | label: 'Categories', 17 | items: [ 18 | 'category-object-types', 19 | 'apostrophe-tags' 20 | ] 21 | }, 22 | { 23 | label: 'Media', 24 | items: ['apostrophe-images', 'apostrophe-files'] 25 | }, 26 | { 27 | label: 'Meta', 28 | items: ['apostrophe-global', 'apostrophe-users'] 29 | } 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | /locales 3 | npm-debug.log 4 | /data 5 | /public/uploads 6 | /public/apos-minified 7 | /data/temp/uploadfs 8 | node_modules 9 | # 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) 10 | /public/modules 11 | # We don't commit CSS, only LESS 12 | /public/css/*.css 13 | /public/css/*.map 14 | # Don't commit masters generated on the fly at startup, these import all the rest 15 | /public/css/master-*.less 16 | .jshintrc 17 | /public/js/_site-compiled.js 18 | 19 | # Don't commit demo specific settings 20 | /deployment/settings.staging 21 | /deployment/settings.production 22 | /deployment/settings.private* 23 | -------------------------------------------------------------------------------- /lib/modules/locations-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | 3 |
4 | 16 |
17 |
-------------------------------------------------------------------------------- /lib/modules/articles-widgets/index.js: -------------------------------------------------------------------------------- 1 | const linkSchema = require('../link-widgets/lib/schema.js'); 2 | const linkTab = require('../link-widgets/lib/tab.js'); 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | label: 'Article Widget', 7 | addFields: _.clone(linkSchema).concat([ 8 | { 9 | name: 'headline', 10 | label: 'Headline', 11 | type: 'string', 12 | required: true 13 | } 14 | ]), 15 | arrangeFields: [ 16 | { 17 | name: 'main', 18 | label: 'Main', 19 | fields: ['headline', '_pieces', 'by', 'limitByAll', 'tags', 'limitByTag'] 20 | }, 21 | { 22 | name: 'link', 23 | label: 'Link', 24 | fields: _.clone(linkTab) 25 | } 26 | ] 27 | }; 28 | -------------------------------------------------------------------------------- /views/footer.html: -------------------------------------------------------------------------------- 1 | {% import 'navigation.html' as navigation %} 2 | {% import 'macros/svgs.html' as svgs %} 3 | 4 | {% macro render(home, currentPage, images) %} 5 | 18 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_tag.less: -------------------------------------------------------------------------------- 1 | .o-tag-list { 2 | margin: 0; 3 | padding: 0; 4 | list-style-type: none; 5 | display: flex; 6 | flex-wrap: wrap; 7 | .o-tag { margin-right: 1rem; margin-bottom: 1rem; } 8 | } 9 | 10 | .o-tag { 11 | display: inline-block; 12 | text-decoration: none; 13 | font-size: 1rem; 14 | background-color: @color-light; 15 | border: 1px solid @color-brand-primary; 16 | color: @color-brand-primary; 17 | font-family: @font-family-body; 18 | text-transform: uppercase; 19 | padding: 0.5rem 0.75rem; 20 | transition: @transition-ease-in-out; 21 | &:hover { border-color: @color-light; } 22 | &:active, &.o-tag--active { border-color: @color-brand-primary; background-color: @color-brand-primary; color: @color-white; } 23 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-search/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | module.exports = { 4 | // These are the types that appear as search results. Since we 5 | // specified this option, no others do. 6 | // 7 | // Also use this to power the grouping order later on 8 | types: [ 9 | 'artwork', 10 | 'artist', 11 | 'event', 12 | 'article' 13 | ], 14 | perPage: 30, 15 | construct: function (self, options) { 16 | self.beforeIndex = function (req, callback) { 17 | // if no search query, send user to a search landing page 18 | if (_.isEmpty(req.query.search)) { 19 | req.template = self.renderer('empty'); 20 | } 21 | req.data.docs = _.groupBy(req.data.docs, 'type'); 22 | return callback(null); 23 | }; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_tag-picker.less: -------------------------------------------------------------------------------- 1 | .c-tag-picker { 2 | position: relative; 3 | } 4 | 5 | .c-tag-picker__tags { 6 | min-width: 45rem; 7 | } 8 | 9 | .c-tag-picker__display { 10 | background-color: @color-white; 11 | border: 1px solid @color-brand-primary; 12 | padding: 0.9rem; 13 | height: 4.4rem; 14 | } 15 | 16 | [data-tag-picker-selected='true'] { 17 | background-color: @color-brand-primary; 18 | color: @color-brand-secondary; 19 | } 20 | 21 | .c-tag-picker__display button { margin-right: 0.5rem; } 22 | 23 | .c-tag-picker__header { 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | margin-bottom: 2rem; 28 | svg { 29 | height: 20px; 30 | width: 20px; 31 | } 32 | } 33 | 34 | .c-tag-picker__submit { 35 | float: right; 36 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_filters.less: -------------------------------------------------------------------------------- 1 | .o-filter-list { 2 | margin: 0; 3 | padding: 0; 4 | list-style-type: none; 5 | display: flex; 6 | flex-wrap: wrap; 7 | .o-filter { margin-right: 1rem; margin-bottom: 1rem; } 8 | } 9 | 10 | .o-filter { 11 | padding: 1rem; 12 | color: @color-white; 13 | font-family: @font-family-body; 14 | font-size: @font-size-meta; 15 | background-color: @color-brand-primary; 16 | transition: @transition-ease-in-out; 17 | border-radius: none; 18 | -webkit-appearance: none; 19 | border: none; 20 | 21 | svg { 22 | max-width: 1rem; 23 | margin-left: 1rem; 24 | } 25 | 26 | &:hover { 27 | background-color: @color-light; 28 | color: @color-brand-primary; 29 | cursor: pointer; 30 | svg g{ 31 | stroke: @color-brand-primary 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /lib/modules/columns-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% set w = data.widget %} 2 | 3 | {% if w.background %} 4 | {% set bg = w.background %} 5 | {% endif %} 6 | 7 | {% if w.config %} 8 | {% set config = w.config %} 9 | {% endif %} 10 | 11 | {% if config === 'two' or config === 'two-reverse' %} 12 | {% set columns = 2 %} 13 | {% elif config === 'three' %} 14 | {% set columns = 3 %} 15 | {% else %} 16 | {% set columns = 2 %} 17 | {% endif %} 18 | 19 |
20 |
21 | {% for column in range(0, columns) %} 22 |
23 | {{ apos.area(w, 'column' + (column + 1), { 24 | widgets: apos.helpers.narrowWidgets 25 | }) }} 26 |
27 | {% endfor %} 28 |
29 |
-------------------------------------------------------------------------------- /lib/modules/people/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'person', 3 | label: 'Person', 4 | pluralLabel: 'People', 5 | addFields: [ 6 | { 7 | name: 'thumbnail', 8 | label: 'Headshot', 9 | type: 'singleton', 10 | widgetType: 'apostrophe-images', 11 | options: { 12 | aspectRatio: [ 1, 1 ], 13 | minSize: [ 300, 300 ], 14 | limit: 1 15 | } 16 | }, 17 | { 18 | name: 'role', 19 | label: 'Role', 20 | type: 'string' 21 | }, 22 | { 23 | name: 'description', 24 | label: 'Description', 25 | type: 'string', 26 | textarea: true 27 | } 28 | ], 29 | arrangeFields: [ 30 | { 31 | name: 'details', 32 | label: 'Details', 33 | fields: [ 34 | 'role', 35 | 'thumbnail', 36 | 'description' 37 | ] 38 | } 39 | ] 40 | }; 41 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_checkbox-filter.less: -------------------------------------------------------------------------------- 1 | .c-checkboxes__popup { 2 | min-width: 30rem; 3 | } 4 | .c-checkboxes { 5 | position: relative; 6 | } 7 | 8 | .c-checkboxes__main { 9 | columns: 2 auto; 10 | 11 | .o-field-container--checkbox { 12 | margin-bottom: 0.5rem; 13 | white-space: nowrap; 14 | } 15 | } 16 | 17 | .c-checkboxes__display { 18 | border: 1px solid @color-brand-primary; 19 | padding: 0.8rem; 20 | height: 4.4rem; 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | min-width: 20rem; 25 | &:hover { 26 | cursor: pointer; 27 | } 28 | } 29 | 30 | .c-checkboxes__header { 31 | display: flex; 32 | justify-content: space-between; 33 | margin-bottom: 2rem; 34 | } 35 | 36 | .c-checkboxes__all { 37 | margin-right: 1rem; 38 | } 39 | 40 | .c-checkboxes__empty { 41 | white-space: nowrap; 42 | } -------------------------------------------------------------------------------- /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 | module.exports = { 7 | modules: { 8 | 'apostrophe-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 | 'apostrophe-db': { 15 | // uri: 'mongodb://localhost:27017/apostrophe-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/modules/apostrophe-assets/public/js/utils.js: -------------------------------------------------------------------------------- 1 | window.omUtils = { 2 | parseParams: function (query) { 3 | var re = /([^&=]+)=?([^&]*)/g; 4 | var decode = function (str) { 5 | return decodeURIComponent(str.replace(/\+/g, ' ')); 6 | }; 7 | var params = {}; 8 | var e, k, v; 9 | 10 | if (query) { 11 | if (query.substr(0, 1) === '?') { 12 | query = query.substr(1); 13 | } 14 | while (true) { 15 | e = re.exec(query); 16 | if (!e) { 17 | break; 18 | } 19 | k = decode(e[1]); 20 | v = decode(e[2]); 21 | if (params[k] !== undefined) { 22 | if (!$.isArray(params[k])) { 23 | params[k] = [params[k]]; 24 | } 25 | params[k].push(v); 26 | } else { 27 | params[k] = v; 28 | } 29 | } 30 | } 31 | return params; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /lib/modules/articles-featured-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Featured Article Widget', 3 | addFields: [ 4 | { 5 | name: 'label', 6 | label: 'Meta Label', 7 | help: 'Small meta description for context of widget', 8 | type: 'string' 9 | }, 10 | { 11 | name: 'description', 12 | label: 'Short Description', 13 | type: 'string', 14 | help: 'Optional custom short description', 15 | textarea: true 16 | }, 17 | { 18 | name: '_article', 19 | label: 'Article to feature', 20 | type: 'joinByOne', 21 | withType: 'article', 22 | filters: { 23 | projection: { 24 | _url: 1, 25 | slug: 1, 26 | title: 1, 27 | type: 1, 28 | publishedAt: 1, 29 | tags: 1, 30 | thumbnail: 1 31 | } 32 | } 33 | } 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_two-panel.less: -------------------------------------------------------------------------------- 1 | .c-two-panel { 2 | display: flex; 3 | } 4 | 5 | .c-two-panel--content-left { 6 | @media @breakpoint-medium { 7 | flex-direction: column; 8 | } 9 | } 10 | 11 | .c-two-panel--content-right { 12 | flex-direction: row-reverse; 13 | @media @breakpoint-medium { 14 | flex-direction: column-reverse; 15 | } 16 | } 17 | 18 | .c-two-panel__body { 19 | padding: 12rem 8% 20 | } 21 | 22 | .c-two-panel__content { 23 | width: 50%; 24 | @media @breakpoint-medium { 25 | width: 100%; 26 | } 27 | } 28 | 29 | .c-two-panel__media { 30 | @media @breakpoint-medium { 31 | height: 40vw; 32 | } 33 | } 34 | 35 | .c-two-panel__media { 36 | .apos-area, 37 | .apos-area-widgets, 38 | .apos-area-widget-wrapper, 39 | .apos-area-widget, 40 | .o-background-image 41 | { 42 | height: 100%; 43 | width: 100%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 | if [ ! -f "app.js" ]; then 7 | echo "I don't see app.js in the current directory." 8 | exit 1 9 | fi 10 | 11 | # Stop the node app via 'forever'. You'll get a harmless warning if the app 12 | # was not already running. Use `pwd` to make sure we have a full path, 13 | # forever is otherwise easily confused and will stop every server with 14 | # the same filename 15 | forever stop `pwd`/app.js && echo "Site stopped" 16 | 17 | # Stop the app without 'forever'. We recommend using 'forever' for node apps, 18 | # but this may be your best bet for non-node apps 19 | # 20 | # if [ -f "data/pid" ]; then 21 | # kill `cat data/pid` 22 | # rm data/pid 23 | # echo "Site stopped" 24 | # else 25 | # echo "Site was not running" 26 | # fi 27 | 28 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_peer-nav.less: -------------------------------------------------------------------------------- 1 | .c-peer-nav { 2 | min-width: 20rem; 3 | @media @breakpoint-mid { 4 | margin-bottom: 7rem; 5 | } 6 | } 7 | 8 | .c-peer-nav__title { 9 | margin-bottom: 2rem; 10 | @media @breakpoint-mid { 11 | text-align: center; 12 | } 13 | } 14 | 15 | .c-peer-nav__items { 16 | @media @breakpoint-mid { 17 | display: flex; 18 | justify-content: space-evenly 19 | } 20 | } 21 | 22 | .c-peer-nav__link { 23 | display: inline-block; 24 | margin-bottom: 1.5rem; 25 | text-decoration: none; 26 | border-bottom: 1px solid black; 27 | color: black; 28 | } 29 | 30 | .c-peer-nav__link--active { 31 | font-weight: bold; 32 | color: @color-brand-primary; 33 | border-bottom: 2px solid @color-brand-primary; 34 | } 35 | 36 | .c-peer-nav ~ .c-area { 37 | max-width: ~"calc(100% - 20rem)"; 38 | @media @breakpoint-mid { 39 | max-width: 100%; 40 | } 41 | } -------------------------------------------------------------------------------- /lib/modules/artworks-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Artwork Widget', 3 | addFields: [ 4 | { 5 | name: 'title', 6 | label: 'Widget Title', 7 | type: 'string' 8 | }, 9 | { 10 | name: '_page', 11 | label: 'Page to Link', 12 | type: 'joinByOne', 13 | withType: 'apostrophe-page', 14 | filters: { 15 | projection: { 16 | // _url: 1 gives us slug, type, tags and whatever is currently 17 | // considered essential to populate _url properly 18 | _url: 1, 19 | title: 1 20 | } 21 | } 22 | } 23 | ], 24 | arrangeFields: [ 25 | { 26 | name: 'basics', 27 | label: 'Basics', 28 | fields: [ 29 | 'title', 30 | 'by', 31 | '_pieces', 32 | 'limitByAll', 33 | 'tags', 34 | 'limitByTag', 35 | '_page' 36 | ] 37 | } 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /lib/modules/default-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Default Page', 3 | name: 'default', 4 | addFields: [ 5 | { 6 | name: 'masthead', 7 | label: 'Display Masthead?', 8 | help: 'This is the purple title band that appears above page content', 9 | type: 'boolean', 10 | choices: [ 11 | { label: 'Yes', value: true }, 12 | { label: 'No', value: false } 13 | ] 14 | }, 15 | { 16 | name: 'peerNav', 17 | label: 'Display Peer Navigation?', 18 | help: 'This is the left hand navigation list', 19 | type: 'boolean', 20 | choices: [ 21 | { label: 'Yes', value: true }, 22 | { label: 'No', value: false } 23 | ] 24 | } 25 | ], 26 | arrangeFields: [ 27 | { 28 | name: 'admin', 29 | label: 'Admin', 30 | fields: [ 31 | 'masthead', 32 | 'peerNav' 33 | ] 34 | } 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_pagination.less: -------------------------------------------------------------------------------- 1 | .c-pager .apos-pager { 2 | background-color: transparent; 3 | padding: 2rem 0; 4 | } 5 | 6 | .c-pager .apos-pager-number.apos-active { 7 | font-weight: 700; 8 | color: @color-brand-primary; 9 | &:after { 10 | bottom: 0.2rem; 11 | background-color: @color-brand-primary; 12 | } 13 | } 14 | 15 | .c-pager .apos-pager-number, 16 | .c-pager .apos-pager-number a { 17 | display: inline-block; 18 | padding: 0.5rem; 19 | position: relative; 20 | text-decoration: none; 21 | display: inline-block; 22 | font-size: @font-size-lede; 23 | font-family: @font-family-body; 24 | color: @color-brand-primary; 25 | &:after { 26 | position: absolute; 27 | content: '' !important; 28 | bottom: 0rem; 29 | width: 100%; 30 | left: 0; 31 | height: 1px; 32 | background-color: transparent; 33 | transition: @transition-ease-in-out; 34 | } 35 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_location-widget.less: -------------------------------------------------------------------------------- 1 | .c-location-widget { 2 | position: relative; 3 | z-index: 10; 4 | display: flex; 5 | border: 1px solid @color-brand-primary; 6 | } 7 | 8 | .c-location-widget__addresses-items { 9 | width: 30%; 10 | padding: 4rem 5rem; 11 | border-right: 1px solid @color-brand-primary; 12 | list-style-type: upper-alpha; 13 | font-weight: strong; 14 | p { 15 | font-weight: normal; 16 | padding-left: 1rem; 17 | } 18 | } 19 | 20 | .c-location-widget__addresses-item { 21 | margin-bottom: 2rem; 22 | } 23 | 24 | .c-location-widget__map { 25 | width: 70%; 26 | height: 40rem; 27 | } 28 | 29 | a.c-location-widget__directions { 30 | display: inline-block; 31 | margin-top: 1rem; 32 | text-decoration: none; 33 | color: @color-dark; 34 | padding-bottom: 3px; 35 | &:hover { 36 | border-bottom: 1px solid @color-dark; 37 | padding-bottom: 2px; 38 | } 39 | } -------------------------------------------------------------------------------- /lib/modules/random-met-artwork-widgets/index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = { 5 | extend: 'apostrophe-widgets', 6 | label: 'Random Met Artwork', 7 | contextualOnly: true, 8 | construct: function (self, options) { 9 | self.route('post', 'get', async function (req, res) { 10 | for (let i = 0; i < 10; i++) { 11 | try { 12 | let id = _.random(0, 400000); // sort of cheating but I know the MET has objectIds in numerical order increasing by 1, saves a request 13 | let endpoint = 'https://collectionapi.metmuseum.org/public/collection/v1/objects/' + id; 14 | let data = await request(endpoint, { json: true }); 15 | if (data.primaryImage !== '') { 16 | return res.json(data); 17 | } 18 | } catch (error) { 19 | // this will happen 20 | } 21 | } 22 | }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /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 | PROJECT=apostrophe-open-museum 10 | DIR=/opt/stagecoach/apps/$PROJECT 11 | 12 | # Adjust the PATH environment variable on the remote host. Here's an example 13 | # for deploying to MacPorts 14 | #ADJUST_PATH='export PATH=/opt/local/bin:$PATH' 15 | 16 | # ... But you probably won't need to on real servers. I just find it handy for 17 | # testing parts of stagecoach locally on a Mac. : is an acceptable "no-op" (do-nothing) statement 18 | ADJUST_PATH=':' 19 | 20 | # ssh port. Sensible people leave this set to 22 but it's common to do the 21 | # "security by obscurity" thing alas 22 | SSH_PORT=22 23 | -------------------------------------------------------------------------------- /lib/modules/artworks-pages/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | module.exports = { 4 | name: 'artwork-pages', 5 | label: 'Artwork Page', 6 | addFields: [], 7 | perPage: 15, 8 | piecesFilters: [ 9 | { name: 'objectType' }, 10 | { name: 'location' }, 11 | { name: 'artist' } 12 | ], 13 | construct: function (self, options) { 14 | self.beforeShow = async function (req, callback) { 15 | // Take advantage of the reverse join from artists back to their 16 | // artworks to sample some related work 17 | const artworks = req.data.piece._artist && req.data.piece._artist._artworks; 18 | if (!artworks) { 19 | return callback(null); 20 | } 21 | const other = _.filter(artworks, function (artwork) { 22 | return !(artwork._id === req.data.piece._id); 23 | }); 24 | // Random sample 25 | req.data.related = _.sampleSize(other, 3); 26 | return callback(null); 27 | }; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /lib/modules/articles-pages/views/indexAjax.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/filters/search.html' as search with context %} 2 | {% import 'macros/filters/tag-picker.html' as tags %} 3 | {% import 'macros/filters/submit.html' as submit %} 4 | {% import 'macros/generic-cards.html' as cards %} 5 | {% import 'apostrophe-pager:macros.html' as pager %} 6 | 7 |
8 | {{ search.render('Articles') }} 9 | {{ tags.render(data.piecesFilters.tags) }} 10 | {{ submit.render() }} 11 |
12 |
13 | {% if data.pieces.length %} 14 | {{ cards.render(data.pieces, { schema: ['title', 'publishedAt', 'tags'] }) }} 15 |
16 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 17 |
18 | {% else %} 19 |
20 | No Articles match that criteria 21 |
22 | {% endif %} 23 |
24 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/index.js: -------------------------------------------------------------------------------- 1 | // This configures the apostrophe-assets module to push two stylesheets, 2 | // a number of JavaScript libraries, and jQuery 3.x rather than an 3 | // older release. See `public/css` and `public/js` of this module. 4 | 5 | module.exports = { 6 | jQuery: 3, 7 | stylesheets: [ 8 | { 9 | name: 'site' 10 | }, 11 | { 12 | name: 'vendor/_multirange' 13 | // import: { inline: true } 14 | } 15 | ], 16 | scripts: [ 17 | { name: 'vendor/multirange' }, 18 | { name: 'vendor/swiper.4.4.1' }, 19 | { name: 'utils' }, 20 | { name: 'popups/base' }, 21 | { name: 'popups/tags' }, 22 | { name: 'popups/multirange' }, 23 | { name: 'popups/checkboxes' }, 24 | { name: 'modules/map' }, 25 | { name: 'modules/bio-box' }, 26 | { name: 'modules/mobile-menu' }, 27 | { name: 'modules/logo-mask' }, 28 | { name: 'modules/pieces-pages-search' }, 29 | { name: 'modules/demo' }, 30 | { name: 'site' } 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_artwork-page.less: -------------------------------------------------------------------------------- 1 | .c-artwork-page { 2 | 3 | } 4 | 5 | .c-artwork-page__title { 6 | margin-bottom: 2rem; 7 | } 8 | 9 | .c-artwork-page__artist { 10 | margin-bottom: 4rem; 11 | } 12 | 13 | .c-artwork-page__image { 14 | margin-bottom: 6rem; 15 | 16 | img { 17 | width: 100%; 18 | } 19 | } 20 | 21 | .c-artwork-page__content, 22 | .c-artwork-page__artist { 23 | display: flex; 24 | margin-bottom: 7rem; 25 | } 26 | 27 | .c-artwork-page__artist__thumbnail img { 28 | width: 100%; 29 | } 30 | 31 | .c-artwork-page__artist__description .o-subheadline { 32 | margin-bottom: 2rem; 33 | } 34 | 35 | .c-artwork-page__artist__description .o-body { 36 | margin-bottom: 3rem; 37 | } 38 | 39 | .c-artwork-page__related { 40 | padding: 4rem 6rem; 41 | border: 1px solid @color-dark; 42 | max-width: 80rem; 43 | .c-artworks-card:last-child { 44 | margin-bottom: 0; 45 | } 46 | } 47 | 48 | .c-artwork-page__related__header { 49 | margin-bottom: 2rem; 50 | } -------------------------------------------------------------------------------- /views/macros/image.html: -------------------------------------------------------------------------------- 1 | {% macro render(imageObj, options= {size: 'full', alt: false, description: false} ) %} 2 |
3 | {% if imageObj === undefined %} 4 | {{ options.alt }} 5 | {% else %} 6 | {% set url = apos.attachments.url(imageObj, { size: options.size, crop: imageObj._crop }) %} 7 | {{ options.alt or imageObj.title or imageObj._description}} 8 | {% if options.description === true and imageObj._description %} 9 |
10 | {{ imageObj._description }} 11 |
12 | {% elif apos.helpers._isString(options.description) %} 13 |
14 | {{ options.description }} 15 |
16 | {% endif %} 17 | {% endif %} 18 |
19 | 20 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_article-featured-widget.less: -------------------------------------------------------------------------------- 1 | .c-article-featured-widget { 2 | margin-bottom: 7rem; 3 | @media @breakpoint-medium { 4 | padding: 0 2rem; 5 | } 6 | } 7 | 8 | .c-article-featured-widget__container { 9 | padding: 7rem 0; 10 | display: flex; 11 | @media @breakpoint-medium { 12 | flex-direction: column; 13 | } 14 | } 15 | 16 | .c-article-featured-widget__media { 17 | width: 48%; 18 | margin-right: 4%; 19 | @media @breakpoint-medium { 20 | width: 100%; 21 | margin-right: 0; 22 | margin-bottom: 2rem; 23 | } 24 | } 25 | 26 | .c-article-featured-widget__content { 27 | color: @color-brand-primary; 28 | width: 48%; 29 | @media @breakpoint-medium { 30 | width: 100%; 31 | } 32 | } 33 | 34 | .c-article-featured-widget__meta, 35 | .c-article-featured-widget__title, 36 | .c-article-featured-widget__description { 37 | margin-bottom: 1.3rem; 38 | } 39 | 40 | .c-article-featured-widget__tags, 41 | .c-article-featured-widget__published { 42 | margin-bottom: 2rem; 43 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_button.less: -------------------------------------------------------------------------------- 1 | .o-button { 2 | display: inline-block; 3 | padding: 2rem 3rem; 4 | border: 1px solid @color-brand-primary; 5 | text-align: center; 6 | font-size: @font-size-meta; 7 | font-family: @font-family-display; 8 | font-weight: 700; 9 | color: @color-brand-primary; 10 | background-color: @color-white; 11 | text-decoration: none; 12 | transition: @transition-ease-in-out; 13 | &:hover { box-shadow: @shadow; cursor: pointer; } 14 | &:active { box-shadow: none; } 15 | } 16 | 17 | .o-button--large { 18 | padding: 3rem 5rem; 19 | background-color: @color-brand-primary; 20 | color: @color-white; 21 | } 22 | 23 | .o-button--ghost { 24 | border-color: @color-white; 25 | color: @color-white; 26 | background-color: transparent; 27 | &:hover { background-color: fade(@color-white, 50%) } 28 | &:active { background-color: @color-white; color: @color-brand-primary; } 29 | } 30 | 31 | .o-button--ghost--alt { 32 | border-color: @color-brand-primary; 33 | color: @color-brand-primary; 34 | } -------------------------------------------------------------------------------- /lib/modules/events-pages/views/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% import 'macros/masthead.html' as masthead %} 3 | {% import 'macros/map.html' as map %} 4 | {% set piece = data.piece %} 5 | 6 | {% block main %} 7 | {{ masthead.render(data.piece, { media: true, page: data.page, type: 'event' }) }} 8 |
9 |
10 |
11 | {{ apos.area(piece, 'body', { widgets: apos.helpers.narrowWidgets }) }} 12 |
13 |
14 | {% if piece._location %} 15 | {{ map.render({ 16 | title: piece._location.title, 17 | latlng: piece._location.latlng, 18 | stateCode: piece._location.stateCode, 19 | city: piece._location.city, 20 | streetName: piece._location.streetName, 21 | zipCode: piece._location.zipCode 22 | }) }} 23 | {% endif %} 24 |
25 |
26 |
27 | {% endblock %} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, 2014, 2015, 2016, 2017, 2018 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 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-pages/views/pages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros/masthead.html" as masthead %} 3 | {% import "macros/peer-nav.html" as peerNav %} 4 | {% set page = data.page %} 5 | 6 | {% if page.peerNav !== false %} 7 | {% set wrapper = 'o-container' %} 8 | {% set widgets = apos.helpers.narrowWidgets %} 9 | {% else %} 10 | {% set wrapper = 'o-wrapper' %} 11 | {% set widgets = apos.helpers.baseWidgets %} 12 | {% endif %} 13 | 14 | {% block main %} 15 | {% if page.masthead !== false %} 16 | {{ masthead.render(page) }} 17 | {% endif %} 18 | 19 |
20 | {% if page.peerNav !== false %} 21 | {% if page._children.length %} 22 | {{ peerNav.render(page._children, page) }} 23 | {% else %} 24 | {{ peerNav.render(page._ancestors[page._ancestors.length - 1]._children, page) }} 25 | {% endif %} 26 | {% endif %} 27 |
28 | {{ apos.area(data.page, 'body', { 29 | widgets: widgets 30 | }) }} 31 |
32 |
33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /lib/modules/events/index.js: -------------------------------------------------------------------------------- 1 | const areas = require('../helpers/lib/areas.js'); 2 | 3 | module.exports = { 4 | name: 'event', 5 | label: 'Event', 6 | pluralLabel: 'Events', 7 | contextual: true, 8 | addFields: [ 9 | { 10 | name: 'thumbnail', 11 | label: 'Thumbnail', 12 | type: 'singleton', 13 | widgetType: 'apostrophe-images', 14 | options: { 15 | aspectRatio: [ 3, 2 ], 16 | limit: 1 17 | } 18 | }, 19 | { 20 | name: '_location', 21 | label: 'Location of Object', 22 | type: 'joinByOne', 23 | withType: 'location' 24 | }, 25 | { 26 | name: 'body', 27 | label: 'Body', 28 | contextual: true, 29 | type: 'area', 30 | options: { 31 | widgets: areas.narrowWidgets 32 | } 33 | } 34 | ], 35 | construct: function (self, options) { 36 | options.arrangeFields = options.arrangeFields.map(group => { 37 | if (group.name === 'basic') { 38 | group.fields = group.fields.concat(['_location', 'thumbnail']); 39 | } 40 | return group; 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/modules/random-met-artwork-widgets/public/js/always.js: -------------------------------------------------------------------------------- 1 | apos.define('random-met-artwork-widgets', { 2 | extend: 'apostrophe-widgets', 3 | construct: function (self, options) { 4 | self.play = function ($widget, data, options) { 5 | self.api('get', {}, function (data) { 6 | if (data.error) { 7 | console.error('Error finding eligible match from MET API, try again later'); 8 | $widget.find('[data-loading]').text('An error has occurred, try again later'); 9 | return; 10 | } 11 | $widget.find('[data-random-met-artwork-widget]').removeClass('c-random-met-artwork--loading').addClass('c-random-met-artwork--loaded'); 12 | var desc = ''; 13 | if (data.artistDisplayName !== '') { 14 | desc = data.artistDisplayName + ': '; 15 | } 16 | desc += data.title; 17 | $widget.find('img').attr('src', data.primaryImage).attr('alt', desc); 18 | $widget.find('figcaption').text(desc); 19 | }, function (err) { 20 | if (err) { 21 | console.log(err); 22 | } 23 | }); 24 | }; 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /lib/modules/locations-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Map', 3 | addFields: [ 4 | { 5 | name: '_locations', 6 | label: 'Locations to Display', 7 | type: 'joinByArray', 8 | withType: 'location' 9 | } 10 | ], 11 | construct: function (self, options) { 12 | // This hydrates joins and returns them to the front-end player's `data` param 13 | self.filterForDataAttribute = function (widget) { return widget; }; 14 | const superGetCreateSingletonOptions = self.getCreateSingletonOptions; 15 | self.getCreateSingletonOptions = function (req) { 16 | // We are interested in the mapquest options given to the 17 | // corresponding pieces module 18 | const locations = self.apos.modules.locations; 19 | let browserOptions = superGetCreateSingletonOptions(req); 20 | if (locations.options.mapQuest) { 21 | browserOptions.mapQuestKey = locations.options.mapQuest.key; 22 | } else { 23 | console.warn('WARNING: locations-widgets has no MapQuest key, see README'); 24 | } 25 | return browserOptions; 26 | }; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-search/views/empty.html: -------------------------------------------------------------------------------- 1 | {% extends 'apostrophe-templates:layout.html' %} 2 | {% import 'apostrophe-templates:macros/breadcrumbs.html' as breadcrumbs %} 3 | {% import 'apostrophe-templates:macros/searchbar.html' as searchbar %} 4 | 5 | {% set page = data.page %} 6 | 7 | {% block main %} 8 |
9 |
10 | {{ apos.singleton(page, 'background', 'apostrophe-images', { 11 | controls: { 12 | position: 'top-right' 13 | }, 14 | template: 'background' 15 | }) }} 16 |
17 |
18 | {{ breadcrumbs.render(data.page) }} 19 |

Search all content

20 | {{ searchbar.render() }} 21 |
22 |

Here are some useful pages you might be looking for:

23 | {{ apos.singleton(page, 'suggestedLinks', 'link', {}) }} 24 |
25 |
26 |
27 | {% endblock %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_popup.less: -------------------------------------------------------------------------------- 1 | .c-popup { 2 | 3 | } 4 | 5 | .c-popup__window { 6 | position: absolute; 7 | opacity: 0; 8 | pointer-events: none; 9 | top: 130%; 10 | z-index: 10; 11 | transition: all 0.2s ease; 12 | background-color: @color-brand-secondary; 13 | padding: 2rem; 14 | color: @color-brand-primary; 15 | box-shadow: 2px 5px 10px -3px @color-brand-primary; 16 | &:after { 17 | content: ''; 18 | position: absolute; 19 | top: -10px; 20 | left: 10px; 21 | width: 0; 22 | height: 0; 23 | border-style: solid; 24 | border-width: 0 10px 10px 10px; 25 | border-color: transparent transparent @color-brand-secondary transparent; 26 | } 27 | } 28 | 29 | [data-popup="active"] .c-popup__window { 30 | opacity: 1; 31 | pointer-events: auto; 32 | top: 110%; 33 | } 34 | 35 | .c-popup__button { 36 | font-weight: 700; 37 | color: @color-brand-primary; 38 | display: inline-block; 39 | padding-bottom: 0.25rem; 40 | border-bottom: 1px solid @color-brand-primary; 41 | transition: @transition-ease-in-out; 42 | &:hover { 43 | cursor: pointer; 44 | } 45 | } -------------------------------------------------------------------------------- /lib/modules/slideshow-widgets/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | label: 'Slideshow', 3 | addFields: [ 4 | { 5 | name: 'imageType', 6 | label: 'What type of images', 7 | type: 'select', 8 | choices: [ 9 | { label: 'Media Library', value: 'media', showFields: [ 'images' ] }, 10 | { label: 'Artwork', value: 'artworks', showFields: [ '_artworks' ] } 11 | ] 12 | }, 13 | { 14 | name: 'title', 15 | label: 'Gallery Title', 16 | type: 'string' 17 | }, 18 | { 19 | name: 'images', 20 | label: 'Images', 21 | type: 'singleton', 22 | widgetType: 'apostrophe-images', 23 | options: { 24 | aspectRatio: false 25 | } 26 | }, 27 | { 28 | name: '_artworks', 29 | label: 'Artwork', 30 | type: 'joinByArray', 31 | withType: 'apostrophe-page', 32 | filters: { 33 | projection: { 34 | // _url: 1 gets us slug, type, tags and anything else essential 35 | // to populating the _url property 36 | _url: 1, 37 | title: 1 38 | } 39 | } 40 | } 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /lib/modules/marquee-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% if data.widget.screen === undefined %} 2 | {% set screen = '0.2' %} 3 | {% else %} 4 | {% set screen = data.widget.screen %} 5 | {% endif %} 6 | 7 | {# We are interested in the options passed to the schema field #} 8 | {# containing our nested apostrophe-images widget #} 9 | 10 | {% set imageSchema = apos.helpers._find(data.manager.schema, { name: 'image' }) %} 11 | 12 |
13 |
14 |
15 | {{ apos.singleton(data.widget, 'image', 'apostrophe-images', imageSchema.options) }} 16 |
17 |
18 |
19 | {{ apos.area(data.widget, 'content', { 20 | widgets: { 21 | 'apostrophe-rich-text': { 22 | toolbar: apos.helpers.baseToolbar, 23 | styles: apos.helpers.baseStyles 24 | }, 25 | 'link': {} 26 | } 27 | }) }} 28 |
29 |
30 |
-------------------------------------------------------------------------------- /views/macros/artist-cards.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | 3 | {% macro card(artist) %} 4 | 5 |
6 | {% set artistImage = apos.images.first(artist.thumbnail) %} 7 | {{ image.render(artistImage, {alt: artist.title }) }} 8 |
9 |
10 | {{ artist.title }} 11 |
12 |
13 | {% endmacro %} 14 | 15 | {% macro cards(arr, options) %} 16 |
17 | {% for item in arr %} 18 | {{ card(item) }} 19 | {% endfor %} 20 |
21 | {% endmacro %} 22 | 23 | {% macro render(data, options) %} 24 | {% if apos.helpers._isArray(data) %} 25 | {{ cards(data, options) }} 26 | {% elif apos.helpers._isObject %} 27 | {{ card(data) }} 28 | {% else %} 29 | {{ apos.log('didnt pass object or array to artist-cards macro') }} 30 | {% endif %} 31 | {% endmacro %} 32 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_card.less: -------------------------------------------------------------------------------- 1 | .o-cards:not(.o-cards--horizontal) { 2 | display: grid; 3 | grid-template-columns: 30% 30% 30%; 4 | grid-gap: 7rem 5%; 5 | @media @breakpoint-small { 6 | grid-template-columns: 100%; 7 | grid-gap: 2rem 0; 8 | .c-generic-card { display: flex; } 9 | .c-generic-card__media { 10 | width: 46%; 11 | margin-right: 4%; 12 | } 13 | .c-generic-card__content { 14 | width: 50%; 15 | } 16 | } 17 | .o-card { 18 | margin-bottom: 4rem; 19 | } 20 | } 21 | 22 | .o-card { 23 | border: 1px solid @color-brand-primary; 24 | text-decoration: none; 25 | transition: @transition-ease-in-out; 26 | &:hover { 27 | box-shadow: @shadow 28 | } 29 | img { width: 100%; } 30 | } 31 | 32 | .o-card__info { 33 | display: block; 34 | color: @color-dark; 35 | } 36 | 37 | .o-card__content { 38 | padding: 0 1.5rem 2rem; 39 | } 40 | 41 | .o-cards--horizontal { 42 | .o-card { 43 | display: flex; 44 | margin-bottom: 4rem; 45 | border: 0; 46 | &:hover { 47 | box-shadow: none; 48 | img { outline: 1px solid @color-dark; } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /lib/modules/events-pages/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% import 'macros/masthead.html' as masthead %} 3 | {% import 'macros/generic-cards.html' as cards %} 4 | {% import 'apostrophe-pager:macros.html' as pager %} 5 | 6 | {% import 'macros/filters/search.html' as search %} 7 | {% import 'macros/filters/tag-picker.html' as tags %} 8 | {% import 'macros/filters/submit.html' as submit %} 9 | 10 | {% block main %} 11 | {{ masthead.render(data.page) }} 12 |
13 | {{ search.render('Events') }} 14 | {{ tags.render(data.piecesFilters.tags) }} 15 | {{ submit.render() }} 16 |
17 |
18 | {% if data.pieces.length %} 19 | {{ cards.render(data.pieces, { schema: ['title', 'startDate', 'tags'] }) }} 20 |
21 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 22 |
23 | {% else %} 24 |
25 | No Events match that criteria 26 |
27 | {% endif %} 28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_loading.less: -------------------------------------------------------------------------------- 1 | .c-load-bar { 2 | position: relative; 3 | width: 100%; 4 | height: 6px; 5 | background-color: #aaaaaa; 6 | } 7 | 8 | .c-load-bar__bar { 9 | content: ""; 10 | display: inline; 11 | position: absolute; 12 | left: 50%; 13 | text-align: center; 14 | width: 0; 15 | height: 100%; 16 | } 17 | 18 | .c-load-bar__bar:nth-child(1) { 19 | background-color: #333333; 20 | -webkit-animation: loading 3s linear infinite; 21 | animation: loading 3s linear infinite; 22 | } 23 | 24 | .c-load-bar__bar:nth-child(2) { 25 | background-color: #8e8e8e; 26 | -webkit-animation: loading 3s linear 1s infinite; 27 | animation: loading 3s linear 1s infinite; 28 | } 29 | 30 | .c-load-bar__bar:nth-child(3) { 31 | background-color: #aaaaaa; 32 | -webkit-animation: loading 3s linear 2s infinite; 33 | animation: loading 3s linear 2s infinite; 34 | } 35 | 36 | @keyframes loading { 37 | from { 38 | left: 50%; 39 | width: 0; 40 | z-index: 100; 41 | } 42 | 33.3333% { 43 | left: 0; 44 | width: 100%; 45 | z-index: 10; 46 | } 47 | to { 48 | left: 0; 49 | width: 100%; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/modules/artworks/lib/cursor.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | construct: function (self, options) { 3 | self.addFilter('eraMin', { 4 | def: false, 5 | launder: function (value) { 6 | return self.apos.launder.integer(value); 7 | }, 8 | safeFor: 'public', 9 | finalize: function (callback) { 10 | const eraMin = self.get('eraMin'); 11 | if (!eraMin) { 12 | return setImmediate(callback); 13 | } 14 | 15 | self.and({ 16 | year: { 17 | $gte: eraMin 18 | } 19 | }); 20 | return callback(null); 21 | } 22 | }); 23 | 24 | self.addFilter('eraMax', { 25 | def: false, 26 | launder: function (value) { 27 | return self.apos.launder.integer(value); 28 | }, 29 | safeFor: 'public', 30 | finalize: function (callback) { 31 | const eraMax = self.get('eraMax'); 32 | if (!eraMax) { 33 | return setImmediate(callback); 34 | } 35 | 36 | self.and({ 37 | year: { 38 | $lte: eraMax 39 | } 40 | }); 41 | return callback(null); 42 | } 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_search-page.less: -------------------------------------------------------------------------------- 1 | .c-search-page { 2 | position: relative; 3 | min-height: 80vh; 4 | 5 | .c-link__link, 6 | .c-breadcrumbs__link { 7 | color: @color-white; 8 | font-weight: 700; 9 | &:after { 10 | background-color: @color-white; 11 | } 12 | } 13 | & ~ .c-footer { 14 | position: relative; 15 | z-index: 2; 16 | } 17 | } 18 | 19 | .c-search-page__background { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | width: 100vw; 24 | height: 100vh; 25 | z-index: 1; 26 | background-color: @color-dark; 27 | .o-background-image { 28 | width: 100vw; 29 | height: 100vh; 30 | opacity: 0.5; 31 | } 32 | } 33 | 34 | .c-search-page__content { 35 | position: relative; 36 | z-index: 5; 37 | padding: 8rem 0; 38 | color: @color-white; 39 | } 40 | 41 | .c-search-page__title { 42 | margin: 10rem 0 4rem; 43 | } 44 | 45 | .c-search-page__suggested { 46 | margin-top: 12rem; 47 | } 48 | 49 | .c-search-page__suggested-text { 50 | margin-bottom: 2rem; 51 | } 52 | 53 | .c-search-result__header { 54 | margin-bottom: 4rem; 55 | } 56 | 57 | .c-search-result__group { 58 | margin-bottom: 10rem; 59 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_masthead.less: -------------------------------------------------------------------------------- 1 | .c-masthead { 2 | padding: 6rem 8rem 6rem; 3 | color: @color-brand-primary; 4 | margin-bottom: 7rem; 5 | 6 | @media @breakpoint-large { 7 | padding: 3rem 0rem 4rem; 8 | } 9 | 10 | .c-searchbar { 11 | margin-top: 2rem; 12 | } 13 | 14 | .o-tag-list { 15 | margin-top: 1rem; 16 | } 17 | @media @breakpoint-medium { 18 | text-align: center; 19 | padding-left: 2rem; 20 | padding-right: 2rem; 21 | .o-tag-list { 22 | justify-content: center; 23 | } 24 | } 25 | } 26 | 27 | .c-masthead--has-media { 28 | .c-masthead__inner { 29 | display: flex; 30 | align-items: center; 31 | @media @breakpoint-medium { 32 | flex-direction: column-reverse 33 | } 34 | } 35 | } 36 | 37 | .c-masthead__title { 38 | margin-bottom: 1rem; 39 | max-width: 100rem; 40 | margin-right: 10rem; 41 | @media @breakpoint-mid { 42 | max-width: 100%; 43 | margin-right: 0; 44 | } 45 | } 46 | 47 | .c-masthead__content { 48 | @media @breakpoint-mid { 49 | min-width: 45%; 50 | } 51 | } 52 | 53 | .c-masthead__media { 54 | @media @breakpoint-medium { 55 | margin-bottom: 4rem; 56 | } 57 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_artworks-cards.less: -------------------------------------------------------------------------------- 1 | .c-artworks-card__media { 2 | position: relative; 3 | height: 35rem; 4 | margin-bottom: 3rem; 5 | 6 | @media @breakpoint-medium { 7 | height: auto; 8 | } 9 | 10 | img { 11 | position: absolute; 12 | max-height: 35rem; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | right: 0; 17 | margin: auto; 18 | width: auto; 19 | @media @breakpoint-medium { 20 | position: relative; 21 | max-height: none; 22 | top: auto; 23 | right: auto; 24 | bottom: auto; 25 | left: auto; 26 | } 27 | } 28 | } 29 | 30 | .c-artist-card__content { padding: 1.5rem; } 31 | .c-artworks-card__title { margin-bottom: 0.5rem; } 32 | .c-artworks-card__artist { margin-bottom: 2rem; } 33 | 34 | .o-cards--horizontal { 35 | &:hover .c-artworks-card img { 36 | outline: none; 37 | } 38 | .c-artworks-card__media { 39 | height: auto; 40 | width: 25rem; 41 | margin-bottom: none; 42 | img { 43 | outline: none; 44 | position: relative; 45 | max-height: none; 46 | top: auto; 47 | right: auto; 48 | bottom: auto; 49 | left: auto; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_generic-card.less: -------------------------------------------------------------------------------- 1 | .c-generic-cards.o-cards--horizontal { 2 | .c-generic-card__content { 3 | padding: 1.5rem; 4 | } 5 | .c-generic-card__media { 6 | height: auto; 7 | width: 25rem; 8 | img { 9 | position: relative; 10 | max-height: none; 11 | top: auto; 12 | right: auto; 13 | bottom: auto; 14 | left: auto; 15 | } 16 | } 17 | } 18 | .c-generic-card { 19 | border: none; 20 | &:hover { 21 | box-shadow: none; 22 | } 23 | 24 | .o-card__info { 25 | display: block; 26 | margin-bottom: 1.2rem; 27 | font-family: @font-family-display; 28 | font-size: 1.4rem; 29 | &.o-meta { 30 | font-family: @font-family-body; 31 | color: @color-brand-primary; 32 | font-size: @font-size-meta; 33 | text-transform: uppercase; 34 | } 35 | } 36 | 37 | .c-generic-card__title { 38 | line-height: 140%; 39 | text-decoration: none; 40 | font-family: @font-family-body; 41 | } 42 | } 43 | 44 | .c-generic-card__media { 45 | margin-bottom: 1rem; 46 | } 47 | 48 | .c-generic-card__content { 49 | padding: 0; 50 | .o-tag-list { 51 | margin-top: 2rem; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/modules/two-panel-widgets/index.js: -------------------------------------------------------------------------------- 1 | const areas = require('../helpers/lib/areas.js'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = { 5 | label: 'Two Panel', 6 | skipInitialModal: true, 7 | addFields: [ 8 | { 9 | name: 'config', 10 | label: 'Configuration', 11 | type: 'select', 12 | def: 'c-two-panel--content-left', 13 | choices: [ 14 | { label: 'Content Left / Media Right', value: 'c-two-panel--content-left' }, 15 | { label: 'Content Right / Media Left', value: 'c-two-panel--content-right' } 16 | ] 17 | }, 18 | { 19 | name: 'image', 20 | label: 'Image', 21 | contextual: true, 22 | type: 'singleton', 23 | widgetType: 'apostrophe-images', 24 | options: { 25 | limit: 1, 26 | template: 'background' 27 | } 28 | }, 29 | { 30 | name: 'body', 31 | label: 'Body', 32 | contextual: true, 33 | type: 'area', 34 | options: { 35 | widgets: { 36 | 'apostrophe-rich-text': { 37 | toolbar: _.clone(areas.baseToolbar), 38 | styles: _.clone(areas.baseStyles) 39 | }, 40 | 'link': {} 41 | } 42 | } 43 | } 44 | ] 45 | }; 46 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | 'extends': 'standard', 3 | "rules": { 4 | 'semi': ['error', 'always' ], 5 | 'no-unused-vars': ['error', { 'varsIgnorePattern': 'apos', 'args': 'none', 'ignoreRestSiblings': true }], 6 | "import/no-extraneous-dependencies": 1, 7 | 'no-var': 'error', 8 | }, 9 | "overrides": [ 10 | { 11 | "files": [ "**/public/**/*.js" ], 12 | "globals": { 13 | "window": true, 14 | "document": true, 15 | "location": true, 16 | "apos": true, 17 | "_": true, 18 | "async": true, 19 | "confirm": true, 20 | "$": true, 21 | "CKEDITOR_BASEPATH": true, 22 | "CKEDITOR": true, 23 | "alert": true, 24 | "jQuery": true, 25 | "sluggo": true, 26 | "moog": true, 27 | "Pikaday": true, 28 | "moment": true 29 | }, 30 | "rules": { 31 | /* for bc with older browsers since this is not a Babel build */ 32 | 'no-var': 0 33 | } 34 | }, 35 | { 36 | "files": [ "test/**/*.js" ], 37 | "globals": { 38 | "describe": true, 39 | "it": true, 40 | "after": true, 41 | "before": true 42 | }, 43 | "rules": { 44 | "no-console": 0 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /views/macros/filters/tag-picker.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% macro render(tags) %} 4 |
5 | 6 | 12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |
    21 | {% for tag in tags %} 22 |
  • 23 | {% endfor %} 24 |
25 |
26 |
27 |
28 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_rich-text.less: -------------------------------------------------------------------------------- 1 | .c-rich-text { 2 | div:not([class]), 3 | p:not([class]) { 4 | font-size: @font-size-body; 5 | font-family: @font-family-body; 6 | line-height: 155%; 7 | margin-bottom: 2rem; 8 | } 9 | ul { 10 | list-style-type: circle; 11 | } 12 | ol, ul { 13 | margin-left: 2rem; 14 | } 15 | ol li, ul li { 16 | font-size: @font-size-body; 17 | font-family: @font-family-body; 18 | line-height: 175%; 19 | a { 20 | color: @color-brand-primary; 21 | } 22 | } 23 | .o-body, 24 | .o-section-header, 25 | .o-subheadline, 26 | .o-headline { 27 | margin-bottom: 2rem; 28 | } 29 | blockquote, 30 | blockquote p:not([class]) { 31 | .o-pull-quote; 32 | margin-top: 4rem; 33 | margin-bottom: 4rem; 34 | } 35 | } 36 | 37 | .c-area > .apos-area > .apos-area-widgets > [data-apos-widget-wrapper="apostrophe-rich-text"] > .apos-area-widget > .c-rich-text, 38 | .c-area > .apos-area > .apos-area-widget-wrapper > [data-apos-widget="apostrophe-rich-text"] > .c-rich-text { 39 | max-width: @layout-max-width; 40 | margin-left: auto; 41 | margin-right: auto; 42 | p, 43 | div, 44 | h1, 45 | h2, 46 | h3, 47 | h4, 48 | h5, 49 | span { 50 | max-width: 58rem; 51 | } 52 | blockquote, 53 | blockquote p { 54 | max-width: 82rem; 55 | } 56 | } -------------------------------------------------------------------------------- /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/modules/apostrophe-assets/public/css/vendor/_multirange.less: -------------------------------------------------------------------------------- 1 | // @supports (--css: variables) { 2 | input[type="range"].multirange { 3 | --range-color: #8956FF; 4 | padding: 0; 5 | margin: 0; 6 | display: inline-block; 7 | vertical-align: top; 8 | } 9 | 10 | input[type="range"].multirange.original { 11 | position: absolute; 12 | } 13 | 14 | input[type="range"].multirange.original::-webkit-slider-thumb { 15 | position: relative; 16 | z-index: 2; 17 | } 18 | 19 | input[type="range"].multirange.original::-moz-range-thumb { 20 | transform: scale(1); /* FF doesn't apply position it seems */ 21 | z-index: 1; 22 | } 23 | 24 | input[type="range"].multirange::-moz-range-track { 25 | border-color: transparent; /* needed to switch FF to "styleable" control */ 26 | } 27 | 28 | input[type="range"].multirange.ghost { 29 | position: relative; 30 | background: var(--track-background); 31 | --track-background: linear-gradient(to right, 32 | transparent var(--low), var(--range-color) 0, 33 | var(--range-color) var(--high), transparent 0 34 | ) no-repeat 0 45% / 100% 40%; 35 | --range-color: hsl(190, 80%, 40%); 36 | } 37 | 38 | input[type="range"].multirange.ghost::-webkit-slider-runnable-track { 39 | background: var(--track-background); 40 | } 41 | 42 | input[type="range"].multirange.ghost::-moz-range-track { 43 | background: var(--track-background); 44 | } -------------------------------------------------------------------------------- /lib/modules/helpers/lib/areas.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | let baseToolbar = [ 'Styles', 'Bold', 'Italic', 'Blockquote', 'Link', 'Anchor', 'Unlink', 'NumberedList', 'BulletedList', 'Split' ]; 3 | let baseStyles = [ 4 | { name: 'Body Copy (P)', element: 'p', attributes: { class: 'o-body' } }, 5 | { name: 'Section Header (H2)', element: 'h2', attributes: { class: 'o-section-header' } }, 6 | { name: 'Headline (H3)', element: 'h3', attributes: { class: 'o-headline' } }, 7 | { name: 'Sub Headline (H4)', element: 'h4', attributes: { class: 'o-subheadline' } }, 8 | { name: 'Meta (P)', element: 'p', attributes: { class: 'o-meta' } } 9 | ]; 10 | let narrowWidgets = { 11 | 'apostrophe-rich-text': { 12 | toolbar: baseToolbar, 13 | styles: baseStyles 14 | }, 15 | 'image': {}, 16 | 'slideshow': {}, 17 | 'logo-mask': {}, 18 | 'link': {}, 19 | 'columns': { 20 | controls: { 21 | position: 'bottom-left' 22 | } 23 | }, 24 | 'apostrophe-video': {}, 25 | 'artworks': {}, 26 | 'locations': {}, 27 | 'content': {}, 28 | 'articles': {}, 29 | 'events': {}, 30 | 'random-met-artwork': {} 31 | }; 32 | 33 | let wideWidgets = { 34 | 'marquee': {}, 35 | 'feature': {}, 36 | 'two-panel': {} 37 | }; 38 | 39 | module.exports = { 40 | baseToolbar: baseToolbar, 41 | baseStyles: baseStyles, 42 | baseWidgets: _.extend({}, narrowWidgets, wideWidgets), 43 | narrowWidgets: narrowWidgets 44 | }; 45 | -------------------------------------------------------------------------------- /views/demo/notification.html: -------------------------------------------------------------------------------- 1 | {% macro render() %} 2 |
3 |
4 | 5 |
6 |
7 |
You’re demoing ApostropheCMS!
8 |

9 | Welcome to an ApostropheCMS editing experience! We hope that you are finding your way around the editing experience with 10 | ease. If you would like to learn more about our commercial solutions and our B2B website builder platform, contact us today! If you are a developer and want to start your own project, 13 | here’s how to Get Started 14 | with access to Apostrophe on GitHub. 15 |

16 |
17 |
18 | Get in touch 19 |
20 |
21 |
22 | {% endmacro %} 23 | -------------------------------------------------------------------------------- /views/macros/artwork-cards.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | 3 | {% macro card(artwork) %} 4 | 5 |
6 | {% set artImage = apos.images.first(artwork.thumbnail) %} 7 | {{ image.render(artImage, {alt: artwork.title }) }} 8 |
9 |
10 | {{ artwork.title }} 11 | {{ artwork._artist.title }} 12 | {{ artwork._objectType.title }} 13 | {{ artwork.year }} 14 |
15 |
16 | {% endmacro %} 17 | 18 | {% macro cards(arr, options) %} 19 |
20 | {% for item in arr %} 21 | {{ card(item) }} 22 | {% endfor %} 23 |
24 | {% endmacro %} 25 | 26 | {% macro render(data, options) %} 27 | {% if apos.helpers._isArray(data) %} 28 | {{ cards(data, options) }} 29 | {% elif apos.helpers._isObject %} 30 | {{ card(data) }} 31 | {% else %} 32 | {{ apos.log('didnt pass object or array to artwork-cards macro') }} 33 | {% endif %} 34 | {% endmacro %} 35 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/modules/bio-box.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var $body = $('body'); 3 | 4 | $body.on('click', '[data-bio-box-trigger]', function (e) { 5 | e.preventDefault(); 6 | var $this = $(this); 7 | if ($this.attr('data-bio-box-trigger') === 'active') { 8 | $this.attr('data-bio-box-trigger', 'inactive'); 9 | clearBio($this); 10 | } else { 11 | $('[data-bio-box-trigger]').attr('data-bio-box-trigger', 'inactive'); 12 | $this.attr('data-bio-box-trigger', 'active'); 13 | populateBio($this); 14 | } 15 | }); 16 | 17 | $body.on('click', '[data-bio-box-close]', function (e) { 18 | e.preventDefault(); 19 | $('[data-bio-box]').attr('data-bio-box', 'inactive'); 20 | }); 21 | 22 | function populateBio ($el) { 23 | var $target = $el.nextAll('[data-bio-box]:first'); 24 | $('[data-bio-box]').attr('data-bio-box', 'inactive'); 25 | $target.attr('data-bio-box', 'active'); 26 | $target.find('[data-bio-box-description]').text($el.attr('data-bio')); 27 | $target.find('[data-bio-box-role]').text($el.attr('data-role')); 28 | // console.log($el.offset().left); 29 | 30 | var left = $el.position().left + ($el.outerWidth() / 2); 31 | // var left = $el.position().left 32 | 33 | $target.find('[data-bio-box-indicator]').css('left', left + 'px'); 34 | } 35 | 36 | function clearBio ($el) { 37 | $el.nextAll('[data-bio-box]:first').attr('data-bio-box', 'inactive'); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_marquee.less: -------------------------------------------------------------------------------- 1 | .c-marquee { 2 | background: @color-light 3 | } 4 | 5 | .c-marquee__inner { 6 | height: 0; 7 | padding-bottom: 55%; 8 | @media @breakpoint-small { 9 | padding-bottom: 0; 10 | height: 100%; 11 | } 12 | } 13 | 14 | .c-marquee__background { 15 | position: absolute; 16 | width: 100%; 17 | height: 100%; 18 | img { 19 | width: 100%; 20 | @media @breakpoint-small { 21 | width: auto; 22 | height: 100%; 23 | object-fit: cover; 24 | } 25 | } 26 | @media @breakpoint-small { 27 | height: 100%; 28 | width: auto; 29 | overflow: hidden; 30 | } 31 | } 32 | 33 | .c-marquee__content { 34 | display: flex; 35 | flex-direction: column; 36 | position: absolute; 37 | width: 100%; 38 | left: 0; 39 | right: 0; 40 | top: 0; 41 | bottom: 0; 42 | padding: 0 9%; 43 | justify-content: center; 44 | @media @breakpoint-small { 45 | position: relative; 46 | padding: 4rem 2rem; 47 | text-align: center; 48 | .o-button, .c-link__item { 49 | display: block; 50 | width: 100%; 51 | margin-right: 0; 52 | } 53 | } 54 | } 55 | 56 | 57 | .c-marquee__content .c-rich-text { 58 | color: @color-white; 59 | margin-bottom: 2rem; 60 | p, div:not([class]) { 61 | font-size: 2.4rem; 62 | } 63 | } 64 | 65 | .c-marquee--feature .c-marquee__inner { 66 | padding-bottom: 25%; 67 | @media @breakpoint-small { 68 | padding-bottom: 0; 69 | } 70 | } -------------------------------------------------------------------------------- /lib/modules/link-widgets/lib/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | name: 'linkText', 4 | label: 'Link Text', 5 | type: 'string', 6 | required: true 7 | }, 8 | { 9 | name: 'linkType', 10 | label: 'Link Type', 11 | type: 'select', 12 | required: true, 13 | choices: [{ 14 | label: 'Page', 15 | value: 'page', 16 | showFields: [ 17 | '_linkPage' 18 | ] 19 | }, 20 | { 21 | label: 'Custom', 22 | value: 'custom', 23 | showFields: [ 24 | 'linkUrl' 25 | ] 26 | } 27 | ] 28 | }, 29 | { 30 | name: '_linkPage', 31 | label: 'Link Page', 32 | type: 'joinByOne', 33 | withType: 'apostrophe-page', 34 | idField: 'pageId', 35 | required: true, 36 | filters: { 37 | projection: { 38 | title: 1, 39 | _url: 1 40 | } 41 | } 42 | }, 43 | { 44 | name: 'linkUrl', 45 | label: 'Link URL', 46 | type: 'url', 47 | required: true 48 | }, 49 | { 50 | name: 'linkStyle', 51 | label: 'Link Style', 52 | type: 'select', 53 | choices: [ 54 | { label: 'Underlined', value: 'o-link' }, 55 | { label: 'Button (Normal)', value: 'o-button' }, 56 | { label: 'Button (Large)', value: 'o-button o-button--large' }, 57 | { label: 'Button (Ghost)', value: 'o-button o-button--ghost' } 58 | ] 59 | }, 60 | { 61 | name: 'linkTarget', 62 | label: 'Will the link open a new browser tab?', 63 | type: 'boolean' 64 | } 65 | ]; 66 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/modules/map.js: -------------------------------------------------------------------------------- 1 | // Loads up maps that just waiting on the page, not ones powered by widgets 2 | 3 | /* global L */ 4 | 5 | $(function () { 6 | var $maps = $('[data-map]'); 7 | 8 | if ($maps.length > 0) { 9 | addResources(); 10 | } 11 | 12 | function init ($map) { 13 | var key = $map.attr('data-mq-key'); 14 | 15 | if (key === '') { 16 | console.warn('DEVELOPER: No MapQuest key/secret set, see the repo\'s README'); 17 | return; 18 | } 19 | 20 | var latlng = JSON.parse($map.attr('data-map')); 21 | 22 | L.mapquest.key = key; 23 | var map = L.mapquest.map($map[0], { 24 | layers: L.mapquest.tileLayer('map'), 25 | center: latlng, 26 | zoom: 13 27 | }); 28 | 29 | L.marker(latlng, { 30 | icon: L.mapquest.icons.marker(), 31 | draggable: false 32 | }).addTo(map); 33 | } 34 | 35 | function addResources () { 36 | var s = document.createElement('script'); 37 | s.setAttribute('src', 'https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js'); 38 | s.onload = function () { 39 | $maps.each(function () { 40 | init($(this)); 41 | }); 42 | }; 43 | 44 | document.body.appendChild(s); 45 | 46 | var l = document.createElement('link'); 47 | l.setAttribute('type', 'text/css'); 48 | l.setAttribute('rel', 'stylesheet'); 49 | l.setAttribute('href', 'https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css'); 50 | 51 | document.body.appendChild(l); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /lib/modules/artists-pages/views/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% set piece = data.piece %} 3 | {% import 'apostrophe-templates:macros/breadcrumbs.html' as breadcrumbs %} 4 | {% import 'macros/artwork-cards.html' as cards %} 5 | {% import 'macros/image.html' as image %} 6 | {% import 'macros/definitionList.html' as dl %} 7 | 8 | {% block main %} 9 |
10 | {{ breadcrumbs.render(data.page) }} 11 |

12 | {{ piece.title }} 13 |

14 |
15 | {% set thumbnail = apos.images.first(piece.thumbnail) %} 16 | {{ image.render(thumbnail, { description: true }) }} 17 |
18 |
19 |
20 |
21 | {{ dl.render(piece, [ 22 | { name: 'lifetime' }, 23 | { name: 'nationality' }, 24 | { name: 'movement' } 25 | ]) }} 26 |
27 |
28 |
29 | {{ apos.area(piece, 'body', { widgets: apos.helpers.narrowWidgets }) }} 30 |
31 |
32 |
33 | {{ apos.area(piece, 'extra', { widgets: apos.helpers.baseWidgets }) }} 34 |
35 |
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /lib/modules/marquee-widgets/index.js: -------------------------------------------------------------------------------- 1 | const areas = require('../helpers/lib/areas.js'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = { 5 | label: 'Marquee', 6 | skipInitialModal: true, 7 | addFields: [ 8 | { 9 | contextual: true, 10 | name: 'image', 11 | label: 'Banner Image', 12 | type: 'singleton', 13 | widgetType: 'apostrophe-images', 14 | controls: { 15 | position: 'top-right' 16 | }, 17 | options: { 18 | aspectRatio: [ 1, 0.55 ], 19 | minSize: [ 1400, 770 ], 20 | limit: 1, 21 | template: 'single' 22 | } 23 | }, 24 | { 25 | contextual: true, 26 | name: 'content', 27 | label: 'Content', 28 | type: 'area', 29 | options: { 30 | widgets: { 31 | 'apostrophe-rich-text': { 32 | toolbar: _.clone(areas.baseToolbar), 33 | styles: _.clone(areas.baseStyles) 34 | }, 35 | 'link': {} 36 | } 37 | } 38 | }, 39 | { 40 | name: 'screen', 41 | label: 'Screen Overlay Transparency', 42 | help: 'This darkens or lightens over the image, creating more or less contrast for content', 43 | type: 'select', 44 | choices: [ 45 | { label: '0% (Fully Transparent)', value: '0' }, 46 | { label: '20%', value: '0.2' }, 47 | { label: '40%', value: '0.4' }, 48 | { label: '60%', value: '0.6' }, 49 | { label: '80%', value: '0.8' }, 50 | { label: '100% (Fully opaque)', value: '1' } 51 | ] 52 | } 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /lib/modules/artworks-pages/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% import 'macros/masthead.html' as masthead %} 3 | {% import 'macros/artwork-cards.html' as cards %} 4 | {% import 'apostrophe-pager:macros.html' as pager %} 5 | 6 | {% import 'macros/filters/search.html' as search %} 7 | {% import 'macros/filters/tag-picker.html' as tags %} 8 | {% import 'macros/filters/submit.html' as submit %} 9 | {% import 'macros/filters/multirange.html' as multirange %} 10 | {% import 'macros/filters/checkboxes.html' as checkboxes %} 11 | 12 | {% block main %} 13 | {{ masthead.render(data.page) }} 14 |
15 | {{ search.render('Artwork') }} 16 | {{ multirange.render({ 17 | min: { 18 | value: 1400, 19 | label: '1400 CE' 20 | }, 21 | max: { 22 | value: 2018, 23 | label: '2018 CE' 24 | } 25 | })}} 26 | {{ checkboxes.render('Object Type', 'objectType', data.piecesFilters.objectType) }} 27 | {{ checkboxes.render('Location', 'location', data.piecesFilters.location) }} 28 | {{ submit.render() }} 29 |
30 |
31 | {% if data.pieces.length %} 32 | {{ cards.render(data.pieces) }} 33 |
34 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 35 |
36 | {% else %} 37 |
38 | No artworks match that criteria 39 |
40 | {% endif %} 41 |
42 | {% endblock %} -------------------------------------------------------------------------------- /lib/modules/articles-featured-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | {% import 'macros/button.html' as button %} 3 | 4 | {% set w = data.widget %} 5 |
6 |
7 |
8 | 9 | {% set thumbnail = apos.images.first(w._article.thumbnail) %} 10 | {{ image.render(thumbnail) }} 11 | 12 |
13 |
14 | {% if w.label %} 15 |

{{ w.label }}

16 | {% endif %} 17 |

{{ w._article.title }}

18 |
Published on {{ w._article.publishedAt | date('MMMM Do YYYY') }}
19 |
    20 | {% for tag in w._article.tags %} 21 |
  • {{ tag }}
  • 22 | {% endfor %} 23 |
24 | {% if w.description %} 25 |

{{ w.description }}

26 | {% endif %} 27 | {{ button.render('Read the Article', w._article._url, { class:'o-button--ghost o-button--ghost--alt' }) }} 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/popups/base.js: -------------------------------------------------------------------------------- 1 | // This will provide the basic open/close actions for the various popup filter interfaces across the site 2 | // Each type of popup will have it's own interaction player responsible for how that particular popup behaves 3 | 4 | $(function () { 5 | var $body = $('body'); 6 | 7 | // Event handling 8 | $body.on('click', '[data-popup-open]', function (e) { 9 | e.preventDefault(); 10 | e.stopPropagation(); 11 | var $popup = $(this).parents('[data-popup]'); 12 | 13 | // If already open and we click the trigger again, close it 14 | if ($popup.attr('data-popup') === 'active') { 15 | closePopup($popup); 16 | } else { 17 | openPopup($popup); 18 | } 19 | }); 20 | 21 | // Actions 22 | function openPopup ($popup) { 23 | var $window = $(window); 24 | var $popups = $('[data-popup]'); 25 | 26 | // Close open popups before opening this one 27 | $popups.each(function () { 28 | closePopup($(this)); 29 | }); 30 | 31 | $popup.attr('data-popup', 'active'); 32 | 33 | $popup.find('[data-popup-window]').on('click', function (e) { 34 | e.stopPropagation(); 35 | }); 36 | 37 | // have to bind this after stopping bubbling 38 | $popup.find('[data-popup-close]').on('click', function (e) { 39 | e.preventDefault(); 40 | e.stopPropagation(); 41 | closePopup($popup); 42 | }); 43 | 44 | $window.one('click', function () { 45 | closePopup($popup); 46 | }); 47 | } 48 | 49 | function closePopup ($popup) { 50 | $popup.attr('data-popup', 'inactive'); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /views/demo/modal.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% macro render() %} 4 |
5 |
6 |

Welcome to the ApostropheCMS Demo!

7 | 14 | {# js will toggle this visible if needed, otherwise don't load it #} 15 | Adding content to ApostropheCMS 16 | 17 |
18 |
19 | {{ svgs.arrowUp() }} 20 |
21 |
22 | {{ svgs.arrowDown() }} 23 |
24 |
25 | {% endmacro %} 26 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_type.less: -------------------------------------------------------------------------------- 1 | .o-title, 2 | .o-section-header, 3 | .o-headline, 4 | .o-subheadline, 5 | .o-meta, 6 | .o-pull-quote { 7 | font-family: @font-family-display; 8 | } 9 | 10 | .o-title, 11 | .o-section-header, 12 | .o-headline, 13 | .o-subheadline, 14 | .o-lede, 15 | .o-meta, 16 | .o-pull-quote { 17 | line-height: 125%; 18 | a { 19 | text-decoration: none; 20 | color: currentColor; 21 | } 22 | } 23 | 24 | .o-title, 25 | .o-section-header, 26 | .o-headline, 27 | .o-subheadline, 28 | .o-pull-quote { 29 | font-weight: 700 30 | } 31 | 32 | .o-title { 33 | font-size: @font-size-title; 34 | } 35 | 36 | .o-section-header, 37 | .o-pull-quote { 38 | font-size: @font-size-section-header; 39 | } 40 | 41 | .o-pull-quote { 42 | color: @color-brand-primary; 43 | } 44 | 45 | .o-headline { 46 | font-size: @font-size-headline; 47 | } 48 | 49 | .o-subheadline { 50 | font-size: @font-size-subheadline; 51 | } 52 | 53 | .o-subheadline--alt { 54 | font-weight: 400; 55 | } 56 | 57 | .o-lede { 58 | font-weight: 500; 59 | text-transform: uppercase; 60 | font-family: @font-family-body; 61 | font-size: @font-size-lede; 62 | } 63 | 64 | .o-body { 65 | font-size: @font-size-body; 66 | font-family: @font-family-body; 67 | line-height: 175%; 68 | a { 69 | color: @color-brand-primary 70 | } 71 | } 72 | 73 | .o-body--small { 74 | font-size: @font-size-body--small; 75 | } 76 | 77 | .o-meta { 78 | font-size: @font-size-meta 79 | } 80 | 81 | .o-meta--alt { 82 | text-transform: uppercase; 83 | font-family: @font-family-body; 84 | font-weight: 400; 85 | letter-spacing: 1px; 86 | } -------------------------------------------------------------------------------- /lib/modules/columns-widgets/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {baseWidgets} = require('../helpers/lib/areas.js'); 3 | 4 | const columnWidgets = _.cloneDeep(baseWidgets); 5 | delete columnWidgets.columns; 6 | 7 | module.exports = { 8 | label: 'Columns', 9 | skipInitialModal: true, 10 | addFields: [ 11 | { 12 | name: 'config', 13 | label: 'Column Configuration', 14 | type: 'select', 15 | def: 'two', 16 | choices: [ 17 | { label: 'Three Columns (3 / 3 / 2)', value: 'three' }, 18 | { label: 'Two Columns (5 / 3)', value: 'two' }, 19 | { label: 'Two Columns (3 / 5)', value: 'two-reverse' } 20 | ] 21 | }, 22 | { 23 | name: 'background', 24 | label: 'Background', 25 | type: 'select', 26 | def: 'none', 27 | choices: [ 28 | { label: 'None', value: 'none' }, 29 | { label: 'Cream', value: 'o-background-brand-secondary' }, 30 | { label: 'Light Purple', value: 'o-background-light' } 31 | ] 32 | }, 33 | { 34 | name: 'column1', 35 | label: 'Column One', 36 | contextual: true, 37 | type: 'area', 38 | options: { 39 | widgets: columnWidgets 40 | } 41 | }, 42 | { 43 | name: 'column2', 44 | label: 'Column Two', 45 | contextual: true, 46 | type: 'area', 47 | options: { 48 | widgets: columnWidgets 49 | } 50 | }, 51 | { 52 | name: 'column3', 53 | label: 'Column Three', 54 | contextual: true, 55 | type: 'area', 56 | options: { 57 | widgets: columnWidgets 58 | } 59 | } 60 | ] 61 | }; 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apostrophe-open-museum", 3 | "version": "2.0.0", 4 | "description": "Minimal Apostrophe Boilerplate", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/apostrophecms/apostrophe-open-museum.git" 12 | }, 13 | "author": "Apostrophe Technologies, Inc.", 14 | "license": "MIT", 15 | "dependencies": { 16 | "apostrophe": "^2.108.1", 17 | "apostrophe-blog": "^2.1.2", 18 | "apostrophe-events": "^2.1.7", 19 | "apostrophe-favicons": "^2.0.5", 20 | "apostrophe-open-graph": "^1.1.6", 21 | "apostrophe-pieces-import": "^2.1.5", 22 | "apostrophe-seo": "^1.3.0", 23 | "eslint": "^4.17.0", 24 | "eslint-config-punkave": "^1.0.8", 25 | "eslint-config-standard": "^10.2.1", 26 | "eslint-loader": "^2.0.0", 27 | "eslint-plugin-import": "^2.22.0", 28 | "eslint-plugin-node": "^5.1.0", 29 | "eslint-plugin-promise": "^3.5.0", 30 | "eslint-plugin-react": "^7.20.3", 31 | "eslint-plugin-standard": "^3.0.1", 32 | "lodash": "^4.17.11", 33 | "lorem-ipsum": "^1.0.6", 34 | "node-geocoder": "^3.25.0", 35 | "request-promise": "^4.2.5", 36 | "typy": "^2.0.1" 37 | }, 38 | "nodemonConfig": { 39 | "verbose": true, 40 | "ignore": [ 41 | "lib/modules/*/public/js/*.js", 42 | "locales/*.json", 43 | "public/modules/**/*.less", 44 | "public/modules/**/*.js", 45 | "public/uploads", 46 | "public/apos-minified/*.js", 47 | "public/css/master-*.less", 48 | "data" 49 | ], 50 | "ext": "json, js, html, less" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-global/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | addFields: [ 3 | { 4 | name: 'logoImages', 5 | label: 'Masked Logo Images', 6 | help: 'On page load, choose one randomly and mask it with the logomark', 7 | type: 'singleton', 8 | widgetType: 'apostrophe-images' 9 | }, 10 | { 11 | name: 'demoMode', 12 | label: 'Enable Demo Mode?', 13 | help: 'This will turn on introductory modal windows when a user first arrives, introducing them to ApostrpoheCMS', 14 | type: 'boolean', 15 | choices: [ 16 | { label: 'No', value: false }, 17 | { label: 'Yes', value: true } 18 | ] 19 | }, 20 | { 21 | name: 'trackingID', 22 | label: 'Google Analytics Tracking ID', 23 | help: 'If present, traffic will be tracked to this Google Analytics property.', 24 | type: 'string' 25 | } 26 | ], 27 | arrangeFields: [ 28 | { 29 | name: 'navigation', 30 | label: 'Navigation', 31 | fields: [ 32 | 'logoImages' 33 | ] 34 | }, 35 | { 36 | name: 'admin', 37 | label: 'Admin', 38 | fields: [ 39 | 'demoMode', 40 | 'trackingID' 41 | ] 42 | } 43 | ], 44 | construct: function (self, options) { 45 | self.prependSnippets = () => { 46 | self.apos.templates.prepend('body', (req) => { 47 | return self.partial('body-snippet', {}); 48 | }); 49 | self.apos.templates.append('head', (req) => { 50 | return self.partial('head-snippet', {}); 51 | }); 52 | }; 53 | }, 54 | afterConstruct: (self) => { 55 | self.prependSnippets(); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-pages/index.js: -------------------------------------------------------------------------------- 1 | // This configures the apostrophe-pages module to add a "home" page type to the 2 | // pages menu 3 | 4 | module.exports = { 5 | filters: { 6 | // Grab our ancestor pages, with two levels of subpages 7 | ancestors: { 8 | children: { 9 | depth: 2 10 | } 11 | }, 12 | // We usually want children of the current page, too 13 | children: true 14 | }, 15 | park: [ 16 | { 17 | title: 'Search', 18 | slug: '/search', 19 | type: 'apostrophe-search', 20 | label: 'Search', 21 | published: true 22 | } 23 | ], 24 | types: [ 25 | { 26 | name: 'home', 27 | label: 'Home' 28 | }, 29 | { 30 | name: 'default', 31 | label: 'Default' 32 | }, 33 | { 34 | name: 'styleguide', 35 | label: 'Styleguide' 36 | }, 37 | { 38 | label: 'Artworks Index', 39 | name: 'artwork-pages' 40 | }, 41 | { 42 | label: 'Artist Index', 43 | name: 'artist-pages' 44 | }, 45 | { 46 | label: 'People Index', 47 | name: 'people-pages' 48 | }, 49 | { 50 | label: 'Location Index', 51 | name: 'location-pages' 52 | }, 53 | { 54 | label: 'Article Index', 55 | name: 'article-pages' 56 | }, 57 | { 58 | label: 'Event Index', 59 | name: 'event-pages' 60 | } 61 | 62 | // Add more page types here, but make sure you create a corresponding 63 | // template in lib/modules/apostrophe-pages/views/pages! An exception: 64 | // "pieces pages" like `event-pages` are rendered via the `views/index.html` 65 | // and `views/show.html` templates of those modules. 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /views/macros/filters/multirange.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% macro render(options) %} 4 |
5 | 6 |
7 |
8 | 9 | {{ svgs.chevronDown() }} 10 |
11 |
12 |
13 | 14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 |
24 | {{ options.min.label }} 25 | {{ options.max.label }} 26 |
27 |
28 |
29 |
30 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_footer.less: -------------------------------------------------------------------------------- 1 | .c-footer { 2 | margin-top: 12rem; 3 | padding: 5rem 0 10rem; 4 | a { color: @color-brand-primary; } 5 | 6 | .c-navigation__items { 7 | @media @breakpoint-large { 8 | margin-bottom: 4rem; 9 | } 10 | @media @breakpoint-medium { 11 | flex-direction: column; 12 | align-items: center; 13 | } 14 | } 15 | @media @breakpoint-medium { 16 | margin-top: 4rem; 17 | .c-footer__inner, 18 | .c-footer__nav { 19 | flex-direction: column; 20 | } 21 | 22 | .c-navigation__logo { 23 | margin-right: 0 !important; 24 | margin-bottom: 4rem; 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | } 29 | } 30 | } 31 | 32 | .c-footer__inner { 33 | display: flex; 34 | justify-content: space-between; 35 | @media @breakpoint-large { 36 | flex-direction: column; 37 | align-items: center; 38 | } 39 | } 40 | 41 | .c-footer .c-navigation__logo { 42 | margin-right: 4rem; 43 | } 44 | 45 | .c-footer__nav { 46 | display: flex; 47 | } 48 | 49 | .c-footer__credit { 50 | text-align: right; 51 | @media @breakpoint-large { 52 | text-align: center; 53 | } 54 | } 55 | 56 | .c-footer__logo { 57 | display: block; 58 | .logo-solid { 59 | transition: all 4s ease; 60 | } 61 | svg:hover { 62 | .logo-solid { 63 | opacity: 0; 64 | } 65 | } 66 | } 67 | 68 | .c-footer__apostrophe-link { 69 | background-image: @color-brand-gradient; 70 | -webkit-background-clip: text; 71 | -webkit-text-fill-color: transparent; 72 | &:hover { 73 | -webkit-text-fill-color: white; 74 | -webkit-background-clip: unset; 75 | text-decoration: none; 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /views/macros/masthead.html: -------------------------------------------------------------------------------- 1 | {% import "apostrophe-templates:macros/breadcrumbs.html" as breadcrumbs %} 2 | {% import "apostrophe-templates:macros/image.html" as image %} 3 | {% import "apostrophe-templates:macros/tags.html" as tags %} 4 | {% import "apostrophe-templates:macros/searchbar.html" as searchbar %} 5 | 6 | {% macro render(page, options={}) %} 7 |
8 |
9 |
10 | {% if options.page %} 11 | {{ breadcrumbs.render(options.page) }} 12 | {% else %} 13 | {{ breadcrumbs.render(page) }} 14 | {% endif %} 15 |

{{ options.title or page.title }}

16 | {% if options.type === 'event' %} 17 |

18 | {{ page.startDate | date('MMMM Do YYYY') }} 19 |

20 |

21 | {{ page._location.title }} 22 |

23 | {% endif %} 24 | {% if options.type === 'event' or options.type === 'article' %} 25 | {{ tags.render(page.tags) }} 26 | {% endif %} 27 | {% if options.search %} 28 | {{ searchbar.render(options.search) }} 29 | {% endif %} 30 |
31 | {% if options.media %} 32 |
33 | {% set thumbnail = apos.images.first(page.thumbnail) %} 34 | {{ image.render(thumbnail, {alt: page.title}) }} 35 |
36 | {% endif %} 37 |
38 |
39 | {% endmacro %} 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | HERE=`pwd` 12 | mkdir -p ../../data 13 | ln -s ../../data $HERE/data 14 | 15 | # We also have a shared uploads folder which is convenient to keep 16 | # in a separate place so we don't have to have two express.static calls 17 | 18 | mkdir -p ../../uploads 19 | ln -s ../../../uploads $HERE/public/uploads 20 | 21 | # Install any dependencies that can't just be rsynced over with 22 | # the deployment. Example: node apps have npm modules in a 23 | # node_modules folder. These may contain compiled C++ code that 24 | # won't work portably from one server to another. 25 | 26 | # This script runs after the rsync, but before the 'stop' script, 27 | # so your app is not down during the npm installation. 28 | 29 | # Make sure node_modules exists so npm doesn't go searching 30 | # up the filesystem tree 31 | mkdir -p node_modules 32 | 33 | # If there is no package.json file then we don't need npm install 34 | if [ -f './package.json' ]; then 35 | # Install npm modules 36 | # Use a suitable version of Python 37 | # export PYTHON=/usr/bin/python26 38 | npm install 39 | if [ $? -ne 0 ]; then 40 | echo "Error during npm install!" 41 | exit 1 42 | fi 43 | fi 44 | 45 | node app apostrophe-migrations:migrate --safe 46 | # Generate new static asset files for this 47 | # deployment of the app without shutting down 48 | node app apostrophe:generation 49 | -------------------------------------------------------------------------------- /lib/modules/styleguide/public/css/styleguide.less: -------------------------------------------------------------------------------- 1 | *[class^="sg-"] { 2 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; 3 | } 4 | .sg-body { 5 | padding: 100px 50px 50px; 6 | width: 80%; 7 | } 8 | .sg-nav { 9 | position: fixed; 10 | right: 0; 11 | top: 120px; 12 | width: 20%; 13 | height: 100%; 14 | padding: 20px; 15 | } 16 | 17 | .sg-nav-link { 18 | display: block; 19 | padding: 10px; 20 | border-radius: 2px; 21 | margin-bottom: 10px; 22 | &:hover { 23 | background-color: #e6f2f3 24 | } 25 | } 26 | 27 | .sg-list { 28 | list-style-type: none; 29 | margin: 0; 30 | padding: 0; 31 | li { 32 | margin-bottom: 20px; 33 | } 34 | } 35 | 36 | .sg-h1 { 37 | margin-bottom: 30px; 38 | border-bottom: 1px solid #efefef; 39 | padding-bottom: 30px; 40 | } 41 | 42 | .sg-h2 { 43 | border-bottom: 1px solid #efefef; 44 | padding-bottom: 15px; 45 | margin-bottom: 15px; 46 | } 47 | 48 | .sg-meta { 49 | color: #9a9a9a; 50 | font-size: 12px; 51 | text-transform: uppercase; 52 | margin-bottom: 10px; 53 | } 54 | 55 | .sg-section { 56 | border: 1px solid #efefef; 57 | border-radius: 4px; 58 | padding: 10px 20px; 59 | margin-bottom: 50px; 60 | } 61 | .sg-color-items { 62 | display: flex; 63 | flex-wrap: wrap; 64 | } 65 | 66 | .sg-color-item { 67 | margin-right: 20px; 68 | margin-bottom: 20px; 69 | display: flex; 70 | flex-direction: column; 71 | align-items: center; 72 | justify-content: center; 73 | text-align: center; 74 | } 75 | 76 | .sg-color-swatch { 77 | height: 100px; 78 | width: 100px; 79 | border-radius: 50%; 80 | margin-bottom: 20px; 81 | } 82 | 83 | .sg-color-label { 84 | font-size: 11px; 85 | } 86 | 87 | .sg-type-item { 88 | margin-bottom: 20px; 89 | } -------------------------------------------------------------------------------- /lib/modules/slideshow-widgets/views/widget.html: -------------------------------------------------------------------------------- 1 | {% import 'apostrophe-templates:macros/image.html' as img %} 2 | {% import 'apostrophe-templates:macros/svgs.html' as svgs %} 3 | 4 | {% set w = data.widget %} 5 | {% set images = apos.images.all(w.images) %} 6 |
7 |
8 | {{ w.title }} 9 |
10 | 19 | 26 |
27 |
28 |
29 |
30 | {% for image in images %} 31 |
32 | {{ img.render(image) }} 33 |
34 | {% endfor %} 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /lib/modules/helpers/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const typy = require('typy'); 3 | const areas = require('./lib/areas.js'); 4 | 5 | module.exports = { 6 | alias: 'helpers', 7 | label: 'Helpers', 8 | construct: function (self, options) { 9 | // Have to wait for all modules to be initialized so we can reach into `locations` options 10 | self.modulesReady = () => { 11 | const locationModule = self.apos.modules.locations; 12 | let mq = locationModule.options.mapQuest; 13 | 14 | self.addHelpers({ 15 | getMapquestKey: () => { 16 | if (mq) { 17 | return mq.key; 18 | } else { 19 | 20 | } 21 | } 22 | }); 23 | }; 24 | 25 | self.addHelpers({ 26 | 27 | _find: (haystack, needle) => _.find(haystack, needle), 28 | 29 | _isObject: (data) => _.isObject(data), 30 | 31 | _isArray: (data) => _.isArray(data), 32 | 33 | _isString: (data) => _.isString(data), 34 | 35 | _shuffle: (array) => _.shuffle(array), 36 | 37 | getPaths: (array) => { 38 | let paths = []; 39 | array.forEach(function (imageObj) { 40 | paths.push(self.apos.attachments.url(imageObj.item.attachment, {size: 'full', crop: imageObj.relationship})); 41 | }); 42 | return paths; 43 | }, 44 | 45 | split: (str, sep) => str.split(sep), 46 | 47 | typy: (obj, path) => typy(obj, path).safeString, 48 | 49 | isJoin: (str) => { 50 | if (str.charAt(0) === '_') { 51 | return true; 52 | } else { 53 | return false; 54 | } 55 | }, 56 | 57 | areas: _.clone(areas.baseToolbar), 58 | baseStyles: _.clone(areas.baseStyles), 59 | baseWidgets: _.clone(areas.baseWidgets), 60 | baseToolbar: _.clone(areas.baseToolbar), 61 | narrowWidgets: _.clone(areas.narrowWidgets) 62 | }); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /lib/modules/locations-widgets/public/js/always.js: -------------------------------------------------------------------------------- 1 | /* global L */ 2 | 3 | apos.define('locations-widgets', { 4 | extend: 'apostrophe-widgets', 5 | construct: function (self, options) { 6 | self.play = function ($widget, data, options) { 7 | var points = []; 8 | 9 | if (self.options.mapQuestKey && (!window.L)) { 10 | addResources(); 11 | } else { 12 | console.warn('DEVELOPER: No MapQuest key/secret set, see the repo\'s README'); 13 | } 14 | 15 | function init () { 16 | L.mapquest.key = self.options.mapQuestKey; 17 | var map = L.mapquest.map($widget.find('[data-location-widget]')[0], { 18 | layers: L.mapquest.tileLayer('map'), 19 | center: [0, 0], 20 | zoom: 6 21 | }); 22 | 23 | data._locations.forEach(function (loc) { 24 | points.push(loc.latlng); 25 | addPin(loc, map); 26 | }); 27 | 28 | var bounds = new L.LatLngBounds(points); 29 | map.fitBounds(bounds, { padding: [50, 50] }); 30 | } 31 | 32 | function addPin (piece, map) { 33 | L.marker(piece.latlng, { 34 | icon: L.mapquest.icons.marker(), 35 | draggable: false 36 | }).bindPopup(piece.title).addTo(map); 37 | } 38 | 39 | function addResources () { 40 | var s = document.createElement('script'); 41 | s.setAttribute('src', 'https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js'); 42 | s.onload = function () { 43 | init(); 44 | }; 45 | 46 | document.body.appendChild(s); 47 | 48 | var l = document.createElement('link'); 49 | l.setAttribute('type', 'text/css'); 50 | l.setAttribute('rel', 'stylesheet'); 51 | l.setAttribute('href', 'https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css'); 52 | document.body.appendChild(l); 53 | } 54 | }; 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/popups/multirange.js: -------------------------------------------------------------------------------- 1 | /* global omUtils */ 2 | 3 | $(function () { 4 | var $mr = $('[data-multirange]'); 5 | 6 | if ($mr.length > 0 && omUtils.parseParams(window.location.search).eraMin) { 7 | var min = omUtils.parseParams(window.location.search).eraMin; 8 | var max = omUtils.parseParams(window.location.search).eraMax; 9 | populateGhostInputs([min, max]); 10 | populateDisplay([min, max]); 11 | updateRange([min, max]); 12 | } else { 13 | populateDisplay(); 14 | } 15 | 16 | // Events 17 | $mr.find('[data-multirange-input]').on('input change', function () { 18 | var $this = $mr.find('[data-multirange-input]:not(.ghost)'); 19 | populateDisplay($this.val().split(',')); 20 | populateGhostInputs($mr.find('[data-multirange-input]').val().split(',')); 21 | }); 22 | 23 | $mr.find('[data-multirange-all]').on('click', function (e) { 24 | e.preventDefault(); 25 | resetRange(); 26 | }); 27 | 28 | // Actions 29 | // We don't actually use the multirange form element, instead populate 2 hidden inputs 30 | function populateGhostInputs (data) { 31 | $mr.find('[data-multirange-ghost-min]').val(data[0]); 32 | $mr.find('[data-multirange-ghost-max]').val(data[1]); 33 | } 34 | 35 | function populateDisplay (data) { 36 | if (data) { 37 | $mr.find('[data-multirange-display]').text(data[0] + 'CE - ' + data[1] + ' CE'); 38 | } else { 39 | $mr.find('[data-multirange-display]').text('Open to select a range'); 40 | } 41 | } 42 | 43 | function updateRange (data) { 44 | $mr.find('[data-multirange-input]')[0].value = data[0] + ',' + data[1]; 45 | } 46 | 47 | function resetRange () { 48 | var $this = $mr.find('[data-multirange-input]:not(.ghost)'); 49 | var data = [$this.attr('min'), $this.attr('max')]; 50 | populateGhostInputs(data); 51 | updateRange(data); 52 | populateDisplay(data); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /views/macros/filters/checkboxes.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% macro render(label, key, data) %} 4 |
5 |
6 |
7 | {{ label }} 8 |
9 | None Selected 10 | {{ svgs.chevronDown() }} 11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 | {% for item in data %} 24 |
25 | 26 |
27 | 28 |
29 | {% endfor %} 30 | {% if data.length === 0 %} 31 |
No filters available
32 | {% endif %} 33 |
34 |
35 |
36 |
37 |
38 | {% endmacro %} -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_slideshow.less: -------------------------------------------------------------------------------- 1 | .c-slideshow__container { 2 | width: 100%; 3 | padding-bottom: 100%; 4 | height: 0; 5 | background-color: @color-brand-secondary; 6 | } 7 | 8 | .c-slideshow { 9 | border: 1px solid @color-brand-primary 10 | } 11 | 12 | .c-slideshow__container img { 13 | max-height: 100%; 14 | height: 0; 15 | position: absolute; 16 | height: auto; 17 | width: auto; 18 | left: 0; 19 | right: 0; 20 | top: 0; 21 | bottom: 0; 22 | margin: auto; 23 | max-width: 97%; 24 | max-height: 97%; 25 | } 26 | 27 | .c-slideshow__header { 28 | display: flex; 29 | justify-content: space-between; 30 | padding: 3rem; 31 | @media @breakpoint-small { 32 | flex-direction: column; 33 | } 34 | } 35 | 36 | .c-slideshow__title { 37 | width: 55%; 38 | @media @breakpoint-small { 39 | width: 100%; 40 | } 41 | } 42 | 43 | .c-slideshow__info { 44 | display: flex; 45 | align-items: baseline; 46 | justify-content: flex-end; 47 | width: 45%; 48 | margin-top: 0.9rem; 49 | @media @breakpoint-small { 50 | width: 100%; 51 | } 52 | } 53 | 54 | .c-slideshow__gallery-description-items { 55 | text-align: right; 56 | // max-width: 23rem; 57 | margin: initial; 58 | top: -2px; 59 | } 60 | 61 | .c-slideshow__gallery-pager { 62 | display: flex; 63 | align-items: center; 64 | min-width: 5rem; 65 | margin-left: 1rem; 66 | } 67 | 68 | .c-slideshow__pager-button { 69 | border: 0; 70 | background: transparent; 71 | cursor: pointer; 72 | svg { 73 | max-width: 6px; 74 | padding-top: 4px; 75 | } 76 | } 77 | 78 | .c-slideshow__pager-count { 79 | display: inline-block; 80 | padding-left: 0.5rem; 81 | padding-right: 0.5rem; 82 | } 83 | 84 | .c-slideshow__description-item { 85 | width: 100% !important; 86 | } 87 | 88 | .c-slideshow__description-wrapper { 89 | text-align: right; 90 | @media @breakpoint-small { 91 | text-align: left; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/popups/checkboxes.js: -------------------------------------------------------------------------------- 1 | /* global omUtils */ 2 | 3 | $(function () { 4 | var $cb = $('[data-checkboxes]'); 5 | 6 | $cb.each(function () { 7 | var $context = $(this); 8 | var qs = omUtils.parseParams(window.location.search); 9 | if (qs[$context.find('input:first').attr('name')]) { 10 | var appliedFilters = qs[$context.find('input:first').attr('name')]; 11 | if (_.isArray(appliedFilters)) { 12 | appliedFilters.forEach(function (filter) { 13 | $context.find('input[value="' + filter + '"]').prop('checked', true).trigger('change'); 14 | }); 15 | } 16 | if (_.isString(appliedFilters)) { 17 | $context.find('input[value="' + appliedFilters + '"]').prop('checked', true).trigger('change'); 18 | } 19 | updateDisplay(); 20 | } 21 | 22 | // Events 23 | $context.find('[data-checkboxes-all]').on('click', function (e) { 24 | e.preventDefault(); 25 | selectAll(); 26 | }); 27 | 28 | $context.find('[data-checkboxes-clear]').on('click', function (e) { 29 | e.preventDefault(); 30 | clearAll(); 31 | }); 32 | 33 | $context.find('input').on('change', function () { 34 | updateDisplay(); 35 | }); 36 | 37 | // Actions 38 | function selectAll () { 39 | $context.find('input').prop('checked', true).trigger('change'); 40 | } 41 | 42 | function clearAll () { 43 | $context.find('input').prop('checked', false).trigger('change'); 44 | } 45 | 46 | function updateDisplay () { 47 | var inputs = $context.find('input:checked'); 48 | if (inputs.length > 1) { 49 | $context.find('[data-checkboxes-display]').text('Multiple Selected'); 50 | } else if (inputs.length === 1) { 51 | $context.find('[data-checkboxes-display]').text(inputs.siblings('label').text()); 52 | } else { 53 | $context.find('[data-checkboxes-display]').text('None Selected'); 54 | } 55 | } 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-search/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import 'apostrophe-templates:macros/masthead.html' as masthead %} 3 | {% import 'apostrophe-templates:macros/searchbar.html' as searchbar %} 4 | {% import 'apostrophe-templates:macros/generic-cards.html' as genericCards %} 5 | {% import 'apostrophe-templates:macros/artist-cards.html' as artistCards %} 6 | {% import 'apostrophe-templates:macros/artwork-cards.html' as artworkCards %} 7 | {% import 'apostrophe-pager:macros.html' as pager %} 8 | {% set types = apos.modules['apostrophe-search'].options.types %} 9 | {% set page = data.page %} 10 | 11 | {% block main %} 12 | {% if data.query.search %} 13 | {% set query = data.query.search %} 14 | {% elif data.query.tag %} 15 | {% set query = data.query.tag %} 16 | {% endif %} 17 | 18 | {{ masthead.render(data.page, { title: 'Search results for ‘' + query + '’', search: query } ) }} 19 | 20 |
21 | 22 |
23 | {% for type in types %} 24 | {% if data.docs[type] %} 25 |

Related {{ type | capitalize }}s

26 |
27 | {% if type === 'artwork' %} 28 | {{ artworkCards.render(data.docs[type]) }} 29 | {% elif type === 'artist' %} 30 | {{ artistCards.render(data.docs[type]) }} 31 | {% else %} 32 | {{ genericCards.render(data.docs[type], { 33 | schema: ['title', 'startDate', 'publishedDate', 'tags', { label: 'Date: ', field: 'year' } ] 34 | }) }} 35 | {% endif %} 36 |
37 | {% endif %} 38 | {% endfor %} 39 |
40 | {{ pager.render({ page: data.currentPage, total: data.totalPages }, data.url) }} 41 |
42 |
43 |
44 | {% endblock %} -------------------------------------------------------------------------------- /lib/modules/slideshow-widgets/public/js/always.js: -------------------------------------------------------------------------------- 1 | /* global Swiper */ 2 | 3 | apos.define('slideshow-widgets', { 4 | extend: 'apostrophe-widgets', 5 | construct: function (self, options) { 6 | self.play = function ($widget, data, options) { 7 | // We're not using Babel in this project, so use "var" for the 8 | // deepest backwards compatibility. If you want to, you can use 9 | // Babel and webpack, then code in the latest version of JavaScript 10 | // and push the output file to Apostrophe as an asset 11 | var $current = $widget.find('[data-gallery-current]'); 12 | // Notice we never use a global CSS selector - we always 13 | // "find" inside $widget. Swiper uses the DOM directly, so use 14 | // [0] to get from the jQuery object to the DOM element 15 | var imageSwiper = new Swiper($widget.find('[data-slideshow]')[0], { 16 | loop: true, 17 | autoHeight: true, 18 | slideToClickedSlide: false, 19 | threshold: 10, 20 | effect: 'fade', 21 | fadeEffect: { 22 | crossFade: true 23 | }, 24 | pagination: { 25 | clickable: false 26 | } 27 | }); 28 | 29 | var descriptionSwiper = new Swiper($widget.find('[data-slideshow-descriptions]')[0], { 30 | loop: true, 31 | effect: 'fade', 32 | fadeEffect: { 33 | crossFade: true 34 | } 35 | }); 36 | 37 | $widget.find('[data-slideshow-prev]').on('click', function () { 38 | imageSwiper.slidePrev(); 39 | }); 40 | 41 | $widget.find('[data-slideshow-next]').on('click', function () { 42 | imageSwiper.slideNext(); 43 | }); 44 | 45 | if (imageSwiper.on) { 46 | imageSwiper.on('slideChange', function () { 47 | $current.text(imageSwiper.realIndex + 1); 48 | descriptionSwiper.slideTo(imageSwiper.realIndex + 1); 49 | }); 50 | imageSwiper.on('click', function () { 51 | imageSwiper.slideNext(); 52 | }); 53 | } 54 | }; 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /views/navigation.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/svgs.html' as svgs %} 2 | 3 | {% macro render(home, currentPage, images, options) %} 4 | {% set paths = apos.helpers.getPaths(apos.helpers._shuffle(images.items[0]._pieces)) %} 5 | 11 | 38 | {% if options.mobile %} 39 | 44 | {% endif %} 45 | {% endmacro %} 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_demo-notification.less: -------------------------------------------------------------------------------- 1 | .c-demo-notification { 2 | font-family: @font-family-body; 3 | position: fixed; 4 | z-index: 500; 5 | right: 2rem; 6 | bottom: 2rem; 7 | padding: 2rem 3rem 2rem 5rem; 8 | background-color: @color-white; 9 | box-shadow: 2px 5px 10px -3px #524979; 10 | border-radius: 10px; 11 | overflow: hidden; 12 | transition: all 0.5s ease; 13 | transform: translateX(0rem); 14 | @media @breakpoint-small { 15 | padding: 2rem 3rem; 16 | right: 0; 17 | left: 0; 18 | } 19 | } 20 | 21 | .c-demo-notification--hidden { 22 | transform: translateX(48rem); 23 | } 24 | 25 | .c-demo-notification__background { 26 | position: absolute; 27 | left: -2rem; 28 | top: -1rem; 29 | opacity: 0.8; 30 | } 31 | 32 | .c-demo-notification__content { 33 | max-width: 290px; 34 | margin-right: 30px; 35 | } 36 | .c-demo-notification__container { 37 | display: flex; 38 | align-items: center; 39 | } 40 | 41 | .c-demo-notification__headline { 42 | font-size: 16px; 43 | font-weight: 700; 44 | color: #4A4A4A; 45 | margin-bottom: 0.5rem; 46 | } 47 | 48 | .c-demo-notification__body { 49 | font-size: 13px; 50 | color: #4A4A4A; 51 | line-height: 1.4; 52 | 53 | a { 54 | background-image: @color-brand-gradient; 55 | -webkit-text-fill-color: transparent; 56 | -webkit-background-clip: text; 57 | text-decoration: none; 58 | 59 | &:hover { 60 | -webkit-text-fill-color: white; 61 | -webkit-background-clip: unset; 62 | text-decoration: none; 63 | } 64 | } 65 | } 66 | 67 | .c-demo-notification__button { 68 | background-image: @color-brand-gradient; 69 | border-radius: 7px; 70 | font-weight: 700; 71 | font-size: 14px; 72 | white-space: nowrap; 73 | padding: 1.5rem 1rem; 74 | a { 75 | color: @color-white; 76 | text-decoration: none; 77 | } 78 | } 79 | 80 | .c-demo-notification__close { 81 | position: absolute; 82 | left: 0; 83 | top: 0; 84 | bottom: 0; 85 | padding: 1rem; 86 | font-size: 19px; 87 | @media @breakpoint-small { 88 | left: auto; 89 | right: 0; 90 | height: 4rem; 91 | } 92 | &:active, &:focus { 93 | outline: none; 94 | border: none; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/modules/styleguide/index.js: -------------------------------------------------------------------------------- 1 | const lorem = require('lorem-ipsum'); 2 | 3 | module.exports = { 4 | extend: 'apostrophe-custom-pages', 5 | name: 'styleguide', 6 | alias: 'styleguide', 7 | 8 | construct: function (self, options) { 9 | self.dispatch('/', function (req, callback) { 10 | req.template = self.renderer('styleguide', { 11 | nav: [ 12 | { label: 'Colors', anchor: 'colors' }, 13 | { label: 'Typography', anchor: 'type' }, 14 | { label: 'UI', anchor: 'ui' }, 15 | { label: 'Forms', anchor: 'forms' } 16 | ], 17 | colors: [ 18 | { label: 'Dark', class: 'o-background-dark' }, 19 | { label: 'Medium', class: 'o-background-med' }, 20 | { label: 'Light', class: 'o-background-light' }, 21 | { label: 'White', class: 'o-background-white' }, 22 | { label: 'Brand Primary', class: 'o-background-brand-primary' }, 23 | { label: 'Brand Secondary', class: 'o-background-brand-secondary' } 24 | ], 25 | type: [ 26 | { label: 'This is a Title', class: 'o-title', content: lorem({ sentenceUpperBound: 5 }) }, 27 | { label: 'This is a Section Header', class: 'o-section-header', content: lorem({ sentenceUpperBound: 5 }) }, 28 | { label: 'This is a Headline', class: 'o-headline', content: lorem({ sentenceUpperBound: 5 }) }, 29 | { label: 'This is a Subheadline', class: 'o-subheadline', content: lorem({ sentenceUpperBound: 5 }) }, 30 | { label: 'This is Meta Information', class: 'o-meta', content: lorem({ sentenceUpperBound: 10 }) }, 31 | { label: 'This is a Lede', class: 'o-lede', content: lorem({ sentenceUpperBound: 10, count: 2 }) }, 32 | { label: 'This is Body Copy', class: 'o-body', content: lorem({ count: 2, units: 'paragraphs' }) } 33 | ] 34 | }); 35 | return callback(null); 36 | }); 37 | 38 | self.apos.pages.park({ 39 | title: 'Styleguide', 40 | type: 'styleguide', 41 | slug: '/styleguide', 42 | published: true, 43 | orphan: true 44 | }); 45 | }, 46 | 47 | afterConstruct: function (self) { 48 | self.pushAsset('stylesheet', 'styleguide', { when: 'always' }); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lib/modules/people-pages/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros/masthead.html" as masthead %} 3 | {% import "macros/peer-nav.html" as peerNav %} 4 | {% import "macros/image.html" as image %} 5 | {% import "macros/svgs.html" as svgs %} 6 | 7 | {% set page = data.page %} 8 | 9 | {% macro bioBox() %} 10 |
11 |
12 | {{ svgs.boxIndicator() }} 13 |
14 | 17 |

18 |
19 | Role on Team 20 |
21 |
22 |
23 | {% endmacro %} 24 | 25 | {% block main %} 26 | {{ masthead.render(page) }} 27 |
28 | {% if page._children.length %} 29 | {{ peerNav.render(page._children, page) }} 30 | {% else %} 31 | {{ peerNav.render(page._ancestors[0]._children, page) }} 32 | {% endif %} 33 |
34 | {{ apos.area(data.page, 'body', { 35 | widgets: apos.helpers.narrowWidgets 36 | }) }} 37 |
38 | {% for person in data.pieces %} 39 | 44 | {% if (loop.index % 4 === 0) and not loop.last %} 45 | {{ bioBox() }} 46 | {% endif %} 47 | {% endfor %} 48 | {{ bioBox() }} 49 |
50 |
51 |
52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /lib/modules/artists/index.js: -------------------------------------------------------------------------------- 1 | const areas = require('../helpers/lib/areas.js'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = { 5 | name: 'artist', 6 | label: 'Artist', 7 | pluralLabel: 'Artists', 8 | contextual: true, 9 | sort: { 10 | title: 1 11 | }, 12 | addFields: [ 13 | { 14 | name: 'description', 15 | label: 'Short Description', 16 | help: 'This is displayed at the bottom of an Artwork show page that this artist is associated with', 17 | type: 'string', 18 | textarea: true 19 | }, 20 | { 21 | name: 'thumbnail', 22 | label: 'Thumbnail', 23 | type: 'singleton', 24 | widgetType: 'apostrophe-images', 25 | options: { 26 | limit: 1, 27 | aspectRatio: [ 1, 1 ] 28 | } 29 | }, 30 | { 31 | name: 'lifetime', 32 | label: 'Lifetime', 33 | help: 'Year range like 1840–1926', 34 | type: 'string' 35 | }, 36 | { 37 | name: 'nationality', 38 | label: 'Nationality', 39 | type: 'string' 40 | }, 41 | { 42 | name: 'movement', 43 | label: 'Movement', 44 | help: 'Art movement this artist is most closely associated with', 45 | type: 'string' 46 | }, 47 | { 48 | name: 'body', 49 | label: 'Body', 50 | contextual: true, 51 | type: 'area', 52 | options: { 53 | widgets: _.clone(areas.narrowWidgets) 54 | } 55 | }, 56 | { 57 | name: 'extra', 58 | label: 'Extra', 59 | contextual: true, 60 | type: 'area', 61 | options: { 62 | widgets: _.clone(areas.baseWidgets) 63 | } 64 | }, 65 | { 66 | // Access to all the works of this artist — but for performance, 67 | // only fetch them if we are fetching just one artist 68 | name: '_artworks', 69 | label: 'Artworks', 70 | type: 'joinByOneReverse', 71 | withType: 'artwork', 72 | idField: 'artistId', 73 | ifOnlyOne: true 74 | } 75 | ], 76 | arrangeFields: [ 77 | { 78 | name: 'main', 79 | label: 'Main content', 80 | fields: [ 81 | 'description', 82 | 'thumbnail', 83 | 'lifetime', 84 | 'nationality', 85 | 'movement' 86 | ] 87 | } 88 | ] 89 | }; 90 | -------------------------------------------------------------------------------- /views/layout.html: -------------------------------------------------------------------------------- 1 | {# Automatically extends the right outer layout and also handles AJAX siutations #} 2 | {% extends data.outerLayout %} 3 | 4 | {% import 'header.html' as header %} 5 | {% import 'footer.html' as footer %} 6 | {% import 'demo/modal.html' as modal %} 7 | {% import 'demo/notification.html' as notification %} 8 | {% import 'apostrophe-favicons:faviconMacros.html' as favicons %} 9 | 10 | {% block title %} 11 | {# Create a useful, SEO-friendly title tag based on what we have #} 12 | {% if data.piece %} 13 | {{ data.piece.title }} | OpenMuseum 14 | {% elseif data.page %} 15 | {{ data.page.title }} | OpenMuseum 16 | {% else %} 17 | {{ 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.') }} 18 | {% endif %} 19 | {% endblock %} 20 | 21 | {% block extraHead %} 22 | {% if data.piece %} 23 | {% if data.piece.seoTitle %} 24 | {% set title = data.piece.seoTitle %} 25 | {% else %} 26 | {% set title = data.piece.title %} 27 | {% endif %} 28 | {% else %} 29 | {% if data.page.seoTitle %} 30 | {% set title = data.page.seoTitle %} 31 | {% else %} 32 | {% set title = data.page.title %} 33 | {% endif %} 34 | {% endif %} 35 | 36 | {% include "apostrophe-open-graph:view.html" %} 37 | {% include "apostrophe-seo:view.html" %} 38 | {% include "analytics.html" %} 39 | {{ favicons.renderLinks(apos, data.global) }} 40 | {% endblock %} 41 | 42 | {% block bodyClass %} 43 | {{ super() }} 44 | {% if data.global.demoMode %} is-demo-mode--{{ data.global.demoMode }}{% endif %} 45 | {% if data.user %} is-logged-in{% endif %} 46 | {% endblock %} 47 | 48 | {% block beforeMain %} 49 | {{ header.render(data.home, data.page, data.global.logoImages) }} 50 | {% endblock %} 51 | 52 | {% block main %} 53 | {# 54 | Usually, your page templates in the apostrophe-pages module will override 55 | this block. It is safe to assume this is where your page-specific content 56 | should go. 57 | #} 58 | {% endblock %} 59 | 60 | {% block afterMain %} 61 | {{ footer.render(data.home, data.page, data.global.logoImages) }} 62 | {% if data.global.demoMode %} 63 | {{ modal.render() }} 64 | {{ notification.render() }} 65 | {% endif %} 66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /views/macros/generic-cards.html: -------------------------------------------------------------------------------- 1 | {% import 'macros/image.html' as image %} 2 | 3 | {% macro card(piece, options) %} 4 |
5 |
6 | 7 | {% set thumbnail = apos.images.first(piece.thumbnail) %} 8 | {{ image.render(thumbnail, { alt: piece.title }) }} 9 | 10 |
11 |
12 | {% if options.schema %} 13 | {% for item in options.schema %} 14 | {% if item === 'title' %} 15 | {{ piece.title }} 16 | {% elif item === 'startDate' or item === 'publishedAt' %} 17 | {{ piece[item] | date('MMMM Do YYYY') }} 18 | {% elif item === 'tags' %} 19 | 24 | {% elif apos.helpers._isObject(item) %} 25 | {% if piece[item.field] %} 26 | {{ item.label if item.label }}{{ piece[item.field] }} 27 | {% endif %} 28 | {% elif apos.helpers.isJoin(item) %} 29 | {{ apos.helpers.typy(piece, item) }} 30 | {% else %} 31 | {{ piece[item] }} 32 | {% endif %} 33 | {% endfor %} 34 | {% endif %} 35 |
36 |
37 | {% endmacro %} 38 | 39 | {% macro cards(arr, options) %} 40 |
41 | {% for item in arr %} 42 | {{ card(item, options) }} 43 | {% endfor %} 44 |
45 | {% endmacro %} 46 | 47 | {% macro render(data, options) %} 48 | {% if apos.helpers._isArray(data) %} 49 | {{ cards(data, options) }} 50 | {% elif apos.helpers._isObject %} 51 | {{ card(data, options) }} 52 | {% else %} 53 | {{ apos.log('didnt pass object or array to generic-cards macro') }} 54 | {% endif %} 55 | {% endmacro %} 56 | -------------------------------------------------------------------------------- /lib/modules/locations/index.js: -------------------------------------------------------------------------------- 1 | const Geo = require('node-geocoder'); 2 | 3 | module.exports = { 4 | name: 'location', 5 | extend: 'apostrophe-pieces', 6 | label: 'Location', 7 | pluralLabel: 'Locations', 8 | contextual: true, 9 | addFields: [ 10 | { 11 | name: 'address', 12 | label: 'Address', 13 | type: 'string', 14 | required: true 15 | }, 16 | { 17 | name: 'stateCode', 18 | label: 'stateCode', 19 | contextual: true, 20 | type: 'string' 21 | }, 22 | { 23 | name: 'zipCode', 24 | label: 'zipCode', 25 | contextual: true, 26 | type: 'string' 27 | }, 28 | { 29 | name: 'streetName', 30 | label: 'streetName', 31 | contextual: true, 32 | type: 'string' 33 | }, 34 | { 35 | name: 'city', 36 | label: 'city', 37 | contextual: true, 38 | type: 'string' 39 | } 40 | ], 41 | arrangeFields: [ 42 | { 43 | name: 'details', 44 | label: 'Details', 45 | fields: ['address'] 46 | } 47 | ], 48 | afterConstruct: function (self) { 49 | self.enableGeo(); 50 | }, 51 | construct: function (self, options) { 52 | self.enableGeo = function () { 53 | if (self.options.mapQuest) { 54 | self.geo = Geo({ 55 | provider: 'mapquest', 56 | apiKey: self.options.mapQuest.key 57 | }); 58 | } else { 59 | console.warn('WARNING: No MapQuest API credentials found, expected as part of the `locations` piece type\'s options. See the README for information about where to put these options and see https://developer.mapquest.com/documentation/open/ for generating a MapQuest key/secret'); 60 | } 61 | }; 62 | self.beforeSave = function (req, piece, options, callback) { 63 | if (!self.geo) { 64 | console.warn('WARNING: Can\'t geocode `location` piece\'s address field, maps will not be rendered'); 65 | return callback(null); 66 | } 67 | return self.geo.geocode(piece.address, function (err, res) { 68 | if (err) { 69 | return callback(err); 70 | } 71 | let l = res[0]; 72 | 73 | piece.latlng = [l.latitude, l.longitude]; 74 | piece.stateCode = l.stateCode; 75 | piece.city = l.city; 76 | piece.streetName = l.streetName; 77 | piece.zipCode = l.zipcode; 78 | return callback(null); 79 | }); 80 | }; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_people-page.less: -------------------------------------------------------------------------------- 1 | .c-people-page { 2 | 3 | } 4 | 5 | .c-people-page__inner { 6 | 7 | } 8 | 9 | .c-people-page__grid { 10 | max-width: 94rem; 11 | display: flex; 12 | align-items: baseline; 13 | flex-wrap: wrap; 14 | position: relative; 15 | 16 | button:nth-of-type(4n+4) { 17 | margin-right: 0; 18 | } 19 | } 20 | 21 | .c-people-page__grid-item { 22 | width: 23%; 23 | margin-right: 2.66%; 24 | text-align: left; 25 | margin-bottom: 4rem; 26 | } 27 | 28 | .c-people-page__grid-item__media { 29 | margin-bottom: 1.5rem; 30 | } 31 | .c-people-page__grid-item__title { 32 | @media @breakpoint-small { 33 | font-size: 1.3rem; 34 | } 35 | } 36 | 37 | .c-people-page__bio-box__description { 38 | width: 58%; 39 | margin-right: 4%; 40 | @media @breakpoint-small { 41 | width: 100%; 42 | margin-right: 0; 43 | } 44 | } 45 | 46 | .c-people-page__bio-box__role-wrapper{ 47 | width: 38%; 48 | @media @breakpoint-small { 49 | width: 100%; 50 | margin-bottom: 2rem; 51 | } 52 | } 53 | 54 | .c-people-page__bio-box__role-header { 55 | display: block; 56 | margin-bottom: 0.5rem; 57 | } 58 | 59 | .c-people-page__bio-box { 60 | position: relative; 61 | display: flex; 62 | background-color: @color-brand-secondary; 63 | color: @color-brand-primary; 64 | transition: @transition-ease-in-out; 65 | max-height: 0; 66 | width: 100%; 67 | padding-left: 4rem; 68 | padding-right: 4rem; 69 | overflow: hidden; 70 | @media @breakpoint-small { 71 | flex-direction: column-reverse 72 | } 73 | } 74 | 75 | .c-people-page__bio-box[data-bio-box="active"] { 76 | padding-top: 4rem; 77 | padding-bottom: 4rem; 78 | max-height: 9999px; 79 | margin-bottom: 2rem; 80 | overflow: visible; 81 | border: 1px solid @color-brand-primary 82 | } 83 | .c-people-page__bio-box[data-bio-box="inactive"]{ 84 | .c-people-page__bio-box__description, 85 | .c-people-page__bio-box__role-wrapper { 86 | max-height: 0; 87 | } 88 | } 89 | 90 | .c-people-page__bio-box__close { 91 | position: absolute; 92 | top: 1rem; 93 | right: 1rem; 94 | padding: 1rem; 95 | svg { 96 | height: 1.5rem; 97 | width: 1.5rem; 98 | } 99 | } 100 | 101 | .c-people-page__bio-box__indicator { 102 | position: absolute; 103 | top: -0.99rem; 104 | transition: left 0.3s cubic-bezier(.77,.17,.42,1.24) 105 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/objects/_fields.less: -------------------------------------------------------------------------------- 1 | .o-field-container { 2 | font-family: @font-family-body; 3 | font-size: @font-size-body; 4 | } 5 | 6 | .o-field-label { 7 | display: block; 8 | font-weight: 700; 9 | margin-bottom: 1rem; 10 | } 11 | 12 | .o-field-input { 13 | outline: none; 14 | transition: @transition-ease-in-out; 15 | } 16 | 17 | .o-field-input--text { 18 | border: 1px solid @color-brand-primary; 19 | padding: 1.15rem; 20 | max-width: 30rem; 21 | width: 100%; 22 | font-family: @font-family-body; 23 | font-size: @font-size-body; 24 | -webkit-appearance: none; 25 | &:focus { 26 | outline: 2px solid @color-brand-primary; 27 | } 28 | } 29 | 30 | .o-field-input--select { 31 | display: inline-block; 32 | padding: 1.2rem; 33 | font-size: 1.6rem; 34 | border-radius: 0; 35 | -webkit-appearance: none; 36 | outline: none; 37 | background-color: @color-white; 38 | border: 1px solid @color-dark; 39 | &:focus { 40 | outline: 2px solid @color-brand-primary; 41 | } 42 | } 43 | 44 | .o-field-container--checkbox { 45 | display: flex; 46 | position: relative; 47 | align-items: center; 48 | .o-field-label { 49 | color: @color-brand-primary; 50 | font-weight: 400; 51 | margin-bottom: 0; 52 | } 53 | .o-field-input { 54 | margin: 0; 55 | height: 1.6rem; 56 | width: 1.6rem; 57 | position: absolute; 58 | left: 0; 59 | top: 0; 60 | z-index: 1; 61 | opacity: 0; 62 | &:hover { cursor: pointer; } 63 | &:checked ~ .o-field-input-checkbox-ui:after { 64 | opacity: 1; 65 | } 66 | &:checked ~ .o-field-label { 67 | font-weight: 700; 68 | } 69 | } 70 | } 71 | 72 | .o-field-input-checkbox-ui { 73 | position: relative; 74 | height: 1.6rem; 75 | width: 1.6rem; 76 | border: 1px solid @color-brand-primary; 77 | margin-right: 1rem; 78 | &:after { 79 | position: absolute; 80 | top: 2px; 81 | left: 2px; 82 | content: ''; 83 | height: 1rem; 84 | width: 1rem; 85 | background-color: @color-brand-primary; 86 | opacity: 0; 87 | } 88 | } 89 | 90 | 91 | // Fancy custom multi select 92 | .o-field-container--tags { 93 | .o-field-input--tag { 94 | opacity: 0; 95 | height: 0; 96 | width: 0; 97 | overflow: hidden; 98 | float: left; // remove from flow 99 | } 100 | 101 | } 102 | 103 | .o-fieldset { 104 | border: none; 105 | } -------------------------------------------------------------------------------- /lib/modules/artworks-pages/views/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% set piece = data.piece %} 3 | {% set artist = data.piece._artist %} 4 | {% import 'apostrophe-templates:macros/breadcrumbs.html' as breadcrumbs %} 5 | {% import 'macros/artwork-cards.html' as cards %} 6 | {% import 'macros/image.html' as image %} 7 | {% import 'macros/definitionList.html' as dl %} 8 | 9 | {% block main %} 10 |
11 | {{ breadcrumbs.render(data.page) }} 12 |

{{ piece.title }}

13 | {% if piece._artist %} 14 |

{{ artist.title }}

15 | {% endif %} 16 |
17 | {% set thumbnail = apos.images.first(piece.thumbnail) %} 18 | {{ image.render(thumbnail) }} 19 |
20 |
21 |
22 |
23 | {{ dl.render(piece, [ 24 | { name: 'year', label: 'Date' }, 25 | { name: 'medium' }, 26 | { name: 'dimensions' }, 27 | { name: '_location.title', label: 'Location' } 28 | ]) }} 29 |
30 |
31 |
32 | {{ apos.area(piece, 'body', { widgets: apos.helpers.narrowWidgets }) }} 33 |
34 |
35 |
36 |
37 | {% set artistImage = apos.images.first(artist.thumbnail) %} 38 | {{ image.render(artistImage) }} 39 |
40 |
41 |

About {{ artist.title }}

42 |

43 | {{ artist.description }} 44 |

45 | Learn More 46 |
47 |
48 | {% if data.related.length > 0 %} 49 | 53 | {% endif %} 54 |
55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/site.less: -------------------------------------------------------------------------------- 1 | @import 'settings/_transitions.less'; 2 | @import 'settings/_colors.less'; 3 | @import 'settings/_fonts.less'; 4 | @import 'settings/_layout.less'; 5 | @import 'settings/_zindex.less'; 6 | @import 'settings/_flourishes.less'; 7 | @import 'settings/_breakpoints.less'; 8 | 9 | @import 'base/_document.less'; 10 | 11 | @import 'generic/_reset.less'; 12 | 13 | @import 'utils/_admin.less'; 14 | 15 | @import 'objects/_blocks.less'; 16 | @import 'objects/_container.less'; 17 | @import 'objects/_spacing.less'; 18 | @import 'objects/_type.less'; 19 | @import 'objects/_link.less'; 20 | @import 'objects/_button.less'; 21 | @import 'objects/_backgrounds.less'; 22 | @import 'objects/_tag.less'; 23 | @import 'objects/_filters.less'; 24 | @import 'objects/_fields.less'; 25 | @import 'objects/_screen.less'; 26 | @import 'objects/_colors.less'; 27 | @import 'objects/_page.less'; 28 | @import 'objects/_card.less'; 29 | @import 'objects/_icon.less'; 30 | @import 'objects/_empty-state.less'; 31 | 32 | @import 'components/_image.less'; 33 | @import 'components/_pagination.less'; 34 | @import 'components/_area.less'; 35 | @import 'components/_slideshow.less'; 36 | @import 'components/_navigation.less'; 37 | @import 'components/_marquee.less'; 38 | @import 'components/_rich-text.less'; 39 | @import 'components/_artworks-widget.less'; 40 | @import 'components/_artworks-cards.less'; 41 | @import 'components/_columns.less'; 42 | @import 'components/_two-panel.less'; 43 | @import 'components/_masthead.less'; 44 | @import 'components/_breadcrumbs.less'; 45 | @import 'components/_peer-nav.less'; 46 | @import 'components/_footer.less'; 47 | @import 'components/_header.less'; 48 | @import 'components/_filters.less'; 49 | @import 'components/_artwork-page.less'; 50 | @import 'components/_definition-list.less'; 51 | @import 'components/_artist-page.less'; 52 | @import 'components/_meta-columns.less'; 53 | @import 'components/_location-widget.less'; 54 | @import 'components/_tag-picker.less'; 55 | @import 'components/_article-page.less'; 56 | @import 'components/_multirange.less'; 57 | @import 'components/_popup.less'; 58 | @import 'components/_checkbox-filter.less'; 59 | @import 'components/_search-page.less'; 60 | @import 'components/_searchbar.less'; 61 | @import 'components/_link-widget.less'; 62 | @import 'components/_event-page.less'; 63 | @import 'components/_generic-card.less'; 64 | @import 'components/_generic-filters.less'; 65 | @import 'components/_map.less'; 66 | @import 'components/_content-widget.less'; 67 | @import 'components/_article-widget.less'; 68 | @import 'components/_article-featured-widget.less'; 69 | @import 'components/_people-page.less'; 70 | @import 'components/_mobile-menu.less'; 71 | @import 'components/_event-widget.less'; 72 | @import 'components/_loading.less'; 73 | @import 'components/_random-met-artwork.less'; 74 | @import 'components/_demo-modal.less'; 75 | @import 'components/_demo-notification.less'; 76 | 77 | @import 'vendor/_multirange.less'; 78 | @import 'vendor/_swiper.4.4.1.less'; 79 | 80 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/popups/tags.js: -------------------------------------------------------------------------------- 1 | /* global omUtils */ 2 | 3 | // Don't use $(function() { ... }). Instead, use the apostrophe 4 | // "enhance" event handler to enhance any block of markup that has 5 | // just been added to the page — such as the whole page at load time, 6 | // or a portion of the page during an AJAX refresh. 7 | 8 | apos.on('enhance', function ($el) { 9 | var $tagPicker = $el.find('[data-tag-picker]'); 10 | 11 | // Setup from previous search 12 | if ($tagPicker.length > 0 && omUtils.parseParams(window.location.search).tags) { 13 | var tags = omUtils.parseParams(window.location.search).tags; 14 | 15 | if (_.isString(tags)) { 16 | addTag($tagPicker.find('[data-tag-picker-selected][value="' + tags + '"]'), $tagPicker); 17 | } 18 | 19 | if (_.isArray(tags)) { 20 | tags.forEach(function (tag) { 21 | addTag($tagPicker.find('[data-tag-picker-selected][value="' + tag + '"]'), $tagPicker); 22 | }); 23 | } 24 | } 25 | 26 | // Events 27 | $tagPicker.find('[data-tag-picker-selected]').on('click', function (e) { 28 | e.preventDefault(); 29 | var $this = $(this); 30 | var $parent = $this.parents('[data-tag-picker]'); 31 | 32 | if ($this.attr('data-tag-picker-selected') === 'true') { 33 | removeTag($this, $parent); 34 | } else { 35 | addTag($this, $parent); 36 | } 37 | }); 38 | 39 | $tagPicker.find('[data-tag-picker-submit]').on('click', function (e) { 40 | e.preventDefault(); 41 | submitTags($tagPicker); 42 | }); 43 | 44 | $tagPicker.find('[data-tag-picker-clear]').on('click', function (e) { 45 | e.preventDefault(); 46 | clearTags($tagPicker); 47 | }); 48 | 49 | // Actions 50 | function removeTag ($tag, $context) { 51 | $tag.attr('data-tag-picker-selected', 'false'); 52 | $context.find('[data-tag-picker-display] button[value="' + $tag.val() + '"]').remove(); 53 | removeTagFromSelect($tag, $context); 54 | } 55 | 56 | function addTag ($tag, $context) { 57 | $tag.attr('data-tag-picker-selected', 'true'); 58 | var $clone = $tag.clone(); 59 | $clone.removeAttr('data-tag-picker-selected'); 60 | $clone.on('click', function (e) { 61 | e.preventDefault(); 62 | removeTag($clone, $context); 63 | }); 64 | addTagToSelect($tag, $context); 65 | $context.find('[data-tag-picker-display]').append($clone); 66 | } 67 | 68 | function clearTags ($context) { 69 | $context.find('[data-tag-picker-selected]').attr('data-tag-picker-selected', 'false'); 70 | $context.find('[data-tag-picker-display] button:not([data-tag-picker-submit])').each(function () { 71 | removeTag($(this), $context); 72 | }); 73 | } 74 | 75 | function submitTags ($tagPicker) { 76 | $tagPicker.parents('form').submit(); 77 | } 78 | 79 | function addTagToSelect ($tag, $context) { 80 | $context.siblings('select').find('option[value="' + $tag.attr('value') + '"]').attr('selected', 'selected'); 81 | } 82 | 83 | function removeTagFromSelect ($tag, $context) { 84 | $context.siblings('select').find('option[value="' + $tag.attr('value') + '"]').removeAttr('selected'); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/js/vendor/multirange.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var supportsMultiple = self.HTMLInputElement && 'valueLow' in HTMLInputElement.prototype; 5 | 6 | var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); 7 | 8 | self.multirange = function (input) { 9 | if (supportsMultiple || input.classList.contains('multirange')) { 10 | return; 11 | } 12 | 13 | var value = input.getAttribute('value'); 14 | var values = value === null ? [] : value.split(','); 15 | var min = +(input.min || 0); 16 | var max = +(input.max || 100); 17 | var ghost = input.cloneNode(); 18 | 19 | input.classList.add('multirange', 'original'); 20 | ghost.classList.add('multirange', 'ghost'); 21 | 22 | input.value = values[0] || min + (max - min) / 2; 23 | ghost.value = values[1] || min + (max - min) / 2; 24 | 25 | input.parentNode.insertBefore(ghost, input.nextSibling); 26 | 27 | Object.defineProperty(input, 'originalValue', descriptor.get ? descriptor : { 28 | // Fuck you Safari >:( 29 | get: function () { 30 | return this.value; 31 | }, 32 | set: function (v) { 33 | this.value = v; 34 | } 35 | }); 36 | 37 | Object.defineProperties(input, { 38 | valueLow: { 39 | get: function () { 40 | return Math.min(this.originalValue, ghost.value); 41 | }, 42 | set: function (v) { 43 | this.originalValue = v; 44 | }, 45 | enumerable: true 46 | }, 47 | valueHigh: { 48 | get: function () { 49 | return Math.max(this.originalValue, ghost.value); 50 | }, 51 | set: function (v) { 52 | ghost.value = v; 53 | }, 54 | enumerable: true 55 | } 56 | }); 57 | 58 | if (descriptor.get) { 59 | // Again, fuck you Safari 60 | Object.defineProperty(input, 'value', { 61 | get: function () { 62 | return this.valueLow + ',' + this.valueHigh; 63 | }, 64 | set: function (v) { 65 | var values = v.split(','); 66 | this.valueLow = values[0]; 67 | this.valueHigh = values[1]; 68 | update(); 69 | }, 70 | enumerable: true 71 | }); 72 | } 73 | 74 | if (typeof input.oninput === 'function') { 75 | ghost.oninput = input.oninput.bind(input); 76 | } 77 | 78 | function update () { 79 | ghost.style.setProperty('--low', 100 * ((input.valueLow - min) / (max - min)) + 1 + '%'); 80 | ghost.style.setProperty('--high', 100 * ((input.valueHigh - min) / (max - min)) - 1 + '%'); 81 | } 82 | 83 | input.addEventListener('input', update); 84 | ghost.addEventListener('input', update); 85 | 86 | update(); 87 | }; 88 | 89 | multirange.init = function () { 90 | [].slice.call(document.querySelectorAll('input[type=range][multiple]:not(.multirange)')).forEach(multirange); 91 | }; 92 | 93 | if (document.readyState == 'loading') { 94 | document.addEventListener('DOMContentLoaded', multirange.init); 95 | } else { 96 | multirange.init(); 97 | } 98 | })(); 99 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_columns.less: -------------------------------------------------------------------------------- 1 | .c-columns__inner { 2 | display: flex; 3 | @media @breakpoint-medium { 4 | flex-direction: column; 5 | } 6 | } 7 | 8 | .c-columns--cream { background-color: @color-brand-secondary } 9 | .c-columns--lightPurple { background-color: @color-light } 10 | 11 | .c-columns.o-background-brand-secondary, 12 | .c-columns.o-background-light { 13 | padding: 5rem 0 10rem 0; 14 | margin-top: -7rem; 15 | } 16 | 17 | .c-area > .apos-area > .apos-area-widgets > [data-apos-widget-wrapper="columns"]:first-child .c-columns{ 18 | margin-top: 0; 19 | } 20 | 21 | // Column Variations 22 | .c-columns--two, 23 | .c-columns--two-reverse { 24 | .c-columns__column--0 { 25 | width: 55%; 26 | margin-right: 13%; 27 | @media @breakpoint-mid { 28 | margin-right: 5%; 29 | } 30 | @media @breakpoint-medium { 31 | width: 100%; 32 | margin-right: 0; 33 | } 34 | } 35 | .c-columns__column--1 { 36 | width: 32%; 37 | @media @breakpoint-mid { 38 | width: 40%; 39 | } 40 | @media @breakpoint-medium { 41 | width: 100%; 42 | } 43 | } 44 | } 45 | 46 | .c-columns--two-reverse { 47 | .c-columns__inner { 48 | flex-direction: row-reverse; 49 | @media @breakpoint-medium { 50 | flex-direction: column-reverse; 51 | } 52 | } 53 | .c-columns__column--0 { margin-right: 0; } 54 | .c-columns__column--1 { 55 | margin-right: 13%; 56 | @media @breakpoint-mid { 57 | margin-right: 5%; 58 | } 59 | @media @breakpoint-medium { 60 | margin-right: 0; 61 | } 62 | } 63 | } 64 | 65 | .c-columns--three { 66 | .c-columns__column--0 { 67 | margin-left: 8%; 68 | width: 25%; 69 | margin-right: 8%; 70 | } 71 | .c-columns__column--1 { 72 | width: 25%; 73 | margin-right: 8%; 74 | } 75 | .c-columns__column--2 { 76 | margin-right: 8%; 77 | width: 18%; 78 | } 79 | @media @breakpoint-large { 80 | .c-columns__column--0 { 81 | margin-left: 0%; 82 | margin-right: 4%; 83 | width: 32% 84 | } 85 | .c-columns__column--1 { 86 | margin-right: 4%; 87 | width: 32% 88 | } 89 | .c-columns__column--2 { 90 | margin-right: 0%; 91 | width: 28%; 92 | } 93 | } 94 | @media @breakpoint-mid { 95 | .c-columns__inner { flex-wrap: wrap; } 96 | .c-columns__column--0, 97 | .c-columns__column--1 { 98 | width: 48% 99 | } 100 | .c-columns__column--1 { 101 | margin-right: 0; 102 | } 103 | .c-columns__column--2 { 104 | width: 100%; 105 | } 106 | } 107 | @media @breakpoint-medium { 108 | .c-columns__inner { flex-direction: column; } 109 | .c-columns__column--0, 110 | .c-columns__column--1, 111 | .c-columns__column--2 { 112 | margin-left: 0; 113 | width: 100%; 114 | margin-right: 0; 115 | } 116 | } 117 | } 118 | 119 | .c-columns__inner { 120 | @media @breakpoint-small { 121 | flex-direction: column; 122 | .c-columns__column--0, 123 | .c-columns__column--1, 124 | .c-columns__column--2 { 125 | width: 100%; 126 | margin-right: 0; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_demo-modal.less: -------------------------------------------------------------------------------- 1 | .is-demo-mode--true--active { 2 | overflow: hidden; 3 | } 4 | 5 | .c-demo-modal { 6 | position: fixed; 7 | z-index: 999; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | top: 0; 12 | left: 0; 13 | width: 100%; 14 | height: 100%; 15 | background-color: fade(@color-brand-primary, 60%); 16 | transition: @transition-ease-in-out; 17 | opacity: 0; 18 | pointer-events: none; 19 | } 20 | 21 | .c-demo-modal--obscured { 22 | background-color: fade(@color-brand-primary, 0%); 23 | .c-demo-modal__content { 24 | opacity: 0.1; 25 | } 26 | } 27 | 28 | .is-demo-mode--true--active .c-demo-modal { 29 | opacity: 1; 30 | pointer-events: auto; 31 | } 32 | 33 | .c-demo-modal__content { 34 | position: relative; 35 | background-color: @color-white; 36 | padding: 5rem 8rem; 37 | box-shadow: 0px 0px 100px 11px rgba(0,0,0,0.3); 38 | border-radius: 10px; 39 | transition: all 0.4s ease; 40 | } 41 | 42 | .c-demo-modal__headline { 43 | text-align: center; 44 | margin-bottom: 2rem; 45 | } 46 | .c-demo-modal__gif { 47 | border: 1px solid @color-brand-primary; 48 | max-width: 400px; 49 | margin: 0 auto; 50 | display: none; 51 | &.is-loaded { 52 | display: block; 53 | } 54 | } 55 | 56 | .c-demo-modal__list { 57 | margin-bottom: 2rem; 58 | list-style-type: disc; 59 | margin-left: 3rem; 60 | li { 61 | margin-bottom: 1rem; 62 | } 63 | } 64 | 65 | .c-demo-modal__pointer-link { 66 | color: @color-brand-primary; 67 | border-bottom: 1px dashed @color-brand-primary; 68 | font-weight: 700; 69 | &:hover { 70 | cursor: pointer; 71 | } 72 | } 73 | 74 | .c-demo-modal__pointer-container { 75 | position: absolute; 76 | left: 1rem; 77 | opacity: 0; 78 | transition: @transition-ease-in-out; 79 | animation: arrowBounce 1s infinite alternate; 80 | -webkit-animation: arrowBounce 1s infinite alternate; 81 | svg { 82 | width: 70px; 83 | } 84 | &.c-demo-modal__pointer-container--active { 85 | opacity: 1; 86 | } 87 | } 88 | 89 | .c-demo-modal__pointer-container--admin-bar { 90 | left: 2rem; 91 | top: 11rem; 92 | @media @breakpoint-xl { 93 | left: 4.75rem; 94 | top: 15rem; 95 | } 96 | } 97 | .c-demo-modal__pointer-container--page-menu { 98 | left: 50px; 99 | bottom: 7rem; 100 | @media @breakpoint-xl { 101 | left: 68px; 102 | bottom: 9rem; 103 | } 104 | } 105 | 106 | .c-demo-modal__close-button { 107 | display: block; 108 | background-color: @color-brand-primary; 109 | padding: 2rem; 110 | color: @color-white; 111 | margin: 2rem auto 0; 112 | font-size: 16px; 113 | &:hover { 114 | cursor: pointer; 115 | } 116 | } 117 | 118 | .c-demo-modal__x { 119 | position: absolute; 120 | top: 1rem; 121 | right: 1rem; 122 | g { fill: @color-dark !important; } 123 | } 124 | 125 | .is-highlighting-demo { 126 | .c-header, 127 | .c-masthead, 128 | .o-container, 129 | .c-footer 130 | { 131 | filter:blur(4px); 132 | } 133 | } 134 | 135 | .is-highlighting-demo--pageMenu { 136 | .apos-context-menu-container { 137 | z-index: 1009; 138 | } 139 | } 140 | 141 | .is-highlighting-demo--adminBar { 142 | .apos-admin-bar { 143 | z-index: 1009; 144 | } 145 | } 146 | 147 | 148 | @keyframes arrowBounce { 149 | from { 150 | transform: translateY(0px); 151 | } 152 | to { 153 | transform: translateY(-15px); 154 | } 155 | } 156 | @-webkit-keyframes arrowBounce { 157 | from { 158 | transform: translateY(0px); 159 | } 160 | to { 161 | transform: translateY(-15px); 162 | } 163 | } 164 | 165 | 166 | -------------------------------------------------------------------------------- /lib/modules/apostrophe-assets/public/css/components/_multirange.less: -------------------------------------------------------------------------------- 1 | .c-multirange { 2 | min-width: 20rem; 3 | } 4 | 5 | .c-multirange__display { 6 | border: 1px solid @color-brand-primary; 7 | padding: 0.8rem; 8 | height: 4.4rem; 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | &:hover { 13 | cursor: pointer; 14 | } 15 | } 16 | 17 | .c-multirange__popup { 18 | min-width: 40rem; 19 | } 20 | 21 | .c-multirange__header { 22 | display: flex; 23 | justify-content: space-between; 24 | margin-bottom: 2rem; 25 | } 26 | 27 | .c-multirange__labels { 28 | display: flex; 29 | justify-content: space-between; 30 | font-size: 1.4rem; 31 | position: relative; 32 | width: 100%; 33 | // margin-right: 5%; 34 | } 35 | 36 | .c-multirange__main { 37 | display: flex; 38 | justify-content: space-between; 39 | align-content: center; 40 | margin-bottom: 1.5rem; 41 | } 42 | 43 | .c-multirange__wrapper { 44 | position: relative; 45 | width: 100%; 46 | display: flex; 47 | align-items: center; 48 | // margin-right: 5%; 49 | } 50 | 51 | .c-multirange__display { 52 | 53 | } 54 | // Range styling 55 | 56 | .c-multirange { 57 | 58 | position: relative; 59 | 60 | input[type=range] { 61 | -webkit-appearance: none; 62 | width: 100%; 63 | margin: 8.5px 0; 64 | } 65 | input[type=range]:focus { 66 | outline: none; 67 | } 68 | input[type=range]::-webkit-slider-runnable-track { 69 | width: 100%; 70 | height: 1px; 71 | cursor: pointer; 72 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 73 | background: #524979; 74 | border-radius: 0px; 75 | border: 1.1px solid #010101; 76 | } 77 | input[type=range]::-webkit-slider-thumb { 78 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 79 | border: 2px solid #524979; 80 | height: 18px; 81 | width: 18px; 82 | border-radius: 20px; 83 | background: #ffffff; 84 | cursor: pointer; 85 | -webkit-appearance: none; 86 | margin-top: -9.6px; 87 | } 88 | input[type=range]:focus::-webkit-slider-runnable-track { 89 | background: #5d5389; 90 | } 91 | input[type=range]::-moz-range-track { 92 | width: 100%; 93 | height: 1px; 94 | cursor: pointer; 95 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 96 | background: #524979; 97 | border-radius: 0px; 98 | border: 1.1px solid #010101; 99 | } 100 | input[type=range]::-moz-range-thumb { 101 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 102 | border: 2px solid #524979; 103 | height: 18px; 104 | width: 18px; 105 | border-radius: 20px; 106 | background: #ffffff; 107 | cursor: pointer; 108 | } 109 | input[type=range]::-ms-track { 110 | width: 100%; 111 | height: 1px; 112 | cursor: pointer; 113 | background: transparent; 114 | border-color: transparent; 115 | color: transparent; 116 | } 117 | input[type=range]::-ms-fill-lower { 118 | background: #473f69; 119 | border: 1.1px solid #010101; 120 | border-radius: 0px; 121 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 122 | } 123 | input[type=range]::-ms-fill-upper { 124 | background: #524979; 125 | border: 1.1px solid #010101; 126 | border-radius: 0px; 127 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 128 | } 129 | input[type=range]::-ms-thumb { 130 | box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d; 131 | border: 2px solid #524979; 132 | height: 18px; 133 | width: 18px; 134 | border-radius: 20px; 135 | background: #ffffff; 136 | cursor: pointer; 137 | height: 1px; 138 | } 139 | input[type=range]:focus::-ms-fill-lower { 140 | background: #524979; 141 | } 142 | input[type=range]:focus::-ms-fill-upper { 143 | background: #5d5389; 144 | } 145 | } --------------------------------------------------------------------------------