├── .bowerrc ├── .buildpacks ├── .firebaserc ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── Readme.md ├── bower.json ├── config └── nginx.conf.erb ├── design └── icons.ai ├── firebase.json ├── grunt ├── aliases.yml ├── css-tasks.js ├── deploy-tasks.js ├── fonts-tasks.js ├── images-tasks.js ├── js-tasks.js ├── misc-tasks.js └── templates-tasks.js ├── package.json ├── src ├── favicons │ ├── android-chrome-144x144.png │ ├── android-chrome-192x192.png │ ├── android-chrome-36x36.png │ ├── android-chrome-48x48.png │ ├── android-chrome-72x72.png │ ├── android-chrome-96x96.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── safari-pinned-tab.svg ├── fonts │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-Regular.woff2 │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ └── Roboto-Thin.woff2 ├── img │ ├── documentation │ │ └── headers.png │ ├── examples │ │ ├── demo_spreadsheet.png │ │ └── firebase.png │ ├── getting-started │ │ ├── copy-template-to-drive.png │ │ └── customise-endpoint.png │ ├── github.svg │ ├── google-drive-cms-1.png │ ├── google-drive-cms-1.svg │ ├── google-drive-cms-2.svg │ ├── google-drive-cms-3.svg │ ├── howto__publish.gif │ ├── meta │ │ └── social.PNG │ └── writer.jpg ├── js │ ├── addToDrive.js │ ├── app.js │ ├── documentation.js │ └── index.js ├── scss │ ├── _documentation.scss │ ├── _extra_colors.scss │ ├── _index.scss │ ├── _math.scss │ ├── _mixins.scss │ ├── _settings.scss │ ├── _typed.scss │ ├── _utilities.scss │ ├── app.scss │ └── fonts.scss └── templates │ ├── data │ ├── data.json │ ├── features.json │ ├── instructions.json │ ├── meta.json │ ├── navigation.json │ └── readme.json │ ├── includes │ ├── components │ │ ├── _add-to-drive.html │ │ ├── _add-to-drive__error.html │ │ ├── _add-to-drive__error_modal_upload.html │ │ ├── _add-to-drive__success.html │ │ ├── _add-to-drive__updateEndpoint.html │ │ ├── _loader.html │ │ ├── _signupform.html │ │ └── _standard_hero.html │ ├── favicon.html │ ├── footer.html │ ├── header.html │ ├── index │ │ ├── _feature.html │ │ ├── _instruction.html │ │ ├── features.html │ │ ├── hero.html │ │ ├── instructions.html │ │ └── next-steps.html │ └── socialmeta.html │ ├── layouts │ └── base.html │ ├── misc │ └── readme.template.html │ └── pages │ ├── documentation.html │ ├── examples.html │ └── index.html └── todo.txt /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/heroku/heroku-buildpack-nodejs.git 2 | https://github.com/mgmco/heroku-buildpack-nginx.git -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "centering-keep-128420" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | google-api-credentials.json 2 | node_modules/ 3 | bower_components/ 4 | build/ 5 | html/ 6 | public/ 7 | 8 | *.log 9 | 10 | *.sublime-workspace 11 | *.sublime-project -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // jitGrunt assumes that task names correspond to modules 2 | // as => grunt-. This isn't always true, so mappings are required 3 | var jitGruntMappings = { 4 | swig: "grunt-swig-templates", 5 | cmq: "grunt-combine-media-queries", 6 | htmllint: "grunt-html" 7 | }; 8 | 9 | module.exports = function(grunt) { 10 | "use strict"; 11 | 12 | require("time-grunt")(grunt); 13 | require("load-grunt-config")(grunt, { 14 | jitGrunt: { 15 | staticMappings: jitGruntMappings 16 | } 17 | }); 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Max Barry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Google Drive CMS 2 | 3 | ### No longer maintained 4 | Sadly I don't have the time to maintain this project. It still works well, but I don't have the free time to support it. I would also recommend projects like [Prismic.io](https://prismic.io/) and [Contentful](https://www.contentful.com/) that tackle similar problems. 5 | 6 | --- 7 | 8 | A content management system (CMS) built on an interface everyone understands. 9 | 10 | [www.drivecms.xyz](https://www.drivecms.xyz/) 11 | 12 | ## Introduction 13 | The Google Drive CMS uses a combination of Google Sheets and Google Docs to maintain content on a website. These documents are sent to a site or a supporting service (e.g. [a Firebase database](https://words.mxbry.com/how-i-used-google-drive-and-firebase-to-give-my-static-site-a-cms-7226e01a51b5#.umrt2gwpz)) via a POST request to an API the admin specifies. 14 | 15 | The only **requirements** to run the Google Drive CMS are: 16 | - A duplicate copy of the Google Drive CMS template 17 | - An API endpoint to handle the data sent from the spreadsheet 18 | 19 | It is possible to run the CMS without writing server side code at the chosen API endpoint. The CMS' content can be exported as a JSON file, or it can be added directly to any PAAS databases that expose an API. An example of using [Firebase](https://www.firebase.com/) to create the latter of these flows can be found on our [examples page](/examples.html). 20 | 21 | ### Practical examples 22 | The following scenarios are good use cases for the Google Drive CMS: 23 | 24 | - A spreadsheet of names and addresses that are sent to an event website's REST API 25 | - A news blog posting rich text content pulled from a Firebase database 26 | - For exporting JSON to consume as part of the generation of a static site through Jekyll 27 | - Triggering and writing email MailChimp campaigns through a Zapier backed service 28 | 29 | --- 30 | 31 | ## Installation 32 | To begin using the Google Drive CMS you need a copy of the core template. This core template contains the default configuration settings and the [Google Apps Scripts](https://developers.google.com/apps-script/) that live under the hood of the Google Drive CMS. 33 | 34 | To get a copy of the core template there are 3 options: 35 | 36 | 1. Add it to you Google Drive direct from [drivecms.xyz](https://drivecms.xyz) 37 | 2. Make a copy of the core template yourself [using this link](https://docs.google.com/spreadsheets/d/15ifxjEo9nVXTbeX7mwLnW-F5yu96u9IF1RL3wHoYLbs/edit?usp=sharing) 38 | 3. Copy an existing Google Drive CMS template in your Google Drive 39 | 40 | After creating a copy of the core template, change the endpoint value within the *SETTINGS* sheet. For more information on configuring your Google Drive CMS template, see the [settings](#settings) section of this documentation. 41 | 42 | ### Publishing content 43 | The Google Drive CMS will add a custom menu option to the top of the Google Sheets interface. The Google Drive CMS menu item contains two actions. 44 | 45 | **Publish** publishes the CMS' content to the designated endpoint 46 | 47 | **Export content** exports the contents of the CMS as a JSON file to a directory called `_exports` inside of the same Google Drive folder that contains the base template itself 48 | 49 | --- 50 | 51 | ## Understanding the spreadsheet 52 | The core template has four tabs. Each tab can be accessed from Google Drive's tab navigation toolbar at the bottom of the page. 53 | 54 | ### CMS 55 | The main tab where an admin inputs their data. 56 | 57 | #### Headers 58 | The top row of the *CMS* tab represents the content's headers. These are used as the keys to map the content's data against in the JSON object sent to your endpoint. Headers behave similar to column names in a standard database. 59 | 60 | ![Example of headers for a blog's CMS](http://drivecms.xyz/img/documentation/headers.png "Example of headers for a blog's CMS") 61 | 62 | #### Field types 63 | Field types let admins add special functionality to a column. There are three field types, currently: 64 | 65 | - **Simple** (default): TRUE FALSE values are encoded to JSON as booleans, dates as date objects, numbers as integers and all other values as strings. 66 | - **Eval**: The contents of the cell is run against an `eval` function and stored as-is within the JSON object. Useful for complex data structures or nested objects 67 | - **List**: Provide a comma separated list of items within a cell and have them transformed in to a JavaScript array. For example, a cell containing `red, blue, green` will be transformed to the array `["red", "blue", "green"]` 68 | - **Google Sheet**: Point at another Google Sheet to create nested objects. See [Nested data](#nested-data) for more information 69 | 70 | n.b. A blank field type will behave like a simple field. 71 | 72 | We are looking to add more in the future, including foreign key relationships between multiple sheets. Eval is a good stopgap for more complex data, as it can accept raw JavaScript arrays or objects. *Update* Google Drive CMS now supports pointing at [other Google Sheets](#nested-data) to create neater nested objects. 73 | 74 | #### Content rows 75 | Each row beneath the field types will become an object within the JSON array sent to the designated API endpoint. These are the equivalent of a record within a traditional database. 76 | 77 | For example, an individual blog post is a content row, and it might have headers like "title" or "publication date". 78 | 79 | ### Settings 80 | The *SETTINGS* tab within the Google Drive CMS template allows customization the CMS' behavior. 81 | 82 | The following settings can be configured: 83 | 84 | - `endpoint` The endpoint the spreadsheet's data is sent to when published 85 | - `debug` Returns the JSON to the admin as an alert, as supposed to sending it to the designated endpoint. Useful for testing content rows before publishing on a live site 86 | - `saveFile` Whether the Google Drive CMS should save a copy of your exported data as a JSON file. This file is stored in a folder called `_export` found inside the same directory that holds the copy of the core template that requested the save 87 | - `headers` A JavaScript object structure containing HTTP headers to send with your request. Useful for passing any key additional information or context to your server. Alternatively use the *options* setting 88 | - `authorization` A configuration value specifically for the authorization header. Overwrites any *authorization* value found within your custom headers 89 | - `options` Additional keys to add to the JSON object sent to your server. If options are provided, then the content rows of the CMS will be stored behind a key called `data` 90 | - `requestMethod` Send the CMS' content with a custom request method, like PUT, GET or DELETE. By default the API will be contacted via via POST request 91 | 92 | 93 | ### _internals 94 | It might be best to just leave this alone. Values in this tab power current and future functionality at a low level. 95 | 96 | --- 97 | 98 | ## Advanced features 99 | ### Nested data 100 | *NEW* 101 | Use a field type of `Google Sheet` to nest other Google Drive CMS sheets inside of your POST data. 102 | 103 | 1. *Create a secondary Google Drive CMS* The only sheet we really need is the default `CMS` sheet. You could alternatively create a blank Google Sheet with 2 rows (headers and field types), and your data underneath. The `SETTINGS`, `DOCUMENTATION` and `_internals` sheets on your secondary spreadsheet are not used. 104 | 2. Copy and past the URL or spreadsheet ID in to your Drive CMS. Remember to set the field type to `Google Sheet`. 105 | 3. Publish as normal 106 | 107 | Any data in the second spreadsheet will be added to your JSON payload as a nested array. 108 | 109 | #### Example 110 | 111 | *Google Sheet A (your Google Drive CMS instance)* 112 | 113 | | title | nested | 114 | | ----- | ------ | 115 | | *String* | *Google Sheet* | 116 | | A cool title | < URL of Google Sheet B > | 117 | | Another cool title | | 118 | 119 | *Google Sheet B (the data you want to nest)* 120 | 121 | | FieldA | FieldB | 122 | | ----- | ------ | 123 | | *String* | *String* | 124 | | Content that is nested | Content that is also nested | 125 | | Second nested item | Some information | 126 | 127 | *Result* 128 | ``` 129 | [ 130 | { 131 | "title": "A cool title", 132 | "nested": [ 133 | { 134 | "FieldA": "Content that is nested", 135 | "FieldB": "Content that is also nested" 136 | }, 137 | { 138 | "FieldA": "Second nested item", 139 | "FieldB": "Some information" 140 | } 141 | ] 142 | }, 143 | { 144 | "title": "Another cool title", 145 | "nested": "" 146 | } 147 | ] 148 | ``` 149 | 150 | You could probably nest sheets within sheets within sheets. That would probably work. 151 | Don't point spreadsheets at each other. That's going to end in a loop. That would probably be bad. 152 | 153 | ### Rich text 154 | It is possible to combine the Google Drive CMS core template with Google Docs to give an admin a rich text editor. 155 | 156 | Write a standard Google Doc using Google Doc's built in rich text capabilities (headings, links, inline imagery, etc.). Give that doc share settings that would allow the owner of the CMS core template at least view access. Add just the URL of that Google Doc to a cell within a content row of the CMS, and when published the Doc will be transformed in to raw HTML tags and added to the JSON payload. 157 | 158 | Google Docs are structured such that they can be exported straight to HTML. The CMS performs light sanitization of the converted HTML, but otherwise it is returned as a string within the JSON document upon publishing. 159 | 160 | #### Image replacement 161 | Images inserted in to Google Docs are hosted on Google's CDN. When the rich text is extracted you'll have `img` tags with a `src="https://cdn.google.com/..."`. That's okay-ish for small projects, or projects where images aren't vital. Google rate limit access to these URLs meaning every now and again your images will fail to load, as the Google CDN returns a 403. 162 | 163 | To get around this Google Drive CMS provides support for externally hosted images. In your rich text Google Doc that is being consumed by the drive CMS add the following inline: 164 | 165 | ``` 166 | This is the text content written in my Google Doc and here is my pretty image: 167 | 168 | [IMAGE:https://media.giphy.com/media/l46CqLVMWzaJUFPLW/giphy.gif] 169 | 170 | And then my text carries on like normal. 171 | ``` 172 | 173 | When the rich text is extracted and posted to your JSON api your `img` tag will now have a source of `https://media.giphy.com/media/l46CqLVMWzaJUFPLW/giphy.gif` (`[IMAGE:]` is replaced). Using externally hosted images also allows you to use image formats that a Google Doc doesn't natively support (Gifs!). 174 | 175 | ### Slugify 176 | A common CMS requirement is a slug field. A slug field can be used to build URLs on a site, and is often a concatenation of a title field with hyphens. For example, a news article called "Big Announcement Coming" would have a slug of "big-announcement-coming". 177 | 178 | To expedite this process the Google Drive CMS has a shortcut function called SLUGIFY. Add the cell function `=SLUGIFY(:cell:)` to a cell in the CMS sheet, where :cell: is any string input (e.g. the corresponding cell in a *title* column). 179 | 180 | --- 181 | 182 | ## Best practices 183 | ### Securing the CMS 184 | Consider access to the Google Drive CMS spreadsheet like the password and email logins for a traditional CMS. If you wouldn't want someone in your WordPress, don't share them access to your spreadsheet! 185 | 186 | To better understand the security and sharing potential of Google Drive, [check out Google's documentation](https://support.google.com/drive/answer/2494822?hl=en). 187 | 188 | If sharing the CMS spreadsheet with other admins, consider locking down parts of the document using protected ranges. For example, protect the headers row of the spreadsheet to prevent another admin breaking a data structure the API endpoint requires. 189 | 190 | ### Revision history 191 | Admins can rollback to previous iterations using Google Drive's [built in revision history](https://support.google.com/docs/answer/190843?hl=en). Restore a previous version of the sheet and then republish the document. 192 | 193 | ### Extending the CMS 194 | All of the Apps Scripts that power the CMS are baked in to the sheet an admin edits. These scripts can be accessed via `Tools > Script editor...` from the Google Sheets toolbar. We've tried to comment the .gs files where possible, so hack and extend at will! Changes to these scripts will only effect the current instance of the CMS. 195 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-drive-cms", 3 | "version": "0.0.1", 4 | "private": "true", 5 | "dependencies": { 6 | "bourbon": "^4.0.1", 7 | "scut": "scut#^1.2.1", 8 | "Materialize": "materialize#^0.97.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /config/nginx.conf.erb: -------------------------------------------------------------------------------- 1 | daemon off; 2 | 3 | worker_processes <%= ENV["NGINX_WORKERS"] || 4 %>; 4 | 5 | events { 6 | use epoll; 7 | accept_mutex on; 8 | multi_accept on; 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | gzip on; 14 | gzip_disable "MSIE [1-6]\.(?!.*SV1)"; 15 | gzip_comp_level 2; 16 | gzip_min_length 512; 17 | gzip_proxied any; 18 | gzip_vary on; 19 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 20 | 21 | server_tokens off; 22 | 23 | tcp_nopush on; 24 | tcp_nodelay on; 25 | 26 | log_format l2met "measure#nginx.service=$request_time request_id=$http_x_request_id"; 27 | access_log logs/nginx/access.log l2met; 28 | error_log logs/nginx/error.log; 29 | 30 | include mime.types; 31 | default_type application/octet-stream; 32 | sendfile on; 33 | 34 | server { 35 | listen <%= ENV["PORT"] %>; 36 | server_name _; 37 | keepalive_timeout 5; 38 | root <%= ENV["ROOT_DIRECTORY"] || 'html' %>; 39 | index index.html; 40 | try_files $uri.html $uri/ $uri =404; 41 | resolver 8.8.8.8; 42 | port_in_redirect off; 43 | 44 | <% if ENV["FORCE_HTTPS"] %> 45 | if ( $http_x_forwarded_proto != 'https' ) { 46 | return 301 https://$host$request_uri; 47 | } 48 | <% end %> 49 | 50 | <% if ENV["BASIC_AUTH_USER"] && ENV["BASIC_AUTH_PASSWORD"] %> 51 | auth_basic "Restricted"; 52 | auth_basic_user_file <%= "#{ENV["HOME"]}/config/htpasswd" %>; 53 | <% end %> 54 | 55 | if (!-f "${request_filename}index.html") { 56 | rewrite ^/(.*)/$ /$1 permanent; 57 | } 58 | 59 | if ($request_uri ~* ".html") { 60 | rewrite (?i)^(.*)/(.*)\.html $1/$2 permanent; 61 | } 62 | 63 | location / { 64 | expires off; 65 | try_files $uri.html $uri $uri/ <%= ENV["REDIRECT_NOT_FOUND_TO"] || '' %> =404; 66 | <% if ENV["NGINX_DEBUG"] %>add_header Nginx-Base-Location on;<% end %> 67 | } 68 | 69 | location ~ /\. { 70 | deny all; 71 | <% if ENV["NGINX_DEBUG"] %>add_header Nginx-Denied on;<% end %> 72 | } 73 | 74 | location ~* \.(jpg|jpeg|png|gif|svg|ico|css|js|woff|woff2|ttf|woff|eot)$ { 75 | expires 31d; 76 | } 77 | 78 | } 79 | } -------------------------------------------------------------------------------- /design/icons.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/design/icons.ai -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "html", 4 | "redirects": [{ 5 | "source": "/documentation", 6 | "destination": "/documentation.html", 7 | "type": 301 8 | }, { 9 | "source": "/examples", 10 | "destination": "/examples.html", 11 | "type": 301 12 | }], 13 | "headers": [{ 14 | "source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)", 15 | "headers": [{ 16 | "key": "Access-Control-Allow-Origin", 17 | "value": "*" 18 | }] 19 | }, { 20 | "source": "**/*.@(jpg|jpeg|gif|png|svg|eot|otf|ttf|ttc|woff|woff2|css|js)", 21 | "headers": [{ 22 | "key": "Cache-Control", 23 | "value": "max-age=2592000" 24 | }] 25 | }] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /grunt/aliases.yml: -------------------------------------------------------------------------------- 1 | default: [] 2 | 3 | base: 4 | description: "For common tasks shared between development and production builds" 5 | tasks: 6 | - "swig" 7 | - "jshint" 8 | - "sass" 9 | 10 | build: 11 | description: "Development environment build" 12 | tasks: 13 | - "base" 14 | - "webpack:js_dev" 15 | - "copy" 16 | 17 | dist: 18 | description: "Production build" 19 | tasks: 20 | - "clean" 21 | - "markdown" 22 | - "base" 23 | - "webpack:js" 24 | - "prettify" 25 | - "sitemap" 26 | - "cmq" 27 | - "postcss" 28 | - "copy:fonts" 29 | - "copy:misc" 30 | - "imagemin" 31 | - "modernizr" 32 | # - "cacheBust" 33 | 34 | deploy: 35 | description: "Deploy the build to a Surge.sh server" 36 | tasks: 37 | - "dist" 38 | - "surge" 39 | 40 | run: 41 | description: "Starts a development server" 42 | tasks: 43 | - "connect" 44 | 45 | serve: 46 | description: "Alias for grunt run" 47 | tasks: 48 | - "run" -------------------------------------------------------------------------------- /grunt/css-tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sass: { 3 | options: { 4 | includePaths: [ 5 | '<%= package.paths.bower %>bourbon/app/assets/stylesheets/', 6 | '<%= package.paths.bower %>Materialize/sass/', 7 | '<%= package.paths.bower %>scut/dist/' 8 | ] 9 | }, 10 | files: [{ 11 | expand: true, 12 | cwd: "<%= package.src.scss %>", 13 | src: "**/*.scss", 14 | dest: "<%= package.build.css %>", 15 | ext: ".css" 16 | }] 17 | }, 18 | postcss: { 19 | options: { 20 | processors: [ 21 | require("pixrem")(), // add fallbacks for rem units 22 | require("autoprefixer")({ 23 | browsers: "last 2 versions" 24 | }), // add vendor prefixes 25 | require("cssnano")() // minify the result 26 | ] 27 | }, 28 | files: [{ 29 | expand: true, 30 | cwd: "<%= package.build.css %>", 31 | src: ["**/*.css", "!fonts.css"], // There is a weird bug with Postcss that freaks at fontfaces 32 | dest: "<%= package.build.css %>" 33 | }] 34 | }, 35 | cmq: { 36 | files: [{ 37 | expand: true, 38 | cwd: "<%= package.build.css %>", 39 | src: "**/*.css", 40 | dest: "<%= package.build.css %>" 41 | }] 42 | }, 43 | watch: { 44 | files: ["<%= package.src.scss %>**/*.scss"], 45 | tasks: ["sass", "cmq"] 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /grunt/deploy-tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | surge: { 3 | options: { 4 | project: "<%= package.paths.build %>", 5 | domain: null 6 | } 7 | }, 8 | cacheBust: { 9 | options: { 10 | assets: ["html/css/**", "html/js/**" ], 11 | // deleteOriginals: true 12 | }, 13 | files: [{ 14 | expand: true, 15 | cwd: "<%= package.paths.build %>", 16 | src: ["**/*.html"] 17 | }] 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /grunt/fonts-tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copy: { 3 | files: [{ 4 | expand: true, 5 | cwd: "<%= package.src.fonts %>", 6 | src: ["**/*", "!**/*.json"], 7 | dest: "<%= package.build.fonts %>" 8 | }] 9 | }, 10 | watch: { 11 | files: [ 12 | "<%= package.src.fonts %>**/*.{eot,woff,svg,ttf}", 13 | ], 14 | tasks: ["copy:fonts"] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /grunt/images-tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | imagemin: { 3 | files: [{ 4 | expand: true, 5 | cwd: "<%= package.src.images %>", 6 | src: ["**/*.{png,jpg,jpeg,gif,svg}"], 7 | dest: "<%= package.build.images %>" 8 | }] 9 | }, 10 | webp: { 11 | files: [{ 12 | expand: true, 13 | cwd: "<%= package.build.images %>", 14 | src: "**/*.{png,jpg,jpeg}", 15 | dest: "<%= package.build.images %>" 16 | }], 17 | options: { 18 | binpath: require("webp-bin").path, 19 | preset: "default", 20 | verbose: false, 21 | quality: 80, 22 | alphaQuality: 80, 23 | } 24 | }, 25 | copy: { 26 | files: [{ 27 | expand: true, 28 | cwd: "<%= package.src.images %>", 29 | src: "**/*.{png,jpg,jpeg,gif,svg}", 30 | dest: "<%= package.build.images %>" 31 | }] 32 | }, 33 | watch: { 34 | files: [ 35 | "<%= package.src.images %>**/*.{jpeg,jpg,gif,png,svg}" 36 | ], 37 | tasks: ["copy:images"] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /grunt/js-tasks.js: -------------------------------------------------------------------------------- 1 | var webpackModule = require("webpack"); 2 | 3 | module.exports = { 4 | jshint: { 5 | files: { 6 | src: ["<%= package.src.js %>**/*.js"] 7 | }, 8 | options: { 9 | globals: { 10 | jQuery: true, 11 | console: true, 12 | module: true, 13 | alert: true, 14 | document: true, 15 | window: true 16 | } 17 | } 18 | }, 19 | webpack: { 20 | entry: { 21 | app: "<%= package.src.js %>app.js", 22 | index: "<%= package.src.js %>index.js", 23 | documentation: "<%= package.src.js %>documentation.js", 24 | }, 25 | output: { 26 | path: "<%= package.build.js %>", 27 | filename: "[name].js" 28 | }, 29 | plugins: [ 30 | new webpackModule.optimize.DedupePlugin(), 31 | new webpackModule.optimize.UglifyJsPlugin() 32 | ], 33 | resolve: { 34 | extensions: ['', '.js', '.jsx',] 35 | }, 36 | module: { 37 | loaders: [ 38 | { test: /\.html$/, loader: "html-loader" } 39 | ] 40 | } 41 | }, 42 | webpack__dev: { 43 | debug: true, 44 | devtool: "eval", 45 | entry: "<%= webpack.js.entry %>", 46 | output: "<%= webpack.js.output %>", 47 | module: "<%= webpack.js.module %>", 48 | resolve: "<%= webpack.js.resolve %>" 49 | }, 50 | modernizr: { 51 | dest: "<%= package.build.js %>modernizr.min.js", 52 | parseFiles: true, 53 | customTests: [], 54 | uglify: true, 55 | tests: [ 56 | // Tests to explicitly include 57 | ], 58 | options: [ 59 | "setClasses", 60 | "addTest", 61 | "domPrefixes", 62 | "prefixes", 63 | ] 64 | }, 65 | docco: { 66 | src: ["<%= package.src.js %>**/*.js"], 67 | options: { 68 | output: "docs/js/" 69 | } 70 | }, 71 | watch: { 72 | files: ["<%= package.src.js %>**/*.js"], 73 | tasks: ["jshint", "webpack:js_dev"] 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /grunt/misc-tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bump: { 3 | options: { 4 | push: true, 5 | pushTo: "origin", 6 | files: [ 7 | "package.json", 8 | "bower.json" 9 | ], 10 | commitFiles: [ 11 | "package.json", 12 | "bower.json" 13 | ] 14 | } 15 | }, 16 | clean: [ 17 | "<%= package.paths.build %>" 18 | ], 19 | connect: { 20 | options: { 21 | keepalive: true, 22 | open: true, 23 | base: "<%= package.paths.build %>" 24 | } 25 | }, 26 | copy: { 27 | files: [{ 28 | expand: true, 29 | cwd: "<%= package.paths.project %>favicons/", 30 | src: ["**/*"], 31 | dest: "<%= package.paths.build %>" 32 | }] 33 | }, 34 | sitemap: { 35 | pattern: ["html/**/*.html"], 36 | siteRoot: "html/", 37 | "changefreq": "monthly", 38 | "priority": 0.8, 39 | extension: { required: false } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /grunt/templates-tasks.js: -------------------------------------------------------------------------------- 1 | var grunt = require("grunt"); 2 | 3 | module.exports = { 4 | markdown: { 5 | src: "./Readme.md", 6 | dest: "<%= package.templates.data %>readme.json", 7 | // dest: "./Readme.html", 8 | options: { 9 | template: "<%= package.paths.templates %>misc/readme.template.html", 10 | postCompile: function(src, context) { 11 | var json = {}; 12 | 13 | // Remove the first two lines of the readme 14 | json.content = src.split("\n").slice(2).join("\n"); 15 | 16 | // Load Cheerio for DOM manipulation 17 | var cheerio = require("cheerio"), 18 | $ = cheerio.load(src); 19 | 20 | // For each header in the document, build a navigation tree 21 | var headers = []; 22 | 23 | $("h2").each(function(idx, header) { 24 | headers.push({ 25 | id: $(header).attr("id"), 26 | title: $(header).text() 27 | }); 28 | }); 29 | 30 | json.headers = headers; 31 | 32 | return JSON.stringify(json); 33 | } 34 | } 35 | }, 36 | swig: { 37 | options: { 38 | templatePath: "<%= package.paths.templates %>", 39 | data: function() { 40 | /** 41 | Take the .json file in the data directory, 42 | and then merge the objects together 43 | */ 44 | var path = require("path"), 45 | fs = require("fs"), 46 | _ = require("lodash"); 47 | 48 | var pathToDatafiles = path.join(grunt.file.readJSON("package.json").templates.data, "**/*.json"); 49 | 50 | var dataFiles = grunt.file.glob.sync(pathToDatafiles), 51 | dataObj = {}; 52 | 53 | dataFiles.forEach(function(df) { 54 | var dataToMerge = {}; 55 | 56 | var name = path.parse(df).name, 57 | data = grunt.file.readJSON(df); 58 | 59 | if (name === "data") { 60 | /** 61 | Variables within the data.json file are global. 62 | They can be used in templates without needing prefixes 63 | (e.g. {{var}} instead of {{data.var}}) 64 | */ 65 | dataToMerge = data; 66 | } else { 67 | dataToMerge[name] = data; 68 | } 69 | 70 | dataObj = _.merge(dataObj, dataToMerge); 71 | }); 72 | return dataObj; 73 | }() 74 | }, 75 | files: [{ 76 | expand: true, 77 | cwd: "<%= package.templates.pages %>", 78 | ext: ".html", 79 | src: ["**/*.html"], 80 | dest: "<%= package.paths.build %>" 81 | }], 82 | }, 83 | prettify: { 84 | files: [{ 85 | expand: true, 86 | cwd: "<%= package.paths.build %>", 87 | src: ["**/*.html"], 88 | dest: "<%= package.paths.build %>" 89 | }] 90 | }, 91 | htmllint: { 92 | files: [{ 93 | expand: true, 94 | cwd: "<%= package.paths.build %>", 95 | src: ["**/*.html"], 96 | }] 97 | }, 98 | watch: { 99 | files: [ 100 | "<%= package.paths.templates %>**/*.html", 101 | "<%= package.templates.data %>**/*.{json,yml}" 102 | ], 103 | tasks: ["swig"] 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-drive-cms", 3 | "title": "Google Drive CMS", 4 | "description": "Create and manage content for your website straight from Google Drive. Enjoy the security, ease-of-use and familiarity that Google Drive brings as a content management system.", 5 | "version": "0.0.22", 6 | "keywords": ["cms", "google drive", "api", "cloud", "content management system"], 7 | "scripts": { 8 | "postinstall": "bower install && grunt dist", 9 | "deploy": "grunt dist && firebase deploy" 10 | }, 11 | "dependencies": { 12 | "autoprefixer": "^6.2.0", 13 | "bower": "^1.7.9", 14 | "cheerio": "^0.20.0", 15 | "cssnano": "^3.5.2", 16 | "faker": "^3.1.0", 17 | "grunt": "^1.0.0", 18 | "grunt-bump": "^0.8.0", 19 | "grunt-cache-bust": "^1.3.0", 20 | "grunt-cli": "^1.2.0", 21 | "grunt-combine-media-queries": "^1.0.19", 22 | "grunt-contrib-clean": "^1.0.0", 23 | "grunt-contrib-connect": "^1.0.2", 24 | "grunt-contrib-copy": "^1.0.0", 25 | "grunt-contrib-imagemin": "^1.0.0", 26 | "grunt-contrib-jshint": "^1.0.0", 27 | "grunt-contrib-watch": "^1.0.0", 28 | "grunt-docco": "^0.4.0", 29 | "grunt-html": "^7.0.0", 30 | "grunt-markdown": "^0.7.0", 31 | "grunt-modernizr": "^1.0.1", 32 | "grunt-postcss": "^0.8.0", 33 | "grunt-prettify": "^0.4.0", 34 | "grunt-sass": "^1.1.0", 35 | "grunt-sitemap": "^1.2.1", 36 | "grunt-surge": "^0.7.0", 37 | "grunt-swig-templates": "^0.1.4", 38 | "grunt-webp": "^0.0.4", 39 | "grunt-webpack": "^1.0.11", 40 | "html-loader": "^0.4.3", 41 | "jit-grunt": "^0.10.0", 42 | "jquery": "^3.0.0", 43 | "layzr.js": "^2.0.4", 44 | "load-grunt-config": "^0.19.1", 45 | "lodash": "^4.11.2", 46 | "materialize-css": "^0.97.6", 47 | "pixrem": "^3.0.0", 48 | "time-grunt": "^1.3.0", 49 | "typed.js": "^1.1.1", 50 | "webp-bin": "~0.1.4", 51 | "webpack": "^1.12.9", 52 | "webpack-dev-server": "^1.14.0" 53 | }, 54 | "author": { 55 | "name": "Max Barry", 56 | "email": "max@mxbry.com" 57 | }, 58 | "main": "Gruntfile.js", 59 | "repository": { 60 | "type": "git", 61 | "url": "https://github.com/max-barry/google-drive-cms" 62 | }, 63 | "homepage": "https://drivecms.xyz/", 64 | "license": "MIT", 65 | "readmeFilename": "Readme.md", 66 | "paths": { 67 | "project": "./src/", 68 | "templates": "./src/templates/", 69 | "build": "./html/", 70 | "tmp": "./tmp/", 71 | "bower": "./bower_components/", 72 | "node": "./node_modules/" 73 | }, 74 | "build": { 75 | "js": "./html/js/", 76 | "fonts": "./html/fonts/", 77 | "images": "./html/img/", 78 | "css": "./html/css/" 79 | }, 80 | "src": { 81 | "js": "./src/js/", 82 | "scss": "./src/scss/", 83 | "fonts": "./src/fonts/", 84 | "images": "./src/img/" 85 | }, 86 | "templates": { 87 | "pages": "./src/templates/pages/", 88 | "includes": "./src/templates/includes/", 89 | "data": "./src/templates/data/", 90 | "layouts": "./src/templates/layouts/" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/favicons/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-144x144.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-36x36.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-48x48.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-72x72.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #2b5797 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /src/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /src/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /src/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/favicon.ico -------------------------------------------------------------------------------- /src/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google Drive CMS", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": 0.75 9 | }, 10 | { 11 | "src": "\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": 1 15 | }, 16 | { 17 | "src": "\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": 1.5 21 | }, 22 | { 23 | "src": "\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": 2 27 | }, 28 | { 29 | "src": "\/android-chrome-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": 3 33 | }, 34 | { 35 | "src": "\/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": 4 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /src/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /src/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /src/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /src/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /src/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /src/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /src/img/documentation/headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/documentation/headers.png -------------------------------------------------------------------------------- /src/img/examples/demo_spreadsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/examples/demo_spreadsheet.png -------------------------------------------------------------------------------- /src/img/examples/firebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/examples/firebase.png -------------------------------------------------------------------------------- /src/img/getting-started/copy-template-to-drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/getting-started/copy-template-to-drive.png -------------------------------------------------------------------------------- /src/img/getting-started/customise-endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/getting-started/customise-endpoint.png -------------------------------------------------------------------------------- /src/img/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/google-drive-cms-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/google-drive-cms-1.png -------------------------------------------------------------------------------- /src/img/google-drive-cms-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/img/google-drive-cms-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/img/google-drive-cms-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 113 | 114 | 115 | 116 | 117 | 122 | 123 | 124 | 125 | 126 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/img/howto__publish.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/howto__publish.gif -------------------------------------------------------------------------------- /src/img/meta/social.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/meta/social.PNG -------------------------------------------------------------------------------- /src/img/writer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-barry/google-drive-cms/d512463a811da27b5f286b0cacff879eb3d07dcb/src/img/writer.jpg -------------------------------------------------------------------------------- /src/js/addToDrive.js: -------------------------------------------------------------------------------- 1 | var CLIENT_ID = "460689517717-gific9jhjnc163eakneajf62lkob772a.apps.googleusercontent.com", 2 | SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.appdata", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/spreadsheets"], 3 | API_SCRIPT_ID = "M2iJfxCU5Xxwnqe1tXIpolmGcxwm0Lhg6"; 4 | 5 | var loader = require("../templates/includes/components/_loader.html"), 6 | successMsg = require("../templates/includes/components/_add-to-drive__success.html"), 7 | errorMsg = require("../templates/includes/components/_add-to-drive__error.html"), 8 | updateEndpointForm = require("../templates/includes/components/_add-to-drive__updateEndpoint.html"), 9 | modalTemplate = require("../templates/includes/components/_add-to-drive__error_modal_upload.html"); 10 | 11 | // Global variables 12 | window.generatedFileId = null; 13 | window.$trigger = null; 14 | window.$triggerWrap = null; 15 | window.hasGeneratedToast = false; 16 | 17 | var setEndpoint = function(auth) { 18 | 19 | if (!window.generatedFileId) { 20 | setError("No spreadsheet could be found. Make sure to select 'Add to Drive' beforehand"); 21 | return; 22 | } 23 | 24 | loadLibrary(auth, function(){ 25 | 26 | var op = gapi.client.request({ 27 | 'root': "https://script.googleapis.com", 28 | 'path': "v1/scripts/" + API_SCRIPT_ID + ':run', 29 | 'method': "POST", 30 | 'body': { 31 | "function": "updateEndpoint", 32 | "parameters": [ window.generatedFileId.id, window.endpointToUpdate], 33 | "devMode": true 34 | // "parameters": [ window.generatedFileId.id || "sdiopsdkfojkdsfgoj" ] 35 | } 36 | }); 37 | 38 | op.execute(function(response){ 39 | if (response.error && response.error.status) { 40 | // Handle API level error 41 | setError("API Error. The Google Drive API was unresponseive:\n\n" + JSON.stringify(response.error)); 42 | } else if (response.error) { 43 | // Hanndle the script level error 44 | setError("Script Error. The Google Drive API was okay, but the endpoint failed:\n\n" + JSON.stringify(response.error)); 45 | } else { 46 | // Success! 47 | setSuccess(); 48 | } 49 | }); 50 | 51 | }); 52 | }; 53 | 54 | var copyFile = function(auth){ 55 | loadLibrary(auth, function(){ 56 | var request = gapi.client.drive.files.copy({ 57 | "fileId": TEMPLATE_ID, 58 | "resource": {} 59 | }); 60 | request.execute(function(response) { 61 | window.generatedFileId = response; 62 | setSuccess(); 63 | }); 64 | }); 65 | }; 66 | 67 | var loadLibrary = function(auth, cb) { 68 | if (!auth.error) { 69 | // Load the Drive API 70 | gapi.client.load("drive", "v3", cb); 71 | } else { 72 | setError(auth.error); 73 | } 74 | }; 75 | 76 | var authorize = function(callback) { 77 | gapi.auth.authorize({ 78 | client_id: CLIENT_ID, 79 | scope: SCOPES, 80 | immediate: false 81 | }, callback); 82 | }; 83 | 84 | /** 85 | Button states 86 | */ 87 | var setLoading = function(source) { 88 | 89 | // Global vars to track source of action 90 | window.$trigger = $(source); 91 | window.$triggerWrap = $trigger.parent(); 92 | 93 | $trigger.remove(); 94 | $triggerWrap.append(loader); 95 | }; 96 | 97 | var setSuccess = function() { 98 | var targetUrl = "https://docs.google.com/spreadsheets/d/" + window.generatedFileId.id; 99 | 100 | window.$triggerWrap.html("").append( 101 | $(successMsg).attr("href", targetUrl) 102 | ); 103 | 104 | if (!window.hasGeneratedToast) { 105 | window.hasGeneratedToast = true; 106 | $(".update_endpoint").show(); 107 | Materialize.toast('New CMS template added to your Google Drive!', 4000); 108 | } 109 | }; 110 | 111 | var setError = function(msg) { 112 | 113 | window.$triggerWrap.html("").append(errorMsg); 114 | 115 | $("body").append( 116 | $(modalTemplate) 117 | .find(".modal-content pre").html(msg).end() 118 | .find(".modal-content p a").attr("href", "https://docs.google.com/spreadsheets/d/" + TEMPLATE_ID).end() 119 | ); 120 | 121 | $(".modal-trigger").leanModal(); 122 | 123 | Materialize.toast("Error! There was a problem with the Google Drive API", 4000); 124 | }; 125 | 126 | var main = function() { 127 | /** 128 | Make a copy of our base template, and add it to a user's Google Drive 129 | */ 130 | setLoading(this); 131 | authorize(copyFile); 132 | }; 133 | 134 | var updateEndpoint = function() { 135 | // Prevent form being submitted 136 | event.preventDefault(); 137 | 138 | // Fetch from the form the text value 139 | window.endpointToUpdate = $("[data-update-endpoint] input").val(); 140 | 141 | // Add loader 142 | setLoading(this); 143 | 144 | // Authorize and update the endpoint 145 | authorize(setEndpoint); 146 | }; 147 | 148 | var editEndpoint = function() { 149 | // Prevent a link being followed 150 | event.preventDefault(); 151 | 152 | // Add a form to the DOM 153 | $("[data-show-endpoint-form]").parent().html(updateEndpointForm); 154 | }; 155 | 156 | module.exports.main = main; 157 | module.exports.updateEndpoint = updateEndpoint; 158 | module.exports.editEndpoint = editEndpoint; -------------------------------------------------------------------------------- /src/js/app.js: -------------------------------------------------------------------------------- 1 | // Vendor 2 | var $ = window.jQuery = require("jquery"); 3 | var Layzr = window.Layzr = require("layzr.js"); 4 | require("materialize-css"); 5 | 6 | // Global variables 7 | window.TEMPLATE_ID = "15ifxjEo9nVXTbeX7mwLnW-F5yu96u9IF1RL3wHoYLbs"; 8 | 9 | document.addEventListener('DOMContentLoaded', function(event) { 10 | 11 | // Initialize mobile sidenav 12 | $(".button-collapse").sideNav(); 13 | 14 | // Select inputs 15 | $('select').material_select(); 16 | 17 | // Set the active navigation item 18 | $("nav ul:not(#mobile-navigation) a").each(function(i, el) { 19 | if ( $(el).attr("href") === window.location.pathname ) { 20 | $(el).parent().addClass("active"); 21 | } 22 | }); 23 | 24 | // Lazy load images 25 | Layzr() 26 | .update() 27 | .check() 28 | .handlers(true); 29 | 30 | }); -------------------------------------------------------------------------------- /src/js/documentation.js: -------------------------------------------------------------------------------- 1 | var buildDocumentationNav = function() { 2 | var inThisSection, 3 | $link, thisId; 4 | 5 | // Find the headers in the table of contents 6 | var tableOfContentsLinks = $(".table-of-contents").find("a"); 7 | 8 | tableOfContentsLinks.each(function(i, link) { 9 | thisId = $(link).attr("href"); 10 | $link = $(thisId); 11 | // Remove the ID from the link, you put it on the wrapper. 12 | $link.attr("id", ""); 13 | 14 | inThisSection = $link.nextUntil("h2").andSelf(); 15 | inThisSection.wrapAll("
"); 16 | 17 | }); 18 | 19 | // Create "pinned when at top of screen" effect 20 | $(".table-of-contents").pushpin({ top: $(".table-of-contents").offset().top }); 21 | // Instantiate scrollspy for the table of contents 22 | $(".scrollspy").scrollSpy(); 23 | 24 | }; 25 | 26 | buildDocumentationNav(); 27 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | Header area parralax initialisation 3 | */ 4 | $('.parallax').parallax(); 5 | 6 | /** 7 | Generate fake data in the example table in instructions 8 | */ 9 | var faker = require('faker/locale/en_US'); 10 | 11 | var fakeTable = function() { 12 | var tbody = $('.instruction__fake_table > tbody'), 13 | code = $(".instruction__fake_json code"), 14 | rowsToGenerate = 2, 15 | fName, fEmail, tr, 16 | fJson = []; 17 | 18 | for (var i = 0; i < rowsToGenerate; i++) { 19 | fName = [ faker.name.firstName(), faker.name.lastName() ]; 20 | fEmail = faker.internet.email(fName[0], fName[1]); 21 | 22 | tr = "" + fName.join(" ") + "" + fEmail + ""; 23 | tbody.append(tr); 24 | 25 | fJson.push(" {\"name\": \"" + fName.join(" ") + "\", \"email\": \"" + fEmail + "\"}"); 26 | } 27 | 28 | code.append("[\n" + fJson.join(",\n") + "\n]"); 29 | 30 | }; 31 | fakeTable(); 32 | 33 | /** 34 | Explanation typed text effects 35 | */ 36 | window.$.typed = require("typed.js"); 37 | 38 | var POSTExamples = [ 39 | "directly in to a Firebase.io database.", 40 | "to a REST endpoint that saves the data to a local database.", 41 | "to a webhook connected to your database.", 42 | "through a middleman like Zapier or IFTTT.", 43 | "up to a CRM or ESP tool like MailChimp, Mail... Cimp?", 44 | "to Slack, Mandrill, or any trendy tool that accepts JSON POST or PUT calls", 45 | ]; 46 | 47 | var initiateTypedEffect = function(){ 48 | // Fix the max heights on these section to deal with sentence wrapping 49 | var $toFix = $("[data-fix-max-height]"); 50 | $toFix.each(function(idx, el){ 51 | $(el).css("max-height", $(el).outerHeight()); 52 | }); 53 | 54 | // Initiate typed library 55 | $("[data-type-target]").typed({ 56 | strings: POSTExamples, 57 | typeSpeed: 0, 58 | loop: true, 59 | backDelay: 1000 60 | }); 61 | }; 62 | 63 | initiateTypedEffect(); 64 | 65 | /** 66 | Add to Drive functionality 67 | */ 68 | var AddToDrive = require("./addToDrive.js"); 69 | 70 | // When user clicks an "add to drive" button, add a copy of the base template to their Google Drive 71 | $("body").on("click", "[data-add-to-drive]", AddToDrive.main) 72 | .on("submit", "[data-update-endpoint]", AddToDrive.updateEndpoint) 73 | .on("click", "[data-show-endpoint-form]", AddToDrive.editEndpoint); 74 | 75 | /** 76 | Effects on scroll down of index page 77 | */ 78 | var options = [{ 79 | selector: '.next_steps', 80 | offset: 300, 81 | callback: function() { 82 | Materialize.showStaggeredList('#nextSteps'); 83 | } 84 | }, ]; 85 | Materialize.scrollFire(options); -------------------------------------------------------------------------------- /src/scss/_documentation.scss: -------------------------------------------------------------------------------- 1 | .documentation__body { 2 | hr { 3 | margin: 65px auto; 4 | width: 300px; 5 | } 6 | code { 7 | padding: 2px; 8 | background-color: whitesmoke; 9 | } 10 | h2 { 11 | font-size: 3rem; 12 | font-weight: 500; 13 | } 14 | h3 { 15 | font-size: 2rem; 16 | } 17 | h4, h5 { 18 | font-size: 1rem; 19 | // margin-top: 2.5rem; 20 | font-weight: 500; 21 | color: $primary-color; 22 | } 23 | ul li:before { 24 | content: "\003E"; 25 | } 26 | ol { 27 | counter-reset: numberedlist; 28 | list-style: none; 29 | padding: 0; 30 | } 31 | ol li:before { 32 | counter-increment: numberedlist; 33 | content: counter(numberedlist); 34 | } 35 | li { 36 | margin-left: 2em; 37 | &:not(:last-child) { 38 | margin-bottom: 1em; 39 | } 40 | &:before { 41 | margin-right: 1em; 42 | color: $secondary-color; 43 | } 44 | } 45 | } 46 | @media #{$small-and-down} { 47 | .documentation__body h2, .documentation_hero__title { 48 | font-size: 10vw; 49 | } 50 | .documentation__body hr { 51 | width: 100%; 52 | margin: 35px auto; 53 | } 54 | } -------------------------------------------------------------------------------- /src/scss/_extra_colors.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Additional colour classes 3 | */ 4 | .primary { 5 | background-color: $primary-color; 6 | } 7 | .primary-text { 8 | color: $primary-color; 9 | } 10 | 11 | .primary-light { 12 | background-color: $primary-color-light; 13 | } 14 | 15 | .accent { 16 | background-color: $secondary-color; 17 | } 18 | .accent-text { 19 | color: $secondary-color; 20 | } 21 | .success-text { 22 | color: $success-color; 23 | } 24 | .success { 25 | background-color: $success-color; 26 | &.btn:hover, &.btn-large:hover { 27 | background-color: lighten($success-color, 5%); 28 | 29 | } 30 | } 31 | .warn-text { 32 | color: $error-color; 33 | } 34 | .warn { 35 | background-color: $error-color; 36 | &.btn-large, &.btn { 37 | &:hover, &:active, &:focus { 38 | background-color: lighten($error-color, 5%); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/scss/_index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | SECTIONS 3 | 1) Hero 4 | 2) Features 5 | 3) Getting Started 6 | 4) Next Steps 7 | */ 8 | // Hero 9 | .hero { 10 | // height: 60vh; 11 | background-color: $primary-color; 12 | position: relative; 13 | &:before { 14 | content: ""; 15 | background-color: rgba(0, 0, 0, 0.15); 16 | @include fill; 17 | } 18 | } 19 | .hero__wrapper { 20 | z-index: 1; 21 | } 22 | .hero__title { 23 | font-weight: 600; 24 | margin-top: 0; 25 | } 26 | @media #{$small-and-down} { 27 | .hero { 28 | height: auto; 29 | width: 100%; 30 | padding: $master-padding 0; 31 | } 32 | .hero__subtitle br { 33 | display: none; 34 | } 35 | } 36 | 37 | // Features 38 | // .feature { 39 | // margin-top: $master-padding*0.5; 40 | // // margin-bottom: $master-padding*0.5; 41 | // } 42 | 43 | // .feature__image { 44 | // $dim: 180px; 45 | // height: $dim; 46 | // width: $dim; 47 | // // background-color: $grey-light; 48 | // background-repeat: no-repeat; 49 | // background-size: 50%; 50 | // background-position: center; 51 | // margin: 0 auto; 52 | // @for $i from 1 through 3 { 53 | // .feature:nth-child(#{$i}) & { 54 | // background-image: url("/img/google-drive-cms-#{$i}.svg"); 55 | // } 56 | // } 57 | // } 58 | 59 | // @media #{$small-and-down} { 60 | // .feature { text-align: left; } 61 | // .feature__image { 62 | // $mobileDim: 32px; 63 | // float: left; 64 | // margin-right: 15px; 65 | // height: $mobileDim; 66 | // width: $mobileDim; 67 | // position: relative; 68 | // top: 0.5*$mobileDim; 69 | // background-color: transparent; 70 | // background-size: contain; 71 | // } 72 | // .feature h5 { 73 | // display: block; 74 | // margin-top: 15px; 75 | // } 76 | 77 | // } 78 | 79 | // Gettings Started 80 | .getting_started { 81 | counter-reset: getting_started; 82 | } 83 | .getting_started__step { 84 | counter-increment: getting_started; 85 | padding: 110px 0; 86 | @media #{$small-and-down} { 87 | padding: 55px 0; 88 | } 89 | &:nth-child(odd) { 90 | background-color: $grey-soft; 91 | } 92 | .warn-text i { 93 | margin-right: 15px; 94 | } 95 | &.--final { 96 | min-height: 25vh; 97 | background-image: url("/img/howto__publish.gif"); 98 | background-repeat: no-repeat; 99 | background-position: top right; 100 | position: relative; 101 | @media #{$small-and-down} { 102 | background-position: top center; 103 | } 104 | &:before { 105 | content: ""; 106 | background-image: linear-gradient(90deg, darken($grey-soft, 5) 40%, transparent 70%); 107 | @include fill; 108 | } 109 | .container { 110 | z-index: 1; 111 | } 112 | h3:before { 113 | content: counter(getting_started) '.'; 114 | margin-right: 15px; 115 | font-weight: 200; 116 | @media #{$small-and-down} { 117 | display: block; 118 | } 119 | } 120 | } 121 | } 122 | .getting_started__step__number { 123 | font-size: 2vw; 124 | &:before { 125 | content: counter(getting_started) '.'; 126 | } 127 | } 128 | 129 | // Next steps 130 | .next_steps { 131 | .divider { 132 | margin-top: 0.5*$master-padding; 133 | margin-bottom: 0.5*$master-padding; 134 | } 135 | ul li { opacity: 0; } 136 | } 137 | @media #{$medium-and-up} { 138 | .next_steps__wrapper.valign { 139 | display: flex; 140 | } 141 | .next_steps__wrapper, .next_steps { 142 | align-items: flex-end; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/scss/_math.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Math functions provided by Sass-Math 3 | @link https://github.com/adambom/Sass-Math/blob/master/math.scss 4 | */ 5 | 6 | @function power($x, $n) { 7 | /** 8 | Editor note: 9 | Modified to handle indicies of 0 10 | */ 11 | $ret: 1; 12 | @if $n > 0 { 13 | @for $i from 1 through $n { 14 | $ret: $ret * $x; 15 | } 16 | } 17 | @else { 18 | @for $i from $n to 0 { 19 | $ret: $ret / $x; 20 | } 21 | } 22 | @return $ret; 23 | } 24 | 25 | @function factorial($x) { 26 | $ret: 1; 27 | @if $x > 0 { 28 | @while $x > 0 { 29 | $ret: $ret * $x; 30 | $x: $x - 1; 31 | } 32 | } 33 | @return $ret; 34 | } 35 | 36 | @function sin($x) { 37 | $ret: 0; 38 | @for $n from 0 to 25 { 39 | $ret: $ret + power(-1, $n) * power($x, 2 * $n + 1) / factorial(2 * $n + 1); 40 | } 41 | @return $ret; 42 | } 43 | 44 | @function cos($x) { 45 | $ret: 0; 46 | @for $n from 0 to 25 { 47 | $ret: $ret + power(-1, $n) * power($x, 2 * $n) / factorial(2 * $n); 48 | } 49 | @return $ret; 50 | } 51 | 52 | @function exp($x) { 53 | $ret: 0; 54 | @for $n from 0 to 25 { 55 | $ret: $ret + power($x, $n) / factorial($n); 56 | } 57 | @return $ret; 58 | } 59 | 60 | @function ln($x) { 61 | $ret: 0; 62 | $n: 1; 63 | $dx: 0.001; 64 | @while $n <= $x { 65 | $ret: $ret + $dx / $n; 66 | $n: $n + $dx; 67 | } 68 | @return $ret; 69 | } 70 | 71 | @function sqrt($x) { 72 | @return exp(0.5 * ln($x)); 73 | } -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin fill { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | } -------------------------------------------------------------------------------- /src/scss/_settings.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary-color: color("teal", "base"); 3 | $primary-color-light: color("teal", "lighten-2"); 4 | $primary-color-dark: color("teal", "darken-2"); 5 | 6 | $secondary-color: color("amber", "accent-3"); 7 | $success-color: color("green", "base"); 8 | $error-color: color("red", "base"); 9 | $link-color: color("light-blue", "darken-1"); 10 | 11 | $grey-light: color("grey", "lighten-4"); 12 | $grey-dark: color("grey", "darken-3"); 13 | $grey-soft: #fafafa; 14 | 15 | $input-border-color: color("grey", "lighten-3"); 16 | 17 | 18 | // Dimensions 19 | $master-padding: 55px; 20 | -------------------------------------------------------------------------------- /src/scss/_typed.scss: -------------------------------------------------------------------------------- 1 | .typed-cursor{ 2 | opacity: 1; 3 | -webkit-animation: blink 0.7s infinite; 4 | -moz-animation: blink 0.7s infinite; 5 | animation: blink 0.7s infinite; 6 | } 7 | @keyframes blink{ 8 | 0% { opacity:1; } 9 | 50% { opacity:0; } 10 | 100% { opacity:1; } 11 | } 12 | @-webkit-keyframes blink{ 13 | 0% { opacity:1; } 14 | 50% { opacity:0; } 15 | 100% { opacity:1; } 16 | } 17 | @-moz-keyframes blink{ 18 | 0% { opacity:1; } 19 | 50% { opacity:0; } 20 | 100% { opacity:1; } 21 | } -------------------------------------------------------------------------------- /src/scss/_utilities.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Type classes 3 | */ 4 | .--thick { font-weight: 700; } 5 | .--thin { font-weight: 200; } 6 | 7 | /** 8 | Micro classes 9 | */ 10 | 11 | 12 | .vam { 13 | vertical-align: middle; 14 | } 15 | 16 | .--no-margin { 17 | margin: 0; 18 | } 19 | 20 | /** 21 | Miscellaneous 22 | */ 23 | .section-padding { 24 | padding: $master-padding 0; 25 | @media #{$small-and-down} { 26 | padding: $master-padding*0.5 0; 27 | } 28 | } 29 | 30 | .nav-dimensions { 31 | height: $navbar-height-mobile; 32 | line-height: $navbar-height-mobile; 33 | } 34 | 35 | .--hover-underline:hover { 36 | border-bottom: 1px solid; 37 | } 38 | 39 | pre { 40 | padding: 20px; 41 | background-color: #f5f2f0; 42 | } 43 | 44 | header svg { 45 | height: 32px; 46 | vertical-align: middle; 47 | } 48 | 49 | .button-collapse { 50 | margin-right: 15px; 51 | } 52 | 53 | @media #{$medium-and-down} { 54 | .side-nav li svg path { fill: black; } 55 | } 56 | 57 | // @media #{$large-and-up} { 58 | header ul li:first-child { 59 | font-weight: 500; 60 | } 61 | // } 62 | 63 | .select-wrapper span.caret { 64 | color: white; 65 | } -------------------------------------------------------------------------------- /src/scss/app.scss: -------------------------------------------------------------------------------- 1 | /** 2 | VENDOR 3 | */ 4 | @import "bourbon"; 5 | @import "scut"; 6 | 7 | /** 8 | MATERIALIZE 9 | */ 10 | // Mixins 11 | @import "components/prefixer"; // Remove when you bump versions 12 | @import "components/mixins"; 13 | @import "components/color"; 14 | 15 | // Variables; 16 | @import "settings"; // Our custom settings file 17 | @import "components/variables"; 18 | 19 | // Reset 20 | @import "components/normalize"; 21 | 22 | // components 23 | @import "components/global"; 24 | @import "components/icons-material-design"; 25 | @import "components/grid"; 26 | @import "components/navbar"; 27 | @import "components/roboto"; 28 | @import "components/typography"; 29 | @import "components/cards"; 30 | @import "components/toast"; 31 | // @import "components/tabs"; 32 | // @import "components/tooltip"; 33 | @import "components/buttons"; 34 | @import "components/dropdown"; 35 | @import "components/waves"; 36 | @import "components/modal"; 37 | @import "components/collapsible"; 38 | // @import "components/chips"; 39 | @import "components/materialbox"; 40 | // @import "components/form"; 41 | @import "components/forms/forms"; // Readd when you can upgrade version 42 | @import "components/table_of_contents"; 43 | @import "components/sideNav"; 44 | @import "components/preloader"; 45 | // @import "components/slider"; 46 | // @import "components/carousel"; 47 | // @import "components/date_picker/default"; 48 | // @import "components/date_picker/default.date"; 49 | // @import "components/date_picker/default.time"; 50 | 51 | /** 52 | Custom imports 53 | */ 54 | @import "math"; 55 | @import "mixins"; 56 | @import "typed"; 57 | 58 | /** 59 | Custom resets 60 | */ 61 | html { 62 | box-sizing: border-box; 63 | } 64 | 65 | *, *:before, *:after { 66 | box-sizing: inherit; 67 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 68 | } 69 | 70 | ::selection { 71 | background: lighten($secondary-color, 25%); 72 | } 73 | ::-moz-selection { 74 | background: lighten($secondary-color, 25%); 75 | } 76 | 77 | .dropdown-content li span { 78 | color: $primary-color; 79 | } 80 | 81 | /** 82 | Site Components 83 | */ 84 | @import "extra_colors"; 85 | @import "utilities"; 86 | 87 | /** 88 | Site pages 89 | */ 90 | @import "index"; 91 | @import "documentation"; 92 | -------------------------------------------------------------------------------- /src/scss/fonts.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Import font-face 3 | */ -------------------------------------------------------------------------------- /src/templates/data/data.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/templates/data/features.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Easy", 4 | "mobileTitle": "Easy", 5 | "deck": "Create content for your website or app in an environment millions use every day." 6 | }, 7 | { 8 | "title": "Secure", 9 | "deck": "Google Drive's share settings form the backbone of authenticating CMS admins." 10 | }, 11 | { 12 | "title": "Flexible", 13 | "deck": "Use customisable settings to publish your content to an endpoint and format of your choosing." 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /src/templates/data/instructions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Copy the Google Drive CMS template to your Google Drive", 4 | "deck": "

This template provides the Apps Scripts and basic structure to publish content to your API endpoint.

Either use the big yellow buttons on this page, or make a copy of a pre-existing Google Drive CMS template in your Drive.

", 5 | "extra": "
", 6 | "img": "/img/getting-started/copy-template-to-drive.png", 7 | "caption": "Add the Google Drive CMS template to your Google Drive" 8 | }, { 9 | "title": "Create headers for your content", 10 | "deck": "

On the CMS sheet of your new Google Drive CMS template add headers, which will be used as the keys in the JSON object sent to your API.
Think of these headers like field names in a database.

For example, a Spreadsheet that looks like this:

NameEmail

Will send JSON to your endpoint that looks like this:
", 11 | "extra": "
See example Spreadsheet" 12 | }, { 13 | "title": "Add an API to the settings", 14 | "deck": "

Open the sheet on the Spreadsheet called SETTINGS and edit the endpoint value to the API you want the Spreadsheet's content sent to.

", 15 | "extra": "

info_outlineNote! You need to wire-up how your chosen endpoint handles the sent data. Google Drive CMS is unopinionated in this regard.

", 16 | "img": "/img/getting-started/customise-endpoint.png", 17 | "caption": "Edit the endpoint value in the SETTINGS sheet" 18 | } 19 | ] -------------------------------------------------------------------------------- /src/templates/data/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Google Drive CMS", 3 | "description": "Create and manage content for your website straight from Google Drive. Enjoy the security, ease-of-use and familiarity that Google Drive brings as a content management system.", 4 | "keywords": "", 5 | "baseurl": "http://drivecms.xyz" 6 | } -------------------------------------------------------------------------------- /src/templates/data/navigation.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": [ 3 | { "title": "Getting started", "url": "/#getting-started", "blank": false }, 4 | { "title": "Documentation", "url": "/documentation.html", "blank": false }, 5 | { "title": "Examples", "url": "/examples.html", "blank": false } 6 | ], 7 | "footer": [ 8 | { "title": "Documentation", "url": "/documentation.html" }, 9 | { "title": "Project on Github", "url": "https://github.com/max-barry/google-drive-cms", "blank": true }, 10 | { "title": "More by mxbry.com", "url": "https://mxbry.com", "blank": true } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/templates/data/readme.json: -------------------------------------------------------------------------------- 1 | {"content":"

