Closed JedWatson closed 8 years ago
Been evaluating some CMS platforms and see the management of 'static' content (Home, About etc.) as a key feature. Whats the current status of this?
It's been on hold for a bit (we've been flat out) but should be picked up again soon. There's a project we're working on that really wants it.
I'm hesitant to make promises about timing on new features but this is close to the top of the list, focus-wise. I agree it's a key feature :)
What kind of timeline are you on?
Looking at delivery in May so would need to be able to allow the client content entry by mid April at the latest.
Have you guys got a roadmap?
Drafted one last night, actually. Will post it up here soon and link to the issues we've got planned for upcoming releases.
For content management, I don't think April would be a push at all, if you guys are adopting Keystone I'd be happy to work with you to make sure it all works out.
In other news, just realised your company has an office in Sydney about four blocks from mine... small world :)
This is great news Jed. I was just talking with others the other day about Keystone and they asked how it handled "static" content.
The other thing they asked was how it handled dynamic content inside a static page. Basically, on the home screen you have some introductory text, then maybe some images, and a section that reports back the recent news items (say top 3 that exist). Something to think about.
Hi Jed
Gone with another CMS for now purely for maturity and an established feature-set. Definitely have you Keystone earmarked for the future however - definitely think this project is heading in the right direction.
Would love the opportunity to hear your plans in the future regarding this feature, and can stick in some 2 cents (and contribute, given the constraints of parenthood! :smile: )
Hello, any updates on this issue? It is really important feature. At least provide tutorial how to create it appropriate way for keystone
I'm looking into working on this issue. I see you already have some of the groundwork under lib/content
but not sure how to dive in.
@alexzaporozhets There needs to be formal documentation about this, probably when it is more complete however this is what I have discovered.
You can add this to your layout/base.jade or however suits you:
if user && user.canAccessKeystone
script(src='/keystone/js/content/editor.js')
that script will scan the page for all elements with a data-ks-editable={"type": "list"}
and parse the json/use the data to display a button that's action depends on what the type of ks-editable is.
here's a schema of what is currently expected in the JSON:
{
type: 'list', // can be list, content or error (not sure what error is?)
path: '', // (required for type.list) keystone prefixed path to the list page
plural: '', // (required for type.list) pluralized list name
singular: '', // (required for type.list) singular list name
id: '' // (optional for type.list) specific list item to link to
}
this line is the one that particularly pertains to this issue. It is subjective what this case exactly should do but I think it should replace the data-ks-editable element with a live content editor (based on the content field type just like the admin UI). I think this is started here
You can study this folder to figure more out but the content api in its current state roughly works like such:
var keystone = require('keystone')
, Types = keystone.content.Types
, Home = keystone.content.Page('Home');
Home.add({
title: {type: Types.Text, required: true},
about: {type: Types.Text, required: true}
})
// FETCH
// =====
keystone.content.store('home', {title: 'Welcome', about: 'such content' }, function(err) {
if (err) console.log(err);
});
keystone.content.fetch('Home', function(err, content) {
console.log(content) // [Object Object]
});
Some things I'm still trying to figure out:
Sorry if any of this is unclear. I wrote it all mostly as a reference for myself as I am going to start hacking on this issue tonight or tomorrow night
Any updates here? :–) I'm happy to help.
Has this been put on hold for a while or will it be implemented in the near future?
Update on this please!
About the Admin UI part, it seems better to wait for the React rewrite. So we don't code it twice in a short period of time and we can take advantage of the React's (potential) UI flexibility too.
@ignlg is correct, to build a really cool content management interface in Keystone we want the React UI up and running first. I probably should have listed this issue in #503 as one of the things that will unblock.
I've also been uneasy about the duplication of field types for content, especially since we'll be implementing a proper API for plugging in custom field types, so getting that in place is also important.
We're also going to end up with the ability to customize the Admin UI, which this needs to fit in with.
Sorry to anyone who's been waiting on this, but we really want to make sure we get it right, and have the right pieces in place first.
Better to take your time and get it right than to rush something out the door and piss everyone off.
Any update about the timing for the static content management feature? Not that I want to be impatiantly, just checking in for an update since this thread has been inactive for 2,5 months!
I see the update being mentioned in https://github.com/keystonejs/keystone/issues/322 , However there's no estimated timing their aswell.
it's been open for over a year now, I would appreciate any timing update on that.
So this isn't being targeted for a release in 0.3?
Hm that's too bad. Still checking up on this issue now and then for updates. I'm eager to help if there's something I can do.
:+1:
TBH I don't really see the need for this (at least not for static page generation). You can achieve "static" pages, by creating a Pages collection and creating a view which resolves a url parameter to determine what "page" needs to be shown. See
All you need to do is create a Page document, fill out the slug
field. Then if you surf to //<host>/content/<slug>
it'll show whatever you filled out content wise. Obviously if you need multiple page types, i.e. with various fields you'd need multiple collections. But that would be the same with the above approach.
@creynders, as you can see in the original comment, the idea is to be able to manage (maybe completely) different pages from the same "collection". For now, you either create a different collection for each different layout or you limit yourself (and the people who'll be working with the CMS later on) to generic fields (like having a couple of WYSIWYG
editors to compose the whole page.)
On the other hand, I can't help it but feel like this feature would make a huge difference when it comes to internationalization.
@acontreras89 ah yes, didn't read it thoroughly enough I realise! Ok, nvm my other post!
I wrote about an approach that accomplishes this using list inheritance a little while ago. The approach is a bit different than what @JedWatson proposed. But you can read about it @ http://rob.codes/creating-a-page-router-in-keystonejs/
If enough people find this useful I should be able to pull this functionality out into its own library or integrate it with keystone.
thanks for sharing your experience, @lojack Your approach is somewhat similar to what we were talking
create a different collection for each different layout
Of course we can work around the lack of centralized static content management, but having this functionality would likely make things easier :grin:
My way allows pages to be created within the same collection? I believe it works the same way as @creynders mentioned except instead of using different collections for different templates I just load different partials within the same handlebars template from a select dropdown in the Page model.
I just created a collection which allows the user to change the slugs.
Then if I go to through that collection before I do other routes which might match the same pattern checking against the requested URL for a match. If I find a match I parse the item, if I don't i continue down the other routes.
Each 'page' in that collection then has a Select dropdown for the template they want to use for that page which is populated on startup from a template folders contents. It then shows the correct fields for that template using 'dependsOn:'
The handlebars template then has a switch statement in it which loads a different partials depending on template options and other page settings.
Works fine IMO. I'd like a more efficient way of checking perhaps but tbh its going to be just fine unless you've got hundreds of custom pages.
If anyone is looking for an example of more detail please do get in touch.
Hey there !
Thanks for sharing @Mentioum and for offering a more detailed example !
We're looking into this solution as well as discussed here : https://groups.google.com/forum/#!topic/keystonejs/yIqUNaD_H30 since we just figured out keystone doens't offer a single representation for single pages instead of a collection.
Would you mind putting together a Gist example or screen captures to illustrate how you achieve this ? Mainly :
I'll start digging this meanwhile, but that would be awesome !
Based on @Mentioum solution, here is how we are handleling this at the moment : Disclaimer : this solution is not advanced/better, but the code example might help some people.
1 - We've duplicated Post.js model to create a Page.js model
2- created a dropdown with templates
template: { type: Types.Select, options: 'page, about, team, contact, portfolio', default: 'page'},
3- display someField if the selected template is 'about'
someField: { type: String, dependsOn: { template: 'about' } },
and it works :)
Now, I'd like to have an OR operator in dependsOn: { template: 'about' }
, so that I can do something like dependsOn: { template: 'about' OR 'legal' }
or dependsOn: { template: 'about || legal' }
Any idea on how to make this happen ?
Thanks !
Full code example for Page.js here : https://gist.github.com/charleslouis/ff5cc15ce7d2f3aee59d
What about allowing functions?
dependsOn: { template: checkTemplate }
That way it's easy to be extremely flexible:
function checkTemplate(template) {
return (template === 'about' || template === 'legal');
}
It's the easiest way to allow any behaviour, complex or not.
Yes indeed @ignlg !! Thank you very much !
Apologies for disappearing, been busy with work.
@charleslouis and @ignlg The dependsOn object takes an array or a list of comma separated variables (String) as well (I believe) as just a single value so I believe so you can have multiple template values show the same fields if you choose to. I like the idea of being able to use a function a lot though @ignlg
I actually called my model 'SpecialPages' and ended up storing all sorts of stuff in there:
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* Special Page Model
* ==================
*/
var Pages = [
'About',
'Home',
'Facilities',
'Gallery',
'Team',
'FAQ',
'Contact',
'Book a Tour',
'Help'
];
var SpecialPage = new keystone.List('SpecialPage', {
map: {name: 'title'},
plural: 'SpecialPages'
});
SpecialPage.add({
active: {type: Types.Boolean },
title: { type: String, required: true, intial: true},
page: {type: Types.Select, options: Pages, note: 'Choose which page this custom data is for. Make sure there is only one SpecialPage per SpecialPage type Active.' },
home: {
testimonies: {type: Types.Relationship, ref: 'Testimony', many: true, dependsOn: {page:'Home'}},
segments: {type: Types.Relationship, ref: 'Segment', many: true, dependsOn: {page: 'Home'}},
carouselSegments: {type: Types.Relationship, ref: 'Segment', many: true, dependsOn: {page: 'Home'}},
videoUrl: {type: Types.Url, note: 'This will be the video URL for the home page.', dependsOn: {page: 'Home'}},
videoTitle: {type: Types.Text, note: 'This will be the title under the video at the top of the page.', dependsOn: {page: 'Home'}},
videoText: {type: Types.Text, note: 'This will be the small text under the video at the top of the page.', dependsOn: {page: 'Home'}},
videoImage: {type: Types.Relationship, ref: 'Image', note: 'This image will show on mobile intead of the video.', dependsOn: {page: 'Home'}}
},
facilities:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Facilities'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Facilities'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Facilities'} }
},
team:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Team'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Team'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Team'} }
},
help:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Help'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Help'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Help'}},
faq: {
thumbnailTitle: {type: Types.Text, note: 'This is the title which will appear on the FAQ thumbnail Text ', dependsOn:{page: 'Help'}},
thumbnailText: {type: Types.Text, note: 'This is the text which will appear on the FAQ thumbnail Text ', dependsOn:{page: 'Help'}},
thumbnailImage: {type: Types.Relationship, ref: 'Image', note: 'This image will be used for the thumnail linking to the FAQ page.', dependsOn:{page: 'Help'}},
}
},
gallery: {
introduction: {type: Types.Html, wysiwyg: true, dependsOn: {page: 'Gallery'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Gallery'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Gallery'}},
},
faq: {
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'FAQ'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'FAQ'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'FAQ'}},
},
about:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'About'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'About'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'About'}},
},
contact:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Contact'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Contact'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Contact'}},
},
tour:{
introduction:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Book a Tour'}},
successText:{type: Types.Html, wysiwyg: true, dependsOn: {page: 'Book a Tour'}},
bannerImage: {type: Types.Relationship, ref: 'Image', note: 'The image which appears at the top of the page.', dependsOn: {page: 'Book a Tour'}},
bannerText: {type: Types.Text, note: 'This text will appear overlaying the banner image.', dependsOn: {page: 'Book a Tour'}},
},
meta: {type: Types.Relationship, ref:'Meta'},
});
SpecialPage.defaultColumns = 'title, page, active';
SpecialPage.register();
view.on('init', function(next) {
keystone.list('SpecialPage').model.findOne()
.where('page', 'Home')
.where('active', true)
.populate('meta')
.deepPopulate(deepPaths, {
populate:{
'home.testimonies': {
options:{
sort: 'sortOrder'
}
},
'home.segments': {
options:{
sort: 'sortOrder'
}
}
}
})
.exec(function(err, page){
if(err){
console.log(err);
return next(err);
} else {
locals.data.page = page;
next(err);
}
});
});
The reason I ended up allowing people to make multiple Special Pages rather than just having one to cover all of these pages around the site is that it allows the end user to create several 'Home' pages and then switch between them with the 'active' variable in the model. I'd recommend having a pre-save hook checking to make sure any of the same type aren't active to ensure you only have 1 home page active at a time.
We recently updated dependsOn to accept mongoose style expression operators. I have not updated the docs yet. For more info you can checkout the README for expression-match. It has a Keystone example collection.
Thanks @Mentioum and @snowkeeper !
Ah good to know @snowkeeper !
We're closing all feature requests to keep the issue tracker unpolluted. From now on submit feature requests on productpains.com!
It would be great to be able to define a data structure for storing content, where pages would be a single representation instead of a collection.
The structure would be defined in a similar syntax to models:
… then there would be an easy way to retrieve content for a particular page, something like this:
Page content would be stored on a page-per-document basis, in a collection called
app_content
.