├── .circleci └── config.yml ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── .watchmanconfig ├── README.md ├── app ├── app.js ├── components │ ├── .gitkeep │ ├── muuri-grid-component.js │ └── muuri-item-component.js ├── controllers │ ├── .gitkeep │ ├── index.js │ └── schema.js ├── helpers │ ├── .gitkeep │ └── is-image-type.js ├── index.html ├── lib │ ├── generateUUID.js │ └── sanctu.js ├── models │ └── .gitkeep ├── resolver.js ├── router.js ├── routes │ ├── .gitkeep │ ├── application.js │ ├── index.js │ └── schema.js ├── services │ └── extension.js ├── styles │ └── app.css └── templates │ ├── application.hbs │ ├── components │ ├── .gitkeep │ ├── check-icon.hbs │ ├── cross-icon.hbs │ ├── muuri-grid-component.hbs │ ├── muuri-item-component.hbs │ ├── pencil-icon.hbs │ └── plus-icon.hbs │ ├── index.hbs │ └── schema.hbs ├── config ├── deploy.js ├── dotenv.js ├── environment.js └── targets.js ├── dist ├── assets │ ├── auto-import-fastboot-d41d8cd98f00b204e9800998ecf8427e.js │ ├── contentful-fragment-c7a594e5271f5bf6e7069c108e0b2552.js │ ├── contentful-fragment-c8008061df33bffd6a340d2f150eca77.css │ ├── vendor-4a1d15267e4f3f19c160f2f0fcdeba97.js │ └── vendor-e785a04175e54de5481bdb16263f496d.css ├── draggable.svg ├── extension.json ├── index.html └── robots.txt ├── ember-cli-build.js ├── package.json ├── public ├── draggable.svg ├── extension.json └── robots.txt ├── testem.js ├── tests ├── helpers │ └── .gitkeep ├── index.html ├── integration │ ├── .gitkeep │ ├── components │ │ ├── muuri-grid-component-test.js │ │ └── muuri-item-component-test.js │ └── helpers │ │ └── image-type-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ ├── controllers │ ├── index-test.js │ └── schema-test.js │ ├── routes │ ├── application-test.js │ ├── index-test.js │ └── schema-test.js │ └── services │ └── extension-test.js ├── vendor └── .gitkeep └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | working_directory: ~/contentful-fragment 3 | environment: 4 | TZ: "/usr/share/zoneinfo/America/New_York" 5 | docker: 6 | - image: circleci/node:8-browsers 7 | 8 | 9 | version: 2 10 | jobs: 11 | install-dependencies: 12 | <<: *defaults 13 | steps: 14 | - checkout 15 | - attach_workspace: 16 | at: ~/contentful-fragment 17 | - restore_cache: 18 | keys: 19 | - v1-dependencies-{{ checksum "yarn.lock" }} 20 | - v1-dependencies- 21 | - run: yarn install 22 | - save_cache: 23 | paths: 24 | - node_modules 25 | key: v1-dependencies-{{ checksum "yarn.lock" }} 26 | - persist_to_workspace: 27 | root: . 28 | paths: 29 | - . 30 | 31 | run-tests: 32 | <<: *defaults 33 | steps: 34 | - attach_workspace: 35 | at: ~/contentful-fragment 36 | - run: yarn test 37 | 38 | workflows: 39 | version: 2 40 | build-n-test: 41 | jobs: 42 | - install-dependencies 43 | - run-tests: 44 | requires: 45 | - install-dependencies 46 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | 12 | # misc 13 | /coverage/ 14 | 15 | # ember-try 16 | /.node_modules.ember-try/ 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended' 13 | ], 14 | env: { 15 | browser: true 16 | }, 17 | rules: { 18 | }, 19 | overrides: [ 20 | // node files 21 | { 22 | files: [ 23 | 'ember-cli-build.js', 24 | 'testem.js', 25 | 'blueprints/*/index.js', 26 | 'config/**/*.js', 27 | 'lib/*/index.js' 28 | ], 29 | parserOptions: { 30 | sourceType: 'script', 31 | ecmaVersion: 2015, 32 | ecmaFeatures: { 33 | experimentalObjectRestSpread: true 34 | } 35 | }, 36 | env: { 37 | browser: false, 38 | node: true 39 | } 40 | } 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp/ 5 | 6 | # dependencies 7 | /bower_components/ 8 | /node_modules/ 9 | 10 | # misc 11 | /.sass-cache 12 | /connect.lock 13 | /coverage/ 14 | /libpeerconnection.log 15 | /npm-debug.log* 16 | /testem.log 17 | /yarn-error.log 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | 24 | .env 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "6" 5 | 6 | sudo: false 7 | dist: trusty 8 | 9 | addons: 10 | chrome: stable 11 | 12 | cache: 13 | directories: 14 | - $HOME/.npm 15 | 16 | env: 17 | global: 18 | # See https://git.io/vdao3 for details. 19 | - JOBS=1 20 | 21 | before_install: 22 | - npm config set spin false 23 | 24 | script: 25 | - npm run lint:js 26 | - npm test 27 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contentful Fragment 2 | 3 | Contentful Fragment is a [Contentful UI Extension](https://www.contentful.com/developers/docs/concepts/uiextensions/) that allows content 4 | modellers to add a "mini" model fragment inside of their content types. 5 | It's intended for small, repeatable pieces of content that don't necessarily 6 | warrant their own model. 7 | 8 | ## Installation on Contentful Space 9 | 10 | 1. Navigate to your Contentful Space 11 | 2. Select the "Settings" dropdown, and click "Extensions" 12 | 3. Click the "Add Extension" button, and select "Install from Github" 13 | 4. To get auto-updates on the master channel, enter `https://github.com/sanctuarycomputer/contentful-fragment/blob/master/dist/extension.json` 14 | 15 | Finally, click "Install". 16 | 17 | Now, when you're adding fields to your Content Model, you'll be able to 18 | use Contentful Fragment with any `JSON object` field type. Just navigate 19 | to the "Appearance" settings for your field, and select "Contentful Fragment"! 20 | 21 | ## Using a predefined schema for all instances 22 | 23 | In order to strictly dictate the schema for a Contentful Fragment so that 24 | your content editor can not change it (and potentially break the site), you'll 25 | need to use a predefined schema. 26 | 27 | A predefined schema is simply a string, formatted like so: 28 | ``` 29 | [Schema Key]:[Schema Type],[Schema Key]:[Schema Type] 30 | ``` 31 | 32 | For example: 33 | ``` 34 | Event Name:Symbol,Event Date:Date,Company Logo:Blob 35 | ``` 36 | 37 | To configure that schema, as a Content Modeller, navigate to the "Appearance" 38 | tab of your Contentful field, and under "Predefined Schema", enter your schema 39 | string. 40 | 41 | This will disable the schema editor from the Fragment UI, and preload that 42 | schema for your fragments to use in each instance of that model. 43 | 44 | ## Prerequisites 45 | 46 | You will need the following things properly installed on your computer. 47 | 48 | * [Git](https://git-scm.com/) 49 | * [Node.js](https://nodejs.org/) (with npm) 50 | * [Ember CLI](https://ember-cli.com/) 51 | * [Google Chrome](https://google.com/chrome/) 52 | 53 | ## Installation 54 | 55 | * `git clone ` this repository 56 | * `cd contentful-fragment` 57 | * `npm install` 58 | 59 | ## Running / Development 60 | 61 | * `yarn start` 62 | * Visit your app at [http://localhost:4200](http://localhost:4200). 63 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 64 | 65 | Don't forget to change **line 5** of **application.js** back to `const DEV = false;` before you commit your changes! 66 | 67 | ### Code Generators 68 | 69 | Make use of the many generators for code, try `ember help generate` for more details 70 | 71 | ### Running Tests 72 | 73 | * `ember test` 74 | * `ember test --server` 75 | 76 | ### Linting 77 | 78 | * `npm run lint:js` 79 | * `npm run lint:js -- --fix` 80 | 81 | ### Building 82 | 83 | * `ember build` (development) 84 | * `ember build --environment production` (production) 85 | 86 | ### Deploying 87 | 88 | Specify what it takes to deploy your app. 89 | 90 | ## Further Reading / Useful Links 91 | 92 | * [ember.js](https://emberjs.com/) 93 | * [ember-cli](https://ember-cli.com/) 94 | * Development Browser Extensions 95 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 96 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 97 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/components/.gitkeep -------------------------------------------------------------------------------- /app/components/muuri-grid-component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import Muuri from 'muuri'; 3 | import { get } from '@ember/object'; 4 | 5 | export default Component.extend({ 6 | classNames: ['muuri-grid-component'], 7 | didInsertElement() { 8 | this._super(...arguments); 9 | this.grid = new Muuri(this.element, { 10 | dragEnabled: true, 11 | dragSortInterval: 100, 12 | dragReleaseDuration: 300, 13 | dragReleaseEasing: 'ease', 14 | dragStartPredicate: { 15 | handle: '.cf-card-icon' 16 | } 17 | }); 18 | this.grid.on('layoutEnd', (items) => { 19 | const newOrder = items.map(item => item.getElement().dataset.id); 20 | return get(this, 'updateSort')(newOrder); 21 | }); 22 | }, 23 | didUpdate() { 24 | const grid = get(this, 'grid'); 25 | const prevItems = get(this, 'grid._items'); 26 | const prevItemIds = prevItems.map(item => item._element.getAttribute('data-id')); 27 | 28 | const currentItemIds = []; 29 | const currentGridElements = document.getElementsByClassName('muuri-item-component'); 30 | 31 | for (var i = 0; i < currentGridElements.length; i++) { 32 | const itemEl = currentGridElements[i]; 33 | const uuid = itemEl.getAttribute('data-id'); 34 | 35 | if (!!prevItems[i] && itemEl.clientHeight !== prevItems[i]._height) { 36 | grid.refreshItems().layout() 37 | } 38 | 39 | if (!prevItemIds.includes(uuid)) { 40 | grid.add(itemEl); 41 | } 42 | 43 | currentItemIds.push(uuid); 44 | } 45 | 46 | prevItems.forEach(item => { 47 | const itemEl = item._element 48 | if (!currentItemIds.includes(itemEl.getAttribute('data-id'))) { 49 | grid.remove(itemEl); 50 | } 51 | }); 52 | 53 | grid.refreshItems(); 54 | }, 55 | willRemoveElement() { 56 | delete this.grid; 57 | } 58 | }); -------------------------------------------------------------------------------- /app/components/muuri-item-component.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { computed } from '@ember/object'; 3 | 4 | export default Component.extend({ 5 | classNames: ['muuri-item-component', 'mb1'], 6 | classNameBindings: ['isEditing:muuri-item-component--editing'], 7 | attributeBindings: ['uuid:data-id'], 8 | modelId: computed('uuid', function () { 9 | return this.get('uuid'); 10 | }) 11 | }); -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { get, set } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default Controller.extend({ 6 | extension: service(), 7 | fileQueue: service(), 8 | 9 | editingUUID: "", 10 | showPreview: false, 11 | 12 | actions: { 13 | addFragment() { 14 | const newFragment = get(this, 'extension').addFragment(); 15 | if (!newFragment) return; 16 | const uuid = get(newFragment.findBy('key', 'uuid'), 'value'); 17 | // TODO: re-enable this after muuri rerender on "iEditing" 18 | // bug is fixed 19 | // set(this, 'editingUUID', uuid); 20 | return uuid; 21 | }, 22 | 23 | editFragment(uuid) { 24 | set(this, 'editingUUID', uuid); 25 | }, 26 | 27 | cancelEditing() { 28 | set(this, 'editingUUID', ""); 29 | }, 30 | 31 | removeFragment(fragment) { 32 | get(this, 'extension').removeFragment(fragment); 33 | set(this, 'editingUUID', ""); 34 | }, 35 | 36 | // Savers 37 | save() { 38 | get(this, 'extension').persist(); 39 | }, 40 | 41 | saveDate(fragmentField, date) { 42 | if (date) { 43 | set(fragmentField, 'value', date.toISOString()); 44 | get(this, 'extension').persist(); 45 | } 46 | }, 47 | 48 | saveBlob(fragmentField, file) { 49 | if (!file) return; 50 | 51 | const { name, size, type } = file; 52 | return file.readAsDataURL().then(data => { 53 | set(fragmentField, 'value', { 54 | data, name, size, type 55 | }); 56 | return get(this, 'extension').persist(); 57 | }).finally(() => { 58 | const queue = get(this, 'fileQueue.queues').find(queue => get(queue, 'files').includes(file)); 59 | if (queue) queue.remove(file); 60 | }); 61 | }, 62 | 63 | showPreview() { 64 | set(this, 'showPreview', true); 65 | }, 66 | 67 | hidePreview() { 68 | set(this, 'showPreview', false); 69 | }, 70 | 71 | updateSort(order) { 72 | get(this, 'extension').updateSort(order); 73 | } 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /app/controllers/schema.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { get, set } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default Controller.extend({ 6 | extension: service(), 7 | 8 | deletingUUID: "", 9 | 10 | actions: { 11 | stageFieldDeletion(field) { 12 | set(this, 'deletingUUID', field.uuid); 13 | }, 14 | unstageFieldDeletion() { 15 | set(this, 'deletingUUID', ""); 16 | }, 17 | addEmptySchemaField() { 18 | get(this, 'extension').addSchemaField(); 19 | }, 20 | keyDidChange() { 21 | get(this, 'extension').validateSchema(); 22 | }, 23 | setFieldType(value) { 24 | const [uuid, type] = value.split('-'); 25 | const field = get(this, 'extension.data._schema').findBy('uuid', uuid); 26 | set(field, 'type', type); 27 | get(this, 'extension').validateSchema(); 28 | }, 29 | cancel() { 30 | this.transitionToRoute('index'); 31 | }, 32 | save() { 33 | get(this, 'extension').persist().then(() => { 34 | get(this, 'extension').syncFragmentsToSchema(); 35 | this.transitionToRoute('index'); 36 | }); 37 | }, 38 | removeFragmentField(field) { 39 | get(this, 'extension').removeSchemaField(field); 40 | } 41 | } 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/helpers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/is-image-type.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function imageType(params/*, hash*/) { 4 | const type = params[0] || ""; 5 | return type.startsWith('image/'); 6 | } 7 | 8 | export default helper(imageType); 9 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ContentfulFragment 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{content-for "head-footer"}} 20 | 21 | 22 | {{content-for "body"}} 23 | 24 | 48 | 49 | 50 | 51 | 52 | {{content-for "body-footer"}} 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/lib/generateUUID.js: -------------------------------------------------------------------------------- 1 | export default function generateUUID() { 2 | let d = new Date().getTime(); 3 | if (window.performance && typeof window.performance.now === 'function') { 4 | d += performance.now(); // use high-precision timer if available 5 | } 6 | const uuid = 'xxxxxx'.replace(/[xy]/g, (c) => { 7 | const r = (d + Math.random() * 16) % 16 | 0; 8 | d = Math.floor(d / 16); 9 | return (c === 'x' ? r : (r&0x3|0x8)).toString(16); 10 | }); 11 | return uuid; 12 | } 13 | -------------------------------------------------------------------------------- /app/lib/sanctu.js: -------------------------------------------------------------------------------- 1 | export default "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBARXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAACW6ADAAQAAAABAAACWwAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8IAEQgCWwJbAwERAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAMCBAEFAAYHCAkKC//EAMMQAAEDAwIEAwQGBAcGBAgGcwECAAMRBBIhBTETIhAGQVEyFGFxIweBIJFCFaFSM7EkYjAWwXLRQ5I0ggjhU0AlYxc18JNzolBEsoPxJlQ2ZJR0wmDShKMYcOInRTdls1V1pJXDhfLTRnaA40dWZrQJChkaKCkqODk6SElKV1hZWmdoaWp3eHl6hoeIiYqQlpeYmZqgpaanqKmqsLW2t7i5usDExcbHyMnK0NTV1tfY2drg5OXm5+jp6vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAQIAAwQFBgcICQoL/8QAwxEAAgIBAwMDAgMFAgUCBASHAQACEQMQEiEEIDFBEwUwIjJRFEAGMyNhQhVxUjSBUCSRoUOxFgdiNVPw0SVgwUThcvEXgmM2cCZFVJInotIICQoYGRooKSo3ODk6RkdISUpVVldYWVpkZWZnaGlqc3R1dnd4eXqAg4SFhoeIiYqQk5SVlpeYmZqgo6SlpqeoqaqwsrO0tba3uLm6wMLDxMXGx8jJytDT1NXW19jZ2uDi4+Tl5ufo6ery8/T19vf4+fr/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2gAMAwEAAhEDEQAAAf38rVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWriq6+jVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1ahV8dV+bdfm3XylXS1+jVfpJX6LV6NWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq87r86a/Nuvzkr1+v0kr9JK+4q8rr836/Nuvz5r6er9JK/SWvsKi1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVqFXx/X5t1+bdfL1foJX6SV+kFezVq1atWrVXV8OV+bdfm3Xklfo5X6SV+ileh1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq8+r866/Nuvzjr1av0lr9JK+5asa1atWrVq1atWrVq1eL1+b9fm1X5+19SV+klfpJX1/RK1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrUOvkOvzbr826+V6/QGv0kr9Ia9qrVq1atWrVq1atWrVq1atWrVqra+GK/Nuvzbryyv0er9JK/ROu/rVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atXA1+dtfm3X5w16fX6SV+klfdFWdatWrVq1atWrVq1atWrVq1atWrVq1atWrxOvzer826/P+vquv0kr9JK+u6XWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX8fdfKVfftfpJX6RV7dWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1VdfCtfm3X5t15rX9Gdfq7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4H6/vLq1rVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4EUCv3+rVq1atWrVq1atWrVq1atWrVq1atWrVq1atX8EVf3u1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV+EFUFfv9WrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/wBWrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq/gir+92tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1avwgqgr9/q1atWrVq1atWrVq1atWrVq1atWrVq1atWrV/BFX97tatWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fhBVBX7/Vq1atWrVq1atWrVq1atWrVq1atWrVq1atWr+CKv73a1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq/CCqCv3+rVq1atWrVq1atWrVq1atWrVq1atWrVq1atX8EVf3u1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV+EFUFfv9WrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/wBWrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq/gir+92tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1avwgqgr9/q1atWrVq1atWrVq1atWrVq1atWrVq1atWrV/BFX97tatWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fhBVBX7/Vq1atWrVq1atWrVq1atWrVq1atWrVq1atWr+CKv73a1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq/CCqCv3+rVq1atWrVq1atWrVq1atWrVq1atWrVq1atX8EVf3u1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV+EFUFfv9WrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/wBWrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrV5xX4L18s0GvSq/cOvs6or+ZqvmOryv6pa9Br8Hq/N6tX7m161X87FfvDX2hX4ZV8eVV17rX7X1+F1eP1q1av3hr8Hq/aGv1hr+ZqvmKv7Aq1avwgqgr9/q1atWrVq1atWrVq1atWrVq1atWrVq1atWrV/BFX97tatX48V/MVX2/X6x187V+iVfpjX5TV/K5X6hV+XtftJX9LFfzA1+QNfS1c5X9HFfyqV/T7X5SV+btf1R17DX4SV9X1+y1fk5X8+lftBX7dV8RV/MFX9FFfupX8adfF9f3+Vq1fhBVBX7/Vq1atWrVq1atWrVq1atWrVq1atWrVq1atWr+CKv73a1avIK/H+vmavlaviSv1Yr+muv4o686r+gGvwUrkK/sar8Wq/IGv64a/khr69r4/r+n2vwxr5Qr+1+vd6/KKu4r9S6/HGv5ka/e+v6Da/Iyv5dq/oGr99q/iwr5dr+/GtWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrV+dFfymV9EV9T1+W9ft5VLX4k1/QdXtteR1/PBX2LX1XX5H1/erX8ztfkZWr+nqu/r+Z6u1rt6+bq/f2v3Hr8c6/mUr97K/oLryCv5MK+Z6+oq+Qq/biv6Ka1avwgqgr9/q1atWrVq1atWrVq1atWrVq1atWrVq1atWrV/BFX97tatWqvryekV6pVvWrVq1atWrVq1atWrVqFXk9Nq9bp3WrVq1atWrz+uRr1OrutWrV+EFUFfv9WrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4Iq/vdrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWr8IKoK/f6tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1fwRV/e7WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX4QVQV+/wBWrVq1atWrVq1atWrVq1atWrVq1atWrVq1av4D6/Y2tWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1avj+v0lr9/q1atWrVq1atWrVq1atWrVq1atWrVq1atWrV+QtatWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atX17X17WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV5/XwVXm1d5X3TXrNavk6vnOnVfV9fQlfPtfPtavpavbq+La42tX1FXy7WrV1VfY1ctXi1et16XXzvVXX05WrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atXylX8eNel1+ylfg9TGv7eK/nVr8gK/USqWvzXr9x67WvwBq8r9Qa/pdr+GOk1R1/T7X8wVfTlff9fjJX6WV+rVfy81/RnX7k1/F3XhFf3d1q1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV+DVfz0V/QBX7+V53VVUV/C9XrVf3P03r+Diuer93K/Bmv1tr8s6/oFr8FK/Qevy9r+n2v5gq+qK/Q6vxJr9Ba/YGv5ea/oyr9yq/i6rwmv7u61atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq+Dq/kFr6xr92K/nnrxqv7SK/kWrxWv6uq52v5Wa+o6/VWvwDr+wiv4/wCrSv2przCvyJr+n2v5gq+7q/ear6vuGvhWv5Fq/Tmv2nr+V2vWK/tmrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrV8x1+dNcDXp9fo5X0LXhlfitXy1R6+tq/aevl+vgOv3Cr8mq8ir9ha+M6+Rq/TuvzEr6mr9U61atX5k1+e9ctXttfrvX0HWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atWrVq1atW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AAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAJAIJBAABIIIBBAAIAIABAAAAAAAAAAAAAAABABABIAAAIAIJAJBIIIIIABAAAAAAAAAAAAAAABABJIIJIAIJIJBJAIAAAJABAAAAAAAAAAAAAAABABAJABJIAAAJIIAIIABBABAAAAAAAAAAAAAAABAAJAAAAAAAAAJJAAAABIABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAABJJJJJJJJJJJJJJJJJJJJJJAAAAAAAAAAAAAAABJJJJJJJJJJJJJJJJJJJJJJIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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"; 2 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/models/.gitkeep -------------------------------------------------------------------------------- /app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootRouterURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('schema'); 11 | }); 12 | 13 | export default Router; 14 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/routes/.gitkeep -------------------------------------------------------------------------------- /app/routes/application.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { get } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | import sanctuLogo from 'contentful-fragment/lib/sanctu'; 5 | 6 | const DUMMY_DATA = { 7 | "_schema": [ 8 | { 9 | "key": "Event Location", 10 | "type": "Symbol", 11 | "uuid": "6a92b9" 12 | }, 13 | { 14 | "key": "Event Url", 15 | "type": "Symbol", 16 | "uuid": "6a27d9" 17 | }, 18 | { 19 | "key": "Event Date", 20 | "type": "Date", 21 | "uuid": "a592h9" 22 | }, 23 | { 24 | "key": "Logo", 25 | "type": "Blob", 26 | "uuid": "a592f3" 27 | } 28 | ], 29 | "fragments": [ 30 | [ 31 | { 32 | "key": "uuid", 33 | "value": "c36d6d" 34 | }, 35 | { 36 | "key": "Event Location", 37 | "value": "Sanctuary Computer Inc", 38 | "type": "Symbol", 39 | "_schemaRef": "6a92b9" 40 | }, 41 | { 42 | "key": "Event Url", 43 | "value": "https://www.google.com/maps/place/Sanctuary+Computer/@40.71811,-73.997507,17z/data=!3m1!4b1!4m5!3m4!1s0x89c259880e5637e3:0xcdc06390643521f5!8m2!3d40.71811!4d-73.995313", 44 | "type": "Symbol", 45 | "_schemaRef": "6a27d9" 46 | }, 47 | { 48 | "key": "Event Date", 49 | "value": new Date().setDate(new Date().getDate() + 1), 50 | "type": "Date", 51 | "_schemaRef": "a592h9" 52 | }, 53 | { 54 | "key": "Logo", 55 | "value": { 56 | "data": sanctuLogo, 57 | "name": "default logo.jpg", 58 | "size": 1859, 59 | "type": "image/png" 60 | }, 61 | "type": "Blob", 62 | "_schemaRef": "a592f3" 63 | } 64 | ] 65 | ] 66 | }; 67 | 68 | const DUMMY_SCHEMA_SHORTHAND = null; //"Event Location:Symbol,Event Url: Symbol,Event Date:Date,Logo:Blob" 69 | 70 | const DummyExtension = { 71 | parameters: { 72 | instance: { 73 | schemaShorthand: DUMMY_SCHEMA_SHORTHAND 74 | } 75 | }, 76 | field: { 77 | _value: DUMMY_DATA, 78 | getValue: function() { 79 | return { ...DummyExtension.field._value }; 80 | }, 81 | setValue: function(newValue) { 82 | DummyExtension.field._value = newValue; 83 | window.newValue = newValue; 84 | return new Promise(resolve => resolve(newValue)); 85 | } 86 | } 87 | }; 88 | 89 | export default Route.extend({ 90 | extension: service(), 91 | 92 | model() { 93 | const isDummy = ['localhost', 'contentful-fragment.io'].includes(window.location.hostname) || window.location.pathname === "/dummy"; 94 | if (isDummy) { 95 | document.body.style.overflow = "auto"; 96 | document.body.style.padding = "10px 10px 0px 10px"; 97 | } 98 | if (isDummy) return DummyExtension; 99 | 100 | return new Promise(window.contentfulFragment.getExtension, DummyExtension); 101 | }, 102 | 103 | afterModel(contentfulExtension) { 104 | get(this, 'extension').setup(contentfulExtension); 105 | } 106 | }); 107 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/routes/schema.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/services/extension.js: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | import { get, set } from '@ember/object'; 3 | 4 | const TYPES = [ 5 | 'Symbol', 6 | 'Date', 7 | 'Blob' 8 | ]; 9 | 10 | const emptyForType = (/*type*/) => { 11 | // TODO 12 | return null; 13 | }; 14 | 15 | const coerce = (type, value) => { 16 | if (!value) return emptyForType(type); 17 | return value; 18 | }; 19 | 20 | const generateUUID = () => { 21 | let d = new Date().getTime(); 22 | if (window.performance && typeof window.performance.now === 'function') { 23 | d += performance.now(); // use high-precision timer if available 24 | } 25 | const uuid = 'xxxxxx'.replace(/[xy]/g, (c) => { 26 | const r = (d + Math.random() * 16) % 16 | 0; 27 | d = Math.floor(d / 16); 28 | return (c === 'x' ? r : (r&0x3|0x8)).toString(16); 29 | }); 30 | return uuid; 31 | }; 32 | 33 | const validateSchemaField = field => { 34 | let validation = ''; 35 | if (!field.key || field.key.length === 0) validation += 'Enter a key for this field'; 36 | if (!field.type) { 37 | if (validation.length) validation += ', '; 38 | validation += 'Select a field type'; 39 | } 40 | return validation; 41 | }; 42 | 43 | const newFragmentFromSchema = schema => { 44 | return schema.reduce((acc, field) => { 45 | return [ 46 | ...acc, 47 | { 48 | key: field.key, 49 | value: emptyForType(field.type), 50 | type: field.type, 51 | _schemaRef: field.uuid 52 | } 53 | ]; 54 | }, [{ 55 | key: 'uuid', 56 | value: generateUUID() 57 | }]); 58 | }; 59 | 60 | export default Service.extend({ 61 | data: null, 62 | extension: null, 63 | preview: null, 64 | 65 | setup(extension) { 66 | set(this, 'data', extension.field.getValue() || {}); 67 | set(this, 'extension', extension); 68 | 69 | this.loadSchemaFromShorthand(); 70 | this.syncFragmentsToSchema(); 71 | this.makeSimpleFragments(); 72 | }, 73 | 74 | setSetting(key, value) { 75 | set(this, 'data._settings', get(this, 'data._settings') || {}); 76 | set(this, `data._settings.${key}`, value); 77 | }, 78 | 79 | loadSchemaFromShorthand() { 80 | const shorthand = (get(this, 'extension.parameters.instance.schemaShorthand') || ""); 81 | if (!shorthand.length) return; 82 | 83 | const parsedSchemaFields = 84 | shorthand.split(",").map(tuple => tuple.split(":").map(t => t.trim())); 85 | const existingSchema = get(this, 'data._schema') || []; 86 | 87 | const loadedSchema = parsedSchemaFields.reduce((acc, fieldTuple) => { 88 | const [key, type] = fieldTuple; 89 | if (key.length === 0) { 90 | // eslint-disable-next-line 91 | console.warn( 92 | `Contentful Fragment: Your Predefined Schema included a blank schema key! Please refer to the documentation.` 93 | ); 94 | return acc; 95 | } 96 | if (!TYPES.includes(type)) { 97 | // eslint-disable-next-line 98 | console.warn( 99 | `Contentful Fragment: Your Predefined Schema included unknown type: ${type}. Must be one of <${TYPES.join(' ')}>` 100 | ); 101 | return acc; 102 | } 103 | const match = existingSchema.findBy('key', fieldTuple[0]); 104 | return [...acc, { 105 | uuid: (match ? match.uuid : generateUUID()), key, type 106 | }]; 107 | }, []).filter(schemaField => { 108 | return parsedSchemaFields.find(schemaTuple => schemaTuple[0] === schemaField.key); 109 | }); 110 | 111 | this.setSetting('usesPredefinedSchema', true); 112 | set(this, 'data._schema', loadedSchema); 113 | this.validateSchema(); 114 | }, 115 | 116 | /* Main Editor */ 117 | addFragment() { 118 | if (get(this, 'data._schema')) { 119 | set(this, 'data.fragments', get(this, 'data.fragments') || []); 120 | const newFragment = newFragmentFromSchema(get(this, 'data._schema')); 121 | get(this, 'data.fragments').pushObject(newFragment); 122 | this.persist(); 123 | return newFragment; 124 | } 125 | }, 126 | 127 | removeFragment(fragment) { 128 | if (get(this, 'data.fragments')) { 129 | const uuid = get(fragment.findBy('key', 'uuid'), 'value'); 130 | const newFragments = this.data.fragments.reject(fragment => { 131 | return get(fragment.findBy('key', 'uuid'), 'value') === uuid; 132 | }); 133 | set(this, 'data.fragments', newFragments); 134 | this.persist(); 135 | } 136 | }, 137 | 138 | /* Schema Editor */ 139 | validateSchema() { 140 | const allValid = get(this, 'data._schema').map(schemaField => { 141 | const validation = validateSchemaField(schemaField); 142 | set(schemaField, 'validation', validation); 143 | 144 | return (validation.length === 0) 145 | }).every(Boolean); 146 | 147 | set(this, 'data.valid', allValid); 148 | }, 149 | 150 | syncFragmentsToSchema() { 151 | const { fragments, _schema = [] } = get(this, 'data'); 152 | const syncedFragments = (fragments || []).map(fragment => { 153 | const syncedFragment = _schema.map(schemaField => { 154 | const dataForSchemaField = fragment.findBy('_schemaRef', schemaField.uuid); 155 | return { 156 | key: schemaField.key, 157 | type: schemaField.type, 158 | value: coerce(schemaField.type, dataForSchemaField ? dataForSchemaField.value : null), 159 | _schemaRef: schemaField.uuid, 160 | }; 161 | }); 162 | return [fragment.findBy('key', 'uuid'), ...syncedFragment]; 163 | }); 164 | 165 | set(this, 'data.fragments', syncedFragments); 166 | this.persist(); 167 | }, 168 | 169 | addSchemaField() { 170 | const newField = { key: '', type: null, uuid: generateUUID(), validation: '' }; 171 | set(newField, 'validation', validateSchemaField(newField)); 172 | if (get(this, 'data._schema')) { 173 | get(this, 'data._schema').pushObject(newField); 174 | } else { 175 | set(this, 'data._schema', [newField]); 176 | } 177 | 178 | return newField; 179 | }, 180 | 181 | removeSchemaField(field) { 182 | const newFields = get(this, 'data._schema').reject(existingField => { 183 | return existingField.uuid === field.uuid; 184 | }); 185 | set(this, 'data._schema', newFields); 186 | }, 187 | 188 | makeSimpleFragments() { 189 | const simpleFragments = (get(this, 'data.fragments') || []).reduce((simpleFragments, fragment, index) => { 190 | const uuid = get(fragment.findBy('key', 'uuid'), 'value'); 191 | 192 | simpleFragments[uuid] = fragment.reduce((simpleFragment, fragmentField) => { 193 | if (fragmentField.key === "uuid") return simpleFragment; 194 | if (fragmentField.key) { 195 | simpleFragment[fragmentField.key.camelize()] = fragmentField.value; 196 | } 197 | 198 | return { 199 | index, 200 | uuid, 201 | ...simpleFragment 202 | }; 203 | }, {}); 204 | 205 | return simpleFragments; 206 | }, {}); 207 | 208 | set(this, 'data.simpleFragments', simpleFragments); 209 | }, 210 | 211 | generateJSONPreview() { 212 | try { 213 | const jsonPreview = JSON.stringify(this.data, null, 2); 214 | set(this, 'preview', jsonPreview); 215 | } catch (e) { 216 | set(this, 'preview', 'Could not generate preview'); 217 | } 218 | }, 219 | 220 | updateSort(order) { 221 | const fragments = get(this, 'data.fragments'); 222 | const resorted = order.map(uuid => fragments.find(fragment => { 223 | const fragmentUuid = get(fragment.findBy('key', 'uuid'), 'value'); 224 | return uuid === fragmentUuid 225 | })); 226 | 227 | set(this, 'data.fragments', resorted); 228 | this.persist(); 229 | }, 230 | 231 | persist() { 232 | this.makeSimpleFragments(); 233 | this.generateJSONPreview(); 234 | return this.extension.field.setValue(get(this, 'data')); 235 | }, 236 | }); 237 | -------------------------------------------------------------------------------- /app/styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | margin: 0; 4 | } 5 | 6 | .green { 7 | color: #14d997; 8 | } 9 | 10 | .red { 11 | color: #fe5c60; 12 | } 13 | 14 | .transition { 15 | transition: all .1s ease-in-out; 16 | } 17 | 18 | .pointer:hover { 19 | cursor: pointer; 20 | } 21 | 22 | .events-none { 23 | pointer-events: none; 24 | } 25 | 26 | .events-all { 27 | pointer-events: all; 28 | } 29 | 30 | .icon-wrapper { 31 | display: inline-block; 32 | } 33 | 34 | a:hover .icon-small { 35 | fill: #2a3039; 36 | } 37 | 38 | .mb0 { 39 | margin-bottom: 0 !important; 40 | } 41 | 42 | .mb1 { 43 | margin-bottom: 0.642857142857143em; 44 | } 45 | 46 | .mb2 { 47 | margin-bottom: 1.928571428571429em; 48 | } 49 | 50 | .icon-small { 51 | display: inline-block; 52 | fill: #3c80cf; 53 | vertical-align: middle; 54 | transition: fill .1s ease-in-out; 55 | position: relative; 56 | bottom: 1px; 57 | } 58 | 59 | .icon-small.red { 60 | fill: #fe5c60; 61 | } 62 | 63 | .plus-icon { 64 | width: 1.125rem; 65 | height: 1.125rem; 66 | } 67 | 68 | .cross-icon { 69 | width: 0.75rem; 70 | height: 0.75rem; 71 | } 72 | 73 | .check-icon { 74 | width: 0.875rem; 75 | height: 0.875rem; 76 | } 77 | 78 | .pencil-icon { 79 | width: 1.0625rem; 80 | height: 1rem; 81 | } 82 | 83 | .red { 84 | color: #fe5c60; 85 | } 86 | 87 | hr { 88 | border: 0; 89 | height: 0; 90 | border-bottom: 1px solid #d3dce0; 91 | } 92 | 93 | .cf-card { 94 | border: 1px solid #d3dce0; 95 | -webkit-border-radius: 2px; 96 | border-radius: 2px; 97 | display: -webkit-box; 98 | display: -moz-box; 99 | display: -webkit-flex; 100 | display: -ms-flexbox; 101 | display: box; 102 | display: flex; 103 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.08); 104 | box-shadow: 0 1px 3px rgba(0,0,0,0.08); 105 | background: #fff; 106 | -webkit-transition: all 200ms ease-in-out; 107 | -moz-transition: all 200ms ease-in-out; 108 | -o-transition: all 200ms ease-in-out; 109 | -ms-transition: all 200ms ease-in-out; 110 | transition: all 200ms ease-in-out; 111 | -webkit-transform: translate3d(0, 0, 0); 112 | -moz-transform: translate3d(0, 0, 0); 113 | -o-transform: translate3d(0, 0, 0); 114 | -ms-transform: translate3d(0, 0, 0); 115 | transform: translate3d(0, 0, 0); 116 | } 117 | 118 | .cf-card-inner { 119 | -webkit-box-flex: 1; 120 | -moz-box-flex: 1; 121 | -o-box-flex: 1; 122 | box-flex: 1; 123 | -webkit-flex: 1 0 0; 124 | -ms-flex: 1 0 0; 125 | flex: 1 0 0; 126 | padding: 14px; 127 | max-width: calc(100% - 19px); 128 | } 129 | 130 | .cf-card-status { 131 | font-size: 12px; 132 | text-transform: uppercase; 133 | font-weight: 600; 134 | letter-spacing: 0.1em; 135 | } 136 | 137 | .cf-card-field { 138 | text-overflow: ellipsis; 139 | max-width: 20rem; 140 | margin-bottom: .5rem; 141 | } 142 | 143 | .cf-card-field--title { 144 | margin-bottom: 0rem; 145 | color: #8091a5; 146 | font-size: 0.875rem; 147 | max-width: 50rem; 148 | } 149 | 150 | .cf-card-field--content { 151 | margin-top: 0rem; 152 | font-size: 1rem; 153 | color: black; 154 | text-overflow: ellipsis; 155 | white-space: nowrap; 156 | overflow: hidden; 157 | } 158 | 159 | .pika-single { 160 | font-family: "Avenir Next W01", Helvetica, sans-serif; 161 | text-rendering: optimizeLegibility; 162 | font-size: 14px; 163 | line-height: 18px; 164 | color: #2d2f31; 165 | } 166 | 167 | .blob-preview img { 168 | max-width: 100px; 169 | } 170 | 171 | .blob-preview .blob-meta { 172 | display: inline-block; 173 | } 174 | 175 | .blob-preview .blob-no-preview { 176 | height: 100%; 177 | background-color: #d3dce0; 178 | } 179 | 180 | .json-preview { 181 | height: 200px; 182 | border: 1px solid #c3d0d5; 183 | -webkit-border-radius: 2px; 184 | border-radius: 2px; 185 | background-color: #f7f9fa; 186 | overflow: scroll; 187 | width: 100%; 188 | font-family: monospace; 189 | font-size: .75rem; 190 | line-height: 1rem; 191 | } 192 | 193 | .cf-card { 194 | display: flex; 195 | transition: all 200ms ease-in-out; 196 | } 197 | .cf-card-icon { 198 | display: flex; 199 | align-items: center; 200 | justify-content: center; 201 | width: 1.25rem; 202 | border-right: 1px solid #d3dce0; 203 | background-color: #f7f9fa; 204 | cursor: grab; 205 | transition: background-color 200ms ease-in-out; 206 | } 207 | .cf-card-icon:hover { 208 | background-color: #e5ebed; 209 | } 210 | 211 | .cf-form-field { 212 | margin-bottom: 1rem !important; 213 | } 214 | 215 | .cf-form-field:last-of-type { 216 | margin-bottom: 2rem !important; 217 | } 218 | 219 | .muuri-grid-component { 220 | position: relative; 221 | display: flex; 222 | flex-wrap: wrap; 223 | } 224 | .muuri-item-component { 225 | display: block; 226 | position: absolute; 227 | width: 100%; 228 | z-index: 1; 229 | } 230 | .muuri-item-component--editing { 231 | z-index: 4; 232 | } 233 | .item-content > .cf-card:hover { 234 | border: solid #3c80cf 1px; 235 | } 236 | .muuri-item-component--editing .item-content > .cf-card { 237 | box-shadow: 0px 6px 12px -3px rgba(0,0,0,0.3); 238 | } 239 | .muuri-item-dragging, .muuri-item-releasing { 240 | z-index: 3; 241 | } 242 | .item-content { 243 | position: relative; 244 | } 245 | 246 | .border { 247 | border: solid red; 248 | } 249 | -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} 2 | 3 |
4 |