www.drivecms.xyz

\n

Introduction

\n

The Google Drive CMS uses a combination of Google Sheets and Google Docs to maintain content on a website. These documents are sent to a site or a supporting service (e.g. a Firebase database) via a POST request to an API the admin specifies.

\n

The only requirements to run the Google Drive CMS are:

\n\n

It is possible to run the CMS without writing server side code at the chosen API endpoint. The CMS' content can be exported as a JSON file, or it can be added directly to any PAAS databases that expose an API. An example of using Firebase to create the latter of these flows can be found on our examples page.

\n

Practical examples

\n

The following scenarios are good use cases for the Google Drive CMS:

\n\n
\n

Installation

\n

To begin using the Google Drive CMS you need a copy of the core template. This core template contains the default configuration settings and the Google Apps Scripts that live under the hood of the Google Drive CMS.

\n

To get a copy of the core template there are 3 options:

\n
    \n
  1. Add it to you Google Drive direct from drivecms.xyz
  2. \n
  3. Make a copy of the core template yourself using this link
  4. \n
  5. Copy an existing Google Drive CMS template in your Google Drive
  6. \n
\n

After creating a copy of the core template, change the endpoint value within the SETTINGS sheet. For more information on configuring your Google Drive CMS template, see the settings section of this documentation.

