Code Monkey home page Code Monkey logo

roots-contentful's Introduction

Roots Contentful

Gitter Chat

npm tests dependencies devDependencies Coverage Status

An extension for using roots with the Contentful CMS API.

Note: This project is in early development, and versioning is a little different. Read this for more details.

Why Should You Care?

We love static sites. They're fast, resilient, simple, and cheap.

However, managing and updating content on a static site is a pain. This extension allows you to load data from Contentful's API into your roots project for use in your view templates during compilation. Non-developers get an easy way to publish and manage content, while developers can still build static sites in a sane way.

Installation

  • make sure you are in your roots project directory
  • npm install roots-contentful --save
  • modify your app.coffee file to include the extension, as such
contentful = require 'roots-contentful'

# ...

module.exports =
  extensions: [
    contentful
      access_token: 'YOUR_ACCESS_TOKEN'
      space_id: 'xxxxxx'
      content_types:
        blog_posts:
          id: 'xxxxxx'
          template: 'views/_post.jade'
          filters: { 'fields.environment[in]': ['staging', 'production'] }
          path: (e) -> "blogging/#{e.category}/#{slugify(e.title)}"
          write: 'data.json'
          sort: compareFunction
          transform: transformFunction
        press_links:
          id: 'xxxxxx'
  ]

# ...

Usage

Accessing Content in Views

A contentful view helper object will be passed into every view containing your content. Each content type will be set as a property on contentful using the name option in your app.coffee configuration. For example with the app.coffee file above, you can access the blog posts like this:

  h1 Hello World
  ul
    - for post in contentful.posts
      li
        h2= post.title
        p= markdown(post.body)

Note: for the above markdown function to parse your post.body, you need to install a markdown parser for roots to work with. Using marked as an example, do the following:

  • npm install marked --save

And then in app.coffee:

contentful = require 'roots-contentful'
marked     = require 'marked'

locals:
  markdown: marked

See the roots documentation for more details.

Note: if you have Links in your content more than 10 levels deep (the max for the include parameter), then unresolved links can be returned.

Single Entry Views

If a template option is defined for a Content Type in app.coffee, roots will compile a single page view for each entry in that Content Type collection. The entry will also have a _url key that returns the path to the single page view (so you can create links on an index page for example).

The Entry Object

Contentful's documentation shows the API response when fetching an entry. Your content fields are nested in a fields key on the entry object. As a convenience, the entry object roots-contentful makes available in your views will have the fields key's value set one level higher on the object. System metadata remains accessible on the sys key and roots-contentful will raise an error if you have a field named sys. Inside your views, the entry object will have this structure:

"entry": {
  "title": "Wow. Such title. Much viral",
  "author": "The Doge of Venice"
  # ... the rest of the fields
  "sys": {
    "type": "Entry",
    "id": "cat"
    # ...
  }
}

