Keystonejs 4.0 File system storage adapter image preview - keystonejs

How do I get an image preview for a Types.File field in the admin UI.
It says "The FS adapter supports all the default Keystone file schema fields. It also additionally supports and enables the filename path (required)." However when I try (doc):
format: function(item, file){
return '<img src="/files/'+file.filename+'" style="max-width: 300px">'
}
Nothing appears in the UI

The format function hasn't been working for a while as far as I can tell from the Keystone GitHub. I don't know if the function exists in Keystone 4.0. Reference here.

You could fork the current beta and patch the function yourself if you need this immediately.
You can find it at https://github.com/keystonejs/keystone/blob/v4.0.0-beta.5/fields/types/file/FileType.js#L81
Doesn't seem right to me, though. I hope they will fix it before releasing 4.0, along with the missing File Array type.

Image previews are now possible in the latest master branch of keystone (see https://github.com/keystonejs/keystone/pull/4509). At the moment you need to depend on the git version of keystone, so put this in your package.json and run npm install:
"keystone": "https://github.com/keystonejs/keystone.git"
In your model, specify thumb: true on the image field in question. You also need the url property in the schema. For example:
const storage = new keystone.Storage({
adapter: keystone.Storage.Adapters.FS,
fs: {
path: keystone.expandPath('./uploads/images'),
publicPath: '/images/'
},
schema: {
url: true,
}
})
ImageUpload.add({
name: { type: Types.Key, index: true },
image: {
type: Types.File,
storage: myStorage,
thumb: true
},
createdTimeStamp: { type: String }
});
The admin UI should now show a small preview of the image and a link to it.

Related

How do I get Watch Mode with Sanity.io and Gatsby to refresh content when referenced documents are edited in the CMS / Studio?

I'm using Sanity.io, GatsbyJS 3.x
Watch mode works great when you update content in the CMS, except for when the content you edit is part of a referenced schema of type 'document'.
Put another way, changes made to a document referenced by another document will not re-render the page despite having watch mode on and configured properly.
For example, here is a snippet from my Page schema.
...
{
name: "content",
type: "array",
title: "Page Sections",
description: "Add, edit, and reorder sections",
of: [
{
type: 'reference',
to: [
{ type: 'nav' },
{ type: 'section' },
{ type: 'footer' }
]
}
],
},
...
The above schema references a
nav schema
section schema
footer schema
Each of these are type 'document'.
See the example below.
export default {
type: 'document',
name: 'section',
title: 'Page Sections',
fields: [
{
name: 'meta',
title: 'Section Meta Data',
type: 'meta'
},
...
I want to reference a document, rather than an object, because I need to use the content created based on these schemas to be re-used in throughout the application.
Finally, I've configured the source plugin correctly for watch mode.
Gatsby Config is set properly
{
resolve: `gatsby-source-sanity`,
options: {
projectId: `asdfasdf`,
dataset: `template`,
watchMode: true,
overlayDrafts: true,
token: process.env.MY_SANITY_TOKEN,
},
},
In the CMS / Studio, when you edit one of the fields, you can see Gatsby re-compile in dev mode from the terminal. However, the page does not auto reload and display the changes made to the referenced document.
I've tried reloading the page with the reload button and via hard refresh, the changes do not render.
The only way to render the changes is to go back to the CMS and edit a field on the main “Page” document. Then it refreshes immediately.
Am I doing something wrong? Is this expected behavior? Is there a way to get this to work?
For those that run across this issue, I was able to answer my own question. I hope this saves you the day's it took me to find a solution.
Solution TLDR
You need to explicitly query the referenced document in order for watch mode to work properly.
Details with Examples
Summary
The gatsby-source-sanity plugin provides convenience queries that start with _raw for array types. When you use the _raw query in your GraphQL query, it will not trigger watch mode to reload the data. You need to explicitly query the referenced document in order for watch mode to work properly. This may have to do with how the plugin sets up listeners and I don't know if this is a bug or a feature.
Example
My Page Document has the following schema
{
name: "content",
type: "array",
title: "Page Sections",
description: "Add, edit, and reorder sections",
of: [
{
type: "reference",
to: [
{ type: "nav" },
{ type: 'section' },
],
},
],
},
The section is a reference to a section document.
{ type: 'section' }
The reason I'm not using an object is because I want the page sections to be re-usable on multiple pages.
Assuming you have watch mode enabled properly in your gatsby-config.js file, watch mode, like so...
// gatsby-config.js
{
resolve: `gatsby-source-sanity`,
options: {
projectId: `asdf123sg`,
dataset: `datasetname`,
watchMode: true,
overlayDrafts: true,
token: process.env.SANITY_TOKEN,
},
},
Then you should see the following behavior:
listen for document/content updates
re-run queries, update the data, hot-reload the page
You'll see the following scroll in your terminal window.
success Re-building development bundle - 1.371s
success building schema - 0.420s
success createPages - 0.020s
info Total nodes: 64, SitePage nodes: 9 (use --verbose for breakdown)
success Checking for changed pages - 0.001s
success update schema - 0.081s
success onPreExtractQueries - 0.006s
success extract queries from components - 0.223s
success write out requires - 0.002s
success run page queries - 0.010s - 1/1 99.82/s
This works great if you are querying the main document or any referenced objects. However, if you are querying any references to another document then there is one gotcha you need to be aware of.
The Gotcha
When you use the _raw query in your GraphQL query, it will not trigger watch mode to reload the data. You need to explicitly query the referenced document in order for watch mode to work properly.
Example: This Query will NOT work
export const PageQuery = graphql`
fragment PageInfo on SanityPage {
_id
_key
_updatedAt
_rawContent(resolveReferences: {maxDepth: 10})
}
`
Example: This query WILL Work
export const PageQuery = graphql`
fragment PageInfo on SanityPage {
_id
_key
_updatedAt
_rawContent(resolveReferences: {maxDepth: 10})
content {
... on SanitySection {
id
}
}
}
`
This additional query is the key
Here is where I am explicitly querying the document that is being referenced in the 'content' array.
content {
... on SanitySection {
id
}
}
You don't actually need to use the data that results from that query, you simply need to include this in your query.
My guess is that this informs the gatsby-source-sanity plugin to set up a listener, whereas the _rawContent fragment does not.
Not sure if this is a feature, bug, or just expected behavior. At the time of writing the versions were as follows.
"gatsby": "3.5.1",
"gatsby-source-sanity": "^7.0.0",

How to configure Create-react-app less module with customize-cra(2.x)?

I used create-react-app(typescripts) to build a project, and added antd#3.26.13 with customize-cra as the website I was following told me.
I would like use the module.css, and I want to use module.less, like css, but encountered some error messages:
./src/layout/basic.module.less (./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-8-1!./node_modules/postcss-loader/src??postcss!./node_modules/less-loader/dist/cjs.js??ref--6-oneOf-8-3!./src/layout/basic.module.less)
ValidationError: Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'localIdentName'. These properties are valid:
object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }
My code follows:
const {
override,
addWebpackAlias,
fixBabelImports,
addLessLoader,
addDecoratorsLegacy
} = require('customize-cra');
module.exports = override(
addWebpackAlias({
"#":require('path').resolve(__dirname,"src")
}),
fixBabelImports('import',{
libraryName:'antd',
libraryDirectory:'es',
style:true
}),
addLessLoader({
javascriptEnabled:true,
modifyVars:{'#primary-color':'#1DA57A'},
}),
addDecoratorsLegacy()
);
The current version of customize-cra isn't compatible with the latest version of create-react-app, to be precise with css-loader. Try to install customize-cra#next to get alpha version. They fixed that issue there.

Image require() in nuxt with hot reload by HRM webpack

I use the dynamic source for vue-webpack images in nuxt :src="require('path/to/image' + dynamic.variable)" in my project navbar. If the users substitute their image through a form which refetches their information and deletes their previous image I get a webpack error module (img) not found (it does not find the new one): is there a way to solve this, like wait for webpack HRM to finish?
I tried setting up a setTimeout() of one second before user re-fetch and it works, but I don't like a random waiting, I'd use a promise or a sync dynamic, the point is webpack hot reload is not controlled by my functions.. I also tried with setting the dynamic path as a computed: but it doesn't fix.
My image tag:
<img v-if="this.$auth.user.image" class="userlogo m-2 rounded-circle" :src="require('#assets/images/users/' + this.$auth.user.image)" alt="usrimg">
My Useredit page methods:
...
methods: {
userEdit() {
//uploads the image
if (this.formImageFilename.name) {
let formImageData = new FormData()
formImageData.append('file', this.formImageFilename)
axios.post('/db/userimage', formImageData, { headers: { 'Content-Type': 'multipart/form-data' } })
// once it has uploaded the new image, it deletes the old one
.then(res=>{this.deleteOldImage()})
.catch(err=>{console.log(err)})
}else{
this.userUpdate() //if no new image has to be inserted, it proceeds to update the user information
}
},
deleteOldImage(){
if(this.$auth.user.image){axios.delete('/db/userimage', {data: {delimage: this.$auth.user.image}} )}
console.log(this.$auth.user.image + ' deleted')
this.userUpdate() // it has deleted the old image so it proceeds to update the user information
},
userUpdate(){
axios.put(
'/db/user', {
id: this.id,
name: this.formName,
surname: this.formSurname,
email: this.formEmail,
password: this.formPassword,
image: this.formImageFilename.name,
})
.then(() => { console.log('User updated'); this.userReload()}) // reloads the updated user information
.catch(err => {console.log(err)} )
},
userReload(){
console.log('User reloading..')
this.$auth.fetchUser()
.then(() => { console.log('User reloaded')})
.catch(err => {console.log(err)} )
},
}
...
the problem happens after "console.log('User reloading..')" and before "console.log('User reloaded');", it is not related to the file upload nor the server response. I broke a single function in many little ones just to check the function progression and its asynchronous dynamics but the only one that is not manageable is the webpack hot reload :/
I'd like the users to upload their images and see their logo in the Navbar appear updated after submitting the form.
First of all, as somebody told you in the comments, webpack hmr shouldn't be used for production.
In Nuxt, everything that you reference from the assets folder will be optimized and bundled into the project package. So the ideal use case for this folder is all assets that can be packaged and optimized, and most likely won't change like fonts, css, background images, icons, etc.
Then, require is called only once by webpack when it is either building the site for local development or building the site for generating a production package. The problem in your case is that you delete the original file while you're in development and webpack tries to read it and fails.
In the case of these images that the user uploads, I think you should use the static folder instead and instead of using require you'll have to change the :src with
:src="'/images/users/' + this.$auth.user.image"
Let me know if this helps.
Okay, I probably solved it.
HMR: you are of course right. Thank you for pointing out, I am sorry, I am a beginner and I try to understand stuff along the way.
Aldarund, thank you, your idea of not changing the path and cache it client side.. I am too noob to understand how I could implement it ( :) ) but it gave me a good hint: the solution was to keep the image name as the user id + the '.png' extension and to manage the image with jimp so that the image name, extension and file type are always the same, and with or without webpack compiling the new path, I always have the correct require().
Jair, thank you for the help, I didn't follow that road, but I will keep it as a second chance if my way creates errors. Just to be specific: the error comes when it does not find -and asks for the name of- the NEW image, not the OLD one, as I wrote in my question: it happens because the fetchUser() functions reloads the user information including the new image name.
Do you guys see any future problems in my methodology?
Really thank you for your answers. I am learning alone and it's great to receive support.

KeystoneJS not auto-compiling LESS files to CSS

In KeystoneJS Docs, it mentions that upon requesting style.min.css from the server, this will compile style.less and generate style.min.css in production.
If you want Keystone to automatically compile .less files into .css files, set this value to the same path as the static option.
I have followed this and I have added some LESS code in style.less but my changes are not reflected in style.min.css, in other words, the file style.min.css is not regenerated, I am not sure if it's a bug or am I missing something.
For ease of example to reproduce, I just added this code in styles/site.less:
html {
background: black !important;
}
I expect the whole background to be black, but it's not working.
Keystone config:
let keystone = require('keystone');
keystone = keystone.init({
'mongo': process.env.MONGO_URI
'name': 'CMS',
'brand': 'CMS',
'less': 'public',
'static': 'public',
'favicon': 'public/favicon.ico',
'views': 'templates/views',
'view engine': 'pug',
'auto update': true,
'session': true,
'auth': true,
'user model': 'User',
});
keystone.import('models');
keystone.set('locals', {
_: require('lodash'),
env: keystone.get('env'),
utils: keystone.utils,
editable: keystone.content.editable,
});
keystone.set('signin logo', '../images/logo.png');
// Load your project's Routes
keystone.set('routes', require('./routes'));
// Configure the navigation bar in Keystone's Admin UI
keystone.set('nav', {
users: 'users',
intents: 'intents',
Items: ['categories', 'sub-categories', 'products'],
messages: 'messages',
});
keystone.start();
this is what i did and it works well
update generator to latest version
create new website with yo keystone
start website by using node keystone
change public/styles/site.less with changes what you did (black background)
refresh the page

Sending Additional Info with a Typeahead Query

I'm using Bootstrap 3.2.0 and jQuery 2.1.1.
I've implemented typeahead search suggestions and now wanted to go a step further. I am using the remote method and was hoping to send more than just the query to the PHP that will run a SQL script:
$(function() {
var productEngine = new Bloodhound({
datumTokenizer: function (datum) { return Bloodhound.tokenizers.whitespace(datum.value); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 10,
remote: { url: 'php/product_suggest.php?storeid='.concat(localStorage.getItem('store_id'), '&query=%QUERY') }
});
productEngine.initialize();
$('input#product_suggest').typeahead(null, {
displayKey: 'value',
source: productEngine.ttAdapter()
});
});
I've also tried building the URL and saving as a variable. Then attempting to call that variable in the remote.
remote: { url: builtURL }
As I don't particularly know how this url is being parsed and used or how the %QUERY is being added, I'm assuming my issue lies in my lack of knowledge around how typeahead is processing this.
I thought this would be a fairly common request, but Google has let me down. Anyone have any suggestions or can point me towards examples of sending additional info with a typeahead query?