\n

Publishing content

\n

The Google Drive CMS will add a custom menu option to the top of the Google Sheets interface. The Google Drive CMS menu item contains two actions.

\n

Publish publishes the CMS' content to the designated endpoint

\n

Export content exports the contents of the CMS as a JSON file to a directory called _exports inside of the same Google Drive folder that contains the base template itself

\n
\n

Understanding the spreadsheet

\n

The core template has four tabs. Each tab can be accessed from Google Drive's tab navigation toolbar at the bottom of the page.

\n

CMS

\n

The main tab where an admin inputs their data.

\n

Headers

\n

The top row of the CMS tab represents the content's headers. These are used as the keys to map the content's data against in the JSON object sent to your endpoint. Headers behave similar to column names in a standard database.

\n

\"Example

\n

Field types

\n

Field types let admins add special functionality to a column. There are three field types, currently:

\n\n

n.b. A blank field type will behave like a simple field.

\n

We are looking to add more in the future, including foreign key relationships between multiple sheets. Eval is a good stopgap for more complex data, as it can accept raw JavaScript arrays or objects. Update Google Drive CMS now supports pointing at other Google Sheets to create neater nested objects.

\n

Content rows

\n

Each row beneath the field types will become an object within the JSON array sent to the designated API endpoint. These are the equivalent of a record within a traditional database.