And can be accessed in your view like this:

  h2= entry.title
  p= markdown(entry.body)
  if entry.image
    img(src!= asset(entry.image)

Configuration Options

access_token

Required. Your Contentful Delivery access token (API key).

space_id

Required. The space ID containing the content you wish to retrieve.

preview

Optional. (Boolean) Allows you use the Contentful Preview API. Also able to be accessed by setting the environment variable CONTENTFUL_ENV to "develop" (preview api) or "production" (default cdn).

content_types

An object whose key-value pairs correspond to a Contentful Content Types. Each content type's entries will be set on the contentful locals object using the key used in the config.

Configuring a content_type

Each object in the content_types array can have the following properties:

id

Required. The Content Type's ID on Contentful.

name

Optional. This is the name of the key the entries will be attached to on the contentful object in your views. Defaults to a pluralized, underscored representation of the Content Type name (e.g. 'Blog Post' => contentful.blog_posts)

template

Optional. Path relative to the roots project of a template for a single entry view. Each entry in the Content Type will be passed into the template in an entry variable. If not given, the Content Type will not be compiled into single entry views and will only be attached to the contentful view helper object.

filters

Optional. Takes an object with different search and filter criteria. See examples of how to structure the object in Contentful's docs.

For example, the following limits the API response to 5 entries, filters by the feature field and orders items in the response by the sys.createdAt property. Notice the minus sign, which reverses the sort order.

content_types:
  blog_posts:
    id: 'xxxxxx'
    template: 'views/_post.jade'
    path: (entry) -> "blog/#{entry.permalink}"
    filters: {
               'limit': '5',
               'fields.feature': 'true',
               'order': '-sys.createdAt'
             }
    transform: transformFunction

path

Optional. Provide a function that returns a string of the relative path to the output file for a given entry without the extension. First argument passed into the function is the entry. Default is <name>/<slug> where slug is the slugified output of the entry's displayField (a property of the Content Type), and name is the provided name option above or the default value. This option is ignored if no template is given.

If the function returns an array of paths, the given entry is compiled to multiple files. The array of paths can be accessed with entry._urls. For instance, the configuration below outputs each blog post entry into two folders, en and fr, for i18n purposes:

content_types:
  blog_posts:
    id: 'xxxxxx'
    template: 'views/_post.jade'
    path: (e) -> ("#{lang}/#{slugify(e.title)}" for lang in ['en', 'fr'])

write

Optional. Provide the relative path to the output file that will hold the JSON data of the current content type. The top level JSON object will be an array.

transform

Optional. Provide a function to transform (map) every entry in a content type. The transformed data is then added to the jade locals variable and written to JSON (if the write property is provided). Transform can also return a when.js promise.

transformFunction = (entry) ->
  delete entry.myProperty
  entry

sort

Optional. Provide a standard compare function that will sort all the data of a content type. The data is sorted after it is transformed and before it is provided to the jade locals variable or written as JSON.

compareFunction = (a, b) ->
  # 0           => a and b are equal
  # -1 or less  => a is before b
  # 1 or more   => a is after b
  a.number - b.number

Asset Image Helper

roots-contentful also provides a convenient view helper called asset that allows you to pass in the asset object returned from their API and returns the url. You can also pass in options that will be appended to the image url as a a query string that allows you to specificy size and quality params as documented here.

- for post in contentful.posts
  img(src!= asset(post.image, {w: 100, h: 100, q: 50}))

License & Contributing

roots-contentful's People

Contributors

axelboc avatar greenkeeperio-bot avatar hhsnopek avatar jescalan avatar joshrowley avatar kylemac avatar notslang avatar nporteschaikin avatar pxlpnk avatar terusus avatar tkraak avatar tmilewski avatar zspecza avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

roots-contentful's Issues

Potentially unhandled rejection

The query you sent was invalid. Probably a fiter or ordering specification is not applicable to the type of a field.

I am getting this error when trying to fetch data from contentful into roots.. followed your tutorial.

Thx.

License

Looks like the license is 404. I'm assuming you were intending to share this work, because it's awesome.

roots watch never completes but roots compile works fine

If I run roots compile everything works well, files are generated correctly, in the right location, etc.

If I run roots watch however, the process hangs at the compiling... step and never finishes.

With roots-contentful out of the project watch works perfectly.

render markdown issues

Per the readme, I would assume = markdown(post.body) would spit out my content as markdown. However, this just produces an error (undefined is not a function). If I just do = post.body, all the markdown is parsed out of it.

I can tell that I'm getting the original markdown due to your handy suggested = JSON.stringify(post.body), but am not sure how to translate this into something more legible. Am I missing a dependency in my package maybe?

debug mode

a configuration value that would log out network requests and their responses. perhaps with a log level

allow roots-contentful to be configured to pull localized entry

hack:

request = http_sync.request({
        method: 'GET',
        protocol: 'https',
        host: 'cdn.contentful.com',
        path: "/spaces/#{contentful_config.space_id}/entries/#{entry_id}?access_token=#{contentful_config.access_token}&locale=#{locale}"
      })

unsure what the config interface would look like...

sluggify sub-entries

Contentful allows to embed entries in other entries. Now if I want to make a link that leads to one of these sub-entries I have to apply the same procedure for creating a url (sluggify). However, require does not work in jade templates. Has anyone dealt with this already?

Cannot read property '___' of undefined

At random times (seemingly related to internet speed and connectivity) it seems that roots is trying to compile before the CONTENTFUL data is available, leading to a bunch of "Cannot read property '____' of undefined" errors.

Any ideas how to fix this?

roots-contentful doesn't play well with roots-yaml

I'm trying to use roots-contentful in combination with roots-yaml. Basically I need to access the data object created by roots-yaml from inside the single entry views compiled by roots-contentful. Unfortunately, the data object is undefined when I try to reference it from my views.

The problem seems to be that the views are compiled during the set-up of roots-contentful (inside the setup hook), but the data object is created by roots-yaml much later, in the before_pass compile hook.

The ordered flag has no effect, of course, since the extension is not dealing with files like roots-yaml is. If roots extensions had some sort of cleanup hook, the compilation could be moved in there, but there isn't.

The only solution I could find is to make the compile_entries function available on the Roots object, and to call it from the after hook in my project's app.coffee.

root-contentful/lib/index.coffee

# ...

    setup: ->
      types = opts.content_types
      types = if _.isPlainObject(types) then reconfigure_alt_type_config(types)
      @roots.compileContentfulViews = compile_entries.bind(@, types)

      configure_content(types).with(@)
        .then(get_all_content)
        .tap(set_urls)
        .tap(set_locals)

# ...

app.coffee

# ...

    after: (roots) ->
      roots.compileContentfulViews()

# ...

Reference asset directly

Is it possible to reference an asset only by its id, not through an entry? Or is this discouraged?

I try to create a website where the start page shows an image. An editor should be able to define/change this image, so I thought it's best as a contentful asset. Anyway, I don't have an entry for the start page, so I can't reference the asset.

Running roots compile or roots watch results in error

Apologies for the constant pestering but I am receiving an error whenever running roots compile or roots watch

My roots version is 3.0.0-rc.10.

The error is as follows:

compiling... Potentially unhandled rejection [3] TypeError: Cannot call method 'renderFile' of undefined at /Users/me/Documents/dev/testproject/node_modules/roots-contentful/lib/index.js:197:31 at mapWithIndex (/Users/me/Documents/dev/testproject/node_modules/roots- contentful/node_modules/when/lib/decorators/array.js:153:12) at /Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/decorators/fold.js:16:19 at tryCatchReject3 (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:769:7) at runContinuation3 (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:738:4) at Fulfilled.fold (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:540:4) at /Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/decorators/fold.js:15:25 at Pending.<anonymous> (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:368:7) at tryCatchReject (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:758:30) at runContinuation1 (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:728:4) at Fulfilled.when (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:544:4) at ContinuationTask.run (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/makePromise.js:648:24) at runQueue (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/Scheduler.js:75:18) at Scheduler._drain (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/Scheduler.js:50:3) at Scheduler.drain (/Users/me/Documents/dev/testproject/node_modules/roots-contentful/node_modules/when/lib/Scheduler.js:26:9) at process._tickCallback (node.js:442:13)

My config settings are as follows:

contentful = require 'roots-contentful'

module.exports =
extensions: [
  contentful({
    access_token: 'MYAPIKEY'
    space_id: 'MYSPACEID'
    content_types: [
      {
        id: 'MYCONTENTMODELID',
        name: 'posts',
        template: 'views/_post.jade',
        path: (e) -> "/#{e.category}/#{e.title}"
      }
    ]
  })
]

The template I am using for my Jade file, is a copy of the one you guys have provided in your documentation.

Thanks in advance.

Ben

caching requests

to avoid unnecessary network requests (and therefore contentful "bandwidth", which is limited depending on the amount of $ you pay) โ€“ we should cache the locals in-between "watch" compiles. to pull down fresh content that may have been edited since your last compile, you'll need to stop the watcher.

this configuration should be true by default, but overridden by cache: false in your extension config.

Get linked content

Hi, I was wondering if there is a way to get associated content for a content type.
Lets say you have a blog post and each one has an author which is its own content type.?

Sample code for a single entry view?

From what I gather, contentful.some_content_type returns an array containing all entries in said content type. How would I structure the Jade in my single entry view to only grab one entry (say, one blog post) per generation of single entry view though? Currently my template produces (obviously) output containing all posts.

extends _bloglayout

block content
    each post in contentful.blog_posts
        h1= post.title
        != markdown(post.body)

I'm kind of unsure how else I'd structure it so Roots can iterate through the array on each view generation. I get the feeling there's something very fundamental I'm missing regarding how roots-contentful does its generation of single entry views.

How to get inverse linked content?

Hello. I have a master data type which is linked by a child data type. I have pages for both the master and the child data type and in each page I would like to reference the other entity. Taking for instance the blog example, I would like a blog entry page to show its author and an author page to show their blog entries. How can I get the entries written by an author if only the author is linked in the entry? Regards

Contentful: Add option to Add New Press Link

Reproduce:

Error:

  • On the Playground, the user is able to add a new blog post entry, OR add a new press link, but this doesn't exist on Carrot.is

Expected:

  • User is able to add a new blog post entry OR add a new press link

add this for carrot is view

Create a video like the ones on the roots site for setup and configuration

Would really appreciate if you could provide additional detail or do a demo of how to set up this and run the plugin.

I am having so many problems with getting this to work and I have no idea where to start with regards to debugging unfortunately.

I did everything specified in the README, but still no luck unfortunately. I just get a bunch of errors thrown.

Appreciate your time and thanks for a great system and integration

map JSON aliases

i'd like to develop an interface to allow us to map new keys for the json responses we get from contentful.

for example, given this json:

# content-type: post
{
  "headline": "This is the title to my article",
  "byline": "with a really cool byline"
}

I'd like to be able to map the data so that in my views i can reference contentful.posts.title and contentful.posts.description - by doing something like:

contentful = require 'roots-contentful'

module.exports =
  mapping:
    posts:
      headline: 'title'
      byline: 'description'

  extensions: [
    contentful(map: mapping)
  ]

strange issues with markdown

In the first screenshot, the markdown from contentful is coming in quite strange. There's an <li> for the first item, then <li><p>, the ol is completely borked as well. The same exact contentful markdown works fine through middleman_contentful (see other screenshot).

- for post in contentful.snuggle
          article.test
            date= post.date
            h1= post.title
            if post.heroImage
              img(src=asset(post.heroImage, {w: 1100, h: 1100}))
            != markdown(post.body)

screenshot 2015-04-27 08 27 56

screenshot 2015-04-27 08 30 59

Investigate possible performance gains of batching of requests for entries

Intro

We use contentful.js to receive data from the official Contentful Delivery API.

A call to contentful.createClient() returns an instance of an authenticated client, with methods for consuming various data from an individual Contentful Space.

One such method, client.entries(), is used in roots-contentful.

How we fetch entries

First, we read all your defined content_types and their IDs in app.coffee, then we call client.entries({ content_type: <id> }) for each Content Type in parallel, which concurrently fetches all the entries for each individual content type. Due to the nature of this concurrency, the whole process will only take as long as the longest-running client.entries() call.

How we can improve fetching of entries

It's possible to batch all of this into one request using the Contentful Delivery API's search feature (notably, the inclusion flag). By aggregating all content types beforehand, we can then combine them into one HTTP request:

client.entries({ 'content_type[in]': '<content_type_1_id>,<content_type_2_id>' })

Then we can transform the response to split out the entries according to their content type so that we're back to multiple content_types, each with their own set of unique entries.

Potential gains

As of this moment, it is unclear to me if there might be any performance gains by doing this. Since the current requests are small in size and run concurrently, it might be the case that one giant HTTP request might take much longer to complete than several smaller ones that run in parallel. However, there is for sure some HTTP overhead associated with having multiple HTTP requests, so this functionality might be extremely useful for larger projects that consume a lot of Contentful data and are nearing the threshold for API request limits or just want to reduce excessive bandwidth consumption.

Therefore, if this theory proves to be of any benefit, it makes sense to offer a configuration option that alternates between the two request strategies (and choose a sensible default out of the two). This will convolute a bit of internal code and very likely look messy, but it might be well worth the effort.

This only applies to incremental compiles

Bare in mind that as of yesterday (Jan 27th 2016), we finally added the final test for some configurable logic that caches locals in the case of a second compile, for a significant performance gain (up to 5x!) - so this enhancement will only apply to the very first compile in a roots watch, as well as any compiles triggered by roots compile. (as long as you don't set cache to false in the options set for roots-contentful in your project's app.coffee)

Cannot read property 'length' of undefined

For some reason, when Roots gets to the for loop in the jade example in the readme, I get the title error.

I'm using the sample code provided in the examples, as well as a slightly modified app.coffee because slugify was also ending up being not defined.

h1 Hello World
ul
  - for post in contentful.posts
    li
      h2= post.title
      p= markdown(post.body)

My app.coffee:

axis            = require 'axis'
rupture         = require 'rupture'
typographic     = require 'typographic'
contentful      = require 'roots-contentful'
dynamic_content = require 'dynamic-content'
autoprefixer    = require 'autoprefixer-stylus'
js_pipeline     = require 'js-pipeline'
css_pipeline    = require 'css-pipeline'
string          = require 'string'

module.exports =
    ignores: ['readme.md', 'gulpfile.js', '**/layout.*', '**/_*', '.gitignore', 'ship.*conf']

    extensions: [
        js_pipeline(files: 'assets/js/*.coffee','assets/js/*.ls'),
        css_pipeline(files: 'assets/css/dev/*.styl')
        dynamic_content()
        contentful
            access_token: 'my_token'
            space_id: 'my_id'
            content_types:
                blog_posts:
                    id: 'my_id'
                    name: 'posts'
                    template: 'views/_post.jade'
                    path: (e) -> "blogging/#{e.category}/#{string(e.title).slugify().s}"
    ]

    stylus:
        use: [axis(), rupture(), typographic(), autoprefixer()]
        sourcemap: true

    'coffee-script':
        sourcemap: true

    jade:
        pretty: true

Is it just something really rudimentary I just missed on writing these files?

Transform function not working

I have the following transform function:

formatEvent = (event) ->
    event.date = 'hello'
    console.log event
    delete event.sys
    event

My contentful config looks like this:

    contentful
        access_token: 'XXXXX'
        space_id: 'zz4gw0sowa4j'
        content_types:
            member:
                id: '2JULZsVvqE88AASqOqwCsg'
            event:
                id: '4U01XhjOU0qiaiWUS0k4wk'
                transform: formatEvent
            document:
                id: '6p4N5fxOWkCWs4SOIKCOum'
            eventLocation:
                id: '7HRiLDEM8wMiMGyOyCewcm'

My function is not being executed, I can tell because nothing is logged to the console. The only difference I can make out between this and the unit tests is that here I use an object instead of an array.
My views do have access to the content. Everything is working fine except the transform.

Any ideas?

Having issues with sort

I'm having some issues trying to sort content type with a date field (only Date), wondering if I can get some help.

app.coffee:

extensions: [
    contentful
      access_token: ''
      space_id: ''
      content_types:
        posts:
          id: ''
          filters: { }
          sort: (a,b) ->
            parseInt( moment(b.date, 'YYYY-MM-DD').unix() ) - parseInt( moment(a.date, 'YYYY-MM-DD').unix() )

The Date field should return something along the lines of 2015-11-05

I already have moment required; it's working elsewhere in the views, just not working here. The 'posts' content type just end up following some sort of random order. Would appreciate some help here!

Access linked references (that aren't just images)

What's the best method to iterate over referenced content objects? This is a single page marketing lander, so it's not a "blogging" use case.

I have a "client testimonial" section (id: clientSection) that has the following:

  • Headline (single item)
  • Body (single item)
  • Reference field (multi-item) that points to a "testimonial" content type that has a couple of text fields and an image reference

I'm doing contentful.clientSection[0].headline and contentful.clientSection[0].body for the first two, and I tried doing:

each testimonial in contentful.clientSection[0].testimonials
        p= testimonial.testimonial
        p= testimonial.attribution

but because the testimonials are references, not directly in the clientSection object, it's not working.

Is there a way to follow reference links?

test case: roots compile

there should be a test case that actually completes a roots compile since thats the context we use it in

_path undefined in entry

When i try to compile for a single entry, the _path variable is undefined so my compiling fails.

I am still a beginner in nodejs so... How can i use the _url as as the _path for a single entry ?

Thank you.

port to es2016

bc we've been looking to port a coffeescript project. partially for future proofing, partially as a learning experience.

Image resizing

Just curious if there is a way to make use of contentful's api to resize images on delivery, I'd like to be able to query thumbnails on the index page, as well as the full size images on the individual post pages. Not sure if there is a configuration setting in app.coffee that can be used, but could be an interesting feature to add in the future, if possible.

asset helper throws "undefined is not a function" TypeError

The asset helper is throwing a "undefined is not a function" TypeError for me

for person in contentful.team_member
  img(src="#{person.profile.fields.file.url}" height='150' width='150') // this works fine
  img(src="#{asset(person.profile, {w: 150, h: 150})}" height='150' width='150') // this throws "undefined is not a function" TypeError
  img(src!= asset(person.profile, {w: 100, h: 100})) // this throws "undefined is not a function" TypeError

Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.