5 | Contentful Fragment was built by Sanctuary Computer in NYC. 6 |

7 |
8 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanctuarycomputer/contentful-fragment/c61fa2a8e15cbec06acc7d5ecb8acafd09af9c8e/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /app/templates/components/check-icon.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/templates/components/cross-icon.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/templates/components/muuri-grid-component.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} -------------------------------------------------------------------------------- /app/templates/components/muuri-item-component.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | {{yield}} 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /app/templates/components/pencil-icon.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/templates/components/plus-icon.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if extension.data._schema}} 2 | {{#muuri-grid-component 3 | updateSort=(action 'updateSort') 4 | editingUUID=editingUUID 5 | }} 6 | {{#each extension.data.fragments as |fragment index|}} 7 | {{#with (get (find-by 'key' 'uuid' fragment) 'value') as |uuid|}} 8 | {{#muuri-item-component uuid=uuid isEditing=(eq editingUUID uuid)}} 9 | {{!-- card content --}} 10 |
11 | {{#each fragment as |fragmentField|}} 12 | {{#if (not-eq fragmentField.key 'uuid')}} 13 |
14 | {{#if (eq editingUUID uuid)}} 15 | 64 | {{else}} 65 | {{#if (eq fragmentField.type 'Symbol')}} 66 |

67 | {{fragmentField.key}} 68 |

69 |

70 | {{fragmentField.value}} 71 |

72 | {{/if}} 73 | 74 | {{#if (eq fragmentField.type 'Date')}} 75 |

76 | {{fragmentField.key}} 77 |

78 |

79 | {{if fragmentField.value (moment-format (utc fragmentField.value) 'dddd, Do MMMM YYYY')}} 80 |

81 | {{/if}} 82 | 83 | {{#if (eq fragmentField.type 'Blob')}} 84 |

85 | {{fragmentField.key}} 86 |

87 | {{#if fragmentField.value}} 88 |
89 |
90 | {{#if (is-image-type fragmentField.value.type)}} 91 | 92 | {{else}} 93 |
94 |

No Preview Available

95 |
96 | {{/if}} 97 |
98 |

File Name: {{fragmentField.value.name}}

99 |

File Size: {{fragmentField.value.size}}

100 |

File Type: {{fragmentField.value.type}}

101 |
102 |
103 |
104 | {{else}} 105 |
106 | {{/if}} 107 | {{/if}} 108 | {{/if}} 109 |
110 | {{/if}} 111 | {{/each}} 112 | 113 |
114 | {{#cross-icon}} 115 | {{/cross-icon}} 116 |
117 | Delete Entry 118 |
119 |
120 | 121 | {{!-- edit or cancel button --}} 122 |
123 | {{#if (eq editingUUID uuid)}} 124 |
125 | {{#check-icon}} 126 | {{/check-icon}} 127 |
128 | {{else}} 129 |
130 | {{#pencil-icon}} 131 | {{/pencil-icon}} 132 |
133 | {{/if}} 134 |
135 | {{/muuri-item-component}} 136 | {{/with}} 137 | {{else}} 138 |

139 | Hey! You haven't added any entries yet. 140 | Add one now! 141 |

142 | {{/each}} 143 | {{/muuri-grid-component}} 144 | {{!-- Add / Edit buttons --}} 145 |
146 | 147 |
148 | {{#plus-icon}} 149 | {{/plus-icon}} 150 |
151 | Add Entry 152 |
153 | {{#unless extension.data._settings.usesPredefinedSchema}} 154 | {{#link-to 'schema' class='ml2 transition'}} 155 |
156 | {{#plus-icon}} 157 | {{/plus-icon}} 158 |
159 | Edit Fragment 160 | {{/link-to}} 161 | {{/unless}} 162 | 163 | {{!-- JSON Preview --}} 164 | 183 |
184 | {{#if showPreview}} 185 |