\n

For example, an individual blog post is a content row, and it might have headers like "title" or "publication date".

\n

Settings

\n

The SETTINGS tab within the Google Drive CMS template allows customization the CMS' behavior.

\n

The following settings can be configured:

\n\n

_internals

\n

It might be best to just leave this alone. Values in this tab power current and future functionality at a low level.

\n
\n

Advanced features

\n

Nested data

\n

NEW\nUse a field type of Google Sheet to nest other Google Drive CMS sheets inside of your POST data.

\n
    \n
  1. Create a secondary Google Drive CMS The only sheet we really need is the default CMS sheet. You could alternatively create a blank Google Sheet with 2 rows (headers and field types), and your data underneath. The SETTINGS, DOCUMENTATION and _internals sheets on your secondary spreadsheet are not used.
  2. \n
  3. Copy and past the URL or spreadsheet ID in to your Drive CMS. Remember to set the field type to Google Sheet.
  4. \n
  5. Publish as normal
  6. \n
\n

Any data in the second spreadsheet will be added to your JSON payload as a nested array.

\n

Example

\n

Google Sheet A (your Google Drive CMS instance)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
titlenested
StringGoogle Sheet
A cool title< URL of Google Sheet B >
Another cool title
\n

Google Sheet B (the data you want to nest)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
FieldAFieldB
StringString
Content that is nestedContent that is also nested
Second nested itemSome information
\n

Result

\n
[\n  {\n    "title": "A cool title",\n    "nested": [\n      {\n        "FieldA": "Content that is nested",\n        "FieldB": "Content that is also nested"\n      },\n      {\n        "FieldA": "Second nested item",\n        "FieldB": "Some information"\n      }\n    ]\n  },\n  {\n    "title": "Another cool title",\n    "nested": ""\n  }\n]\n

You could probably nest sheets within sheets within sheets. That would probably work.\nDon't point spreadsheets at each other. That's going to end in a loop. That would probably be bad.

\n

Rich text

\n

It is possible to combine the Google Drive CMS core template with Google Docs to give an admin a rich text editor.

\n

Write a standard Google Doc using Google Doc's built in rich text capabilities (headings, links, inline imagery, etc.). Give that doc share settings that would allow the owner of the CMS core template at least view access. Add just the URL of that Google Doc to a cell within a content row of the CMS, and when published the Doc will be transformed in to raw HTML tags and added to the JSON payload.

\n

Google Docs are structured such that they can be exported straight to HTML. The CMS performs light sanitization of the converted HTML, but otherwise it is returned as a string within the JSON document upon publishing.

\n

Image replacement

\n

Images inserted in to Google Docs are hosted on Google's CDN. When the rich text is extracted you'll have img tags with a src="https://cdn.google.com/...". That's okay-ish for small projects, or projects where images aren't vital. Google rate limit access to these URLs meaning every now and again your images will fail to load, as the Google CDN returns a 403.

\n

To get around this Google Drive CMS provides support for externally hosted images. In your rich text Google Doc that is being consumed by the drive CMS add the following inline:

\n
This is the text content written in my Google Doc and here is my pretty image:\n\n[IMAGE:https://media.giphy.com/media/l46CqLVMWzaJUFPLW/giphy.gif]\n\nAnd then my text carries on like normal.\n

When the rich text is extracted and posted to your JSON api your img tag will now have a source of https://media.giphy.com/media/l46CqLVMWzaJUFPLW/giphy.gif ([IMAGE:] is replaced). Using externally hosted images also allows you to use image formats that a Google Doc doesn't natively support (Gifs!).

\n

Slugify

\n

A common CMS requirement is a slug field. A slug field can be used to build URLs on a site, and is often a concatenation of a title field with hyphens. For example, a news article called "Big Announcement Coming" would have a slug of "big-announcement-coming".

\n

To expedite this process the Google Drive CMS has a shortcut function called SLUGIFY. Add the cell function =SLUGIFY(:cell:) to a cell in the CMS sheet, where :cell: is any string input (e.g. the corresponding cell in a title column).

\n
\n

Best practices

\n

Securing the CMS

\n

Consider access to the Google Drive CMS spreadsheet like the password and email logins for a traditional CMS. If you wouldn't want someone in your WordPress, don't share them access to your spreadsheet!

\n

To better understand the security and sharing potential of Google Drive, check out Google's documentation.

\n

If sharing the CMS spreadsheet with other admins, consider locking down parts of the document using protected ranges. For example, protect the headers row of the spreadsheet to prevent another admin breaking a data structure the API endpoint requires.

\n

Revision history

\n

Admins can rollback to previous iterations using Google Drive's built in revision history. Restore a previous version of the sheet and then republish the document.

\n

Extending the CMS

\n

All of the Apps Scripts that power the CMS are baked in to the sheet an admin edits. These scripts can be accessed via Tools > Script editor... from the Google Sheets toolbar. We've tried to comment the .gs files where possible, so hack and extend at will! Changes to these scripts will only effect the current instance of the CMS.

\n","headers":[{"id":"introduction","title":"Introduction"},{"id":"installation","title":"Installation"},{"id":"understanding-the-spreadsheet","title":"Understanding the spreadsheet"},{"id":"advanced-features","title":"Advanced features"},{"id":"best-practices","title":"Best practices"}]} -------------------------------------------------------------------------------- /src/templates/includes/components/_add-to-drive.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/templates/includes/components/_add-to-drive__error.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/includes/components/_add-to-drive__error_modal_upload.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/templates/includes/components/_add-to-drive__success.html: -------------------------------------------------------------------------------- 1 | doneSuccess! View on Drive -------------------------------------------------------------------------------- /src/templates/includes/components/_add-to-drive__updateEndpoint.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
-------------------------------------------------------------------------------- /src/templates/includes/components/_loader.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/templates/includes/components/_signupform.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 11 | 12 |
13 |
14 | 15 |
16 | 17 | 20 |
21 | -------------------------------------------------------------------------------- /src/templates/includes/components/_standard_hero.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/includes/favicon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/templates/includes/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
About this project
6 |

Do you like this project and want to keep up to date on its progress?

7 | {% include "includes/components/_signupform.html" %} 8 |
9 |
10 |
Useful links
11 |
    12 | {% for link in navigation.footer %} 13 |
  • » {{link.title}}
  • 14 | {% endfor %} 15 |
16 |
17 |
18 |
19 | 24 |
25 | -------------------------------------------------------------------------------- /src/templates/includes/header.html: -------------------------------------------------------------------------------- 1 |
2 | 20 |
-------------------------------------------------------------------------------- /src/templates/includes/index/_feature.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{feature.title}}

4 |
{{feature.deck|safe}}
5 |
-------------------------------------------------------------------------------- /src/templates/includes/index/_instruction.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {% if instruction.img %} 5 |
Add the Google Drive CMS template to your Google Drive
6 | {% endif %} 7 |
8 |
{{instruction.title}}
9 | {{instruction.deck|safe}} 10 | {{instruction.extra|safe}} 11 |
12 |
13 |
-------------------------------------------------------------------------------- /src/templates/includes/index/features.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% for feature in features %} 4 | {% include "includes/index/_feature.html" %} 5 | {% endfor %} 6 |
7 |
-------------------------------------------------------------------------------- /src/templates/includes/index/hero.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

The Google Drive CMS

4 |
A content management system (CMS)
built on an interface everyone understands.
5 |

6 | {% include "includes/components/_add-to-drive.html" %} 7 |

8 | How does it work 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/templates/includes/index/instructions.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

The Google Drive CMS is a Google Sheet with a custom menu option to publish the content of that spreadsheet to your site's API. Using this workflow you could send the contents of your Google Sheet

4 |
5 |
6 |
7 | {% for instruction in instructions %} 8 | {% include "includes/index/_instruction.html" %} 9 | {% endfor %} 10 |
11 |
12 |

Hit publish from the main toolbar

13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/templates/includes/index/next-steps.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Next Steps »

5 |
6 |
    7 |
  • 8 |

    Documentation

    9 |

    Fairly complete documentation for the various settings found within the sheet.
    Read the documentation

    10 |
  • 11 |
  • 12 |
  • 13 |

    Firebase example

    14 |

    Connecting the CMS directly to Firebase to remove the need for a server endpoint.
    Integrate with firebase

    15 |
  • 16 |
17 |
18 |
-------------------------------------------------------------------------------- /src/templates/includes/socialmeta.html: -------------------------------------------------------------------------------- 1 | {# 2 | Twitter - https://dev.twitter.com/cards/overview 3 | N.B. All cards require validation. Visit https://cards-dev.twitter.com/validator and validate your site 4 | Some cards require additional markup or different image dimensions. See twitter_cards for more details. 5 | 6 | title - required - max. 70 chars. 7 | description - required - max. 200 chars. 8 | 9 | site - @username of website 10 | site:id - Twitter ID of site account 11 | 12 | creator - @username of content creator 13 | creator:id - Twitter ID of creator account 14 | 15 | url - required - Canonical URL of page being shared 16 | domain - Base domain of URL 17 | card (card type) - required - summary, summary_large_image, photo, gallery, app, player, product 18 | 19 | image:src - target 1000x1000 - Absolute URL 20 | image:width - min. dim. 120x120 21 | image:height - min. dim. 120x120 22 | #} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {# 39 | Facebook - https://developers.facebook.com/docs/opengraph/creating-custom-stories/#objecttypes-properties 40 | 41 | title 42 | description 43 | 44 | url - Canonical URL of the page the tag is placed on 45 | site_name - Human readable name of site 46 | image - Min. dim. 600x315. Recommend dim. 1200x630 - Recommend aspect ratio 1.91:1 - Max size 5MB - Absolute URL 47 | 48 | type - https://developers.facebook.com/docs/reference/opengraph/ - e.g. website or article 49 | 50 | locale - Language locale of the page - Default en_US 51 | 52 | restrictions - Restrictions applied to the page (age, country, content, etc) - https://developers.facebook.com/docs/opengraph/using-actions/v2.2#userrestrictions 53 | 54 | see_also - Additional related links to the page 55 | ttl - Number of seconds before the page is re-scraped - Default 7 days 56 | #} 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/templates/layouts/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{meta.title}}{% block meta_title %}{% endblock meta_title %} 7 | 8 | 9 | 10 | {% include "./includes/socialmeta.html" %} 11 | {% include "./includes/favicon.html" %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 30 | 31 | 32 | {% include "./includes/header.html" %} 33 | 34 | {% block content %}{% endblock %} 35 | 36 | {% include "./includes/footer.html" %} 37 | 38 | 39 | {% block extrajs %}{% endblock %} 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/templates/misc/readme.template.html: -------------------------------------------------------------------------------- 1 | <%=content%> -------------------------------------------------------------------------------- /src/templates/pages/documentation.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block meta_title %} | Documentation{% endblock meta_title %} 4 | {% block meta_description %}Google Drive content management system (CMS) documentation. Adjust settings within the CMS and learn how to extend it to help create content for your website straight from Google Drive{% endblock meta_description %} 5 | 6 | 7 | {% block content %} 8 | {% set hero_details = { title: "Documentation", breadcrumbUrl: "/documentation", deck: "The Google Drive CMS is a content management system (CMS) built on an interface everyone understands."} %} 9 | {% include "includes/components/_standard_hero.html" %} 10 |
11 |
12 |
13 | {{readme.content|safe}} 14 |
15 |
16 |
    17 | {% for header in readme.headers %} 18 |
  • {{header.title}}
  • 19 | {% endfor %} 20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | 26 | {% block extrajs %} 27 | 28 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/pages/examples.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block meta_title %} | Examples{% endblock meta_title %} 4 | {% block meta_description %}Google Drive content management system (CMS) examples. Examples and practical case studies that teach you how to use Google Drive to create content for your website{% endblock meta_description %} 5 | 6 | {% block content %} 7 | {% set hero_details = { title: "Examples", breadcrumbUrl: "/examples", deck: "Practical implementations of the Google Drive CMS workflow."} %} 8 | {% include "includes/components/_standard_hero.html" %} 9 |
10 |
11 |
12 |
13 |
14 | Google Drive CMS & Firebase 15 | Google Drive CMS & Firebase 16 |
17 |
18 |

Using Firebase to create an instance of the Google Drive CMS without the need to write server side code to handle the JSON payload.

19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 | Demo Spreadsheet for a simple blog 29 | Demo Spreadsheet for a simple blog 30 |
31 |
32 |

A demo spreadsheet for a blog application sending its content to a Firebase endpoint.

33 |
34 |
35 |
36 | Check out the demo 37 |
38 |
39 |
40 |
41 |
42 | {% endblock %} 43 | 44 | {% block extrajs %} 45 | {# #} 46 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block content %} 4 | {% include "includes/index/hero.html" %} 5 | {# {% include "includes/index/features.html" %} #} 6 | {% include "includes/index/instructions.html" %} 7 | {% include "includes/index/next-steps.html" %} 8 | {% endblock %} 9 | 10 | {% block extrajs %} 11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | DONE 2 | Wire up newsletter to mailchimp 3 | Documentation 4 | Edit the endpoint of the template i just created (put it in an accordion) 5 | Sitemap created 6 | Wire up the navigation header links 7 | Instruction images 8 | 9 | KINDA DONE 10 | Mobile / tablet mode 11 | 12 | TODO 13 | Social meta imagery 14 | 15 | WON'T DO 16 | Logo for top left (drive icon > webpage) 17 | Fixed header? --------------------------------------------------------------------------------