Trouble configuring CKEditor downcast for custom element - ckeditor

I have a custom Media Library which I need to integrate with CKEditor 5. I've managed to write a plugin for adding images and another for adding download links for documents, but I can't get the downcast conversion for the download links to work properly.
I am currently able to insert the following into the content of the CKEditor window:
<a class="downloadLink" data-file-id="3" download="pretty-file-name-for-download.pdf" href="actual_file_name.pdf">Download link text</a>
and when I save my content this gets written to the database - so far so good.
When I reload the content into the editor, it comes back formatted differently:
<a href="actual_file_name.pdf">
<a class="downloadLink" data-file-id="3" download="pretty-file-name-for-download.pdf" href="actual_file_name.pdf">Download link text</a>
</a>
There is now an additional anchor tag wrapping the original anchor tag.
My downcast and upcast converters look like this:
conversion.for('downcast').elementToElement( {
model: {
name: 'downloadLink',
attributes: ['fileId', 'fileUrl', 'fileDownloadName', 'title']
},
view: (modelElement, conversionApi) => {
const viewWriter = conversionApi.writer;
return viewWriter.createContainerElement(
'a',
{
'data-file-id': modelElement.getAttribute('fileId'),
'download': modelElement.getAttribute('fileDownloadName'),
'href': modelElement.getAttribute('fileUrl'),
'class': 'downloadLink'
},
viewWriter.createText(modelElement.getAttribute('title'))
);
}
} );
conversion.for('upcast').elementToElement( {
view: {
name: 'a',
classes: 'downloadLink'
},
model: (viewElement, conversionApi) => {
const modelWriter = conversionApi.writer;
return modelWriter.createElement(
'downloadLink',
{
fileId: viewElement.getAttribute('data-file-id'),
fileUrl: viewElement.getAttribute('href'),
fileDownloadName: viewElement.getAttribute('download'),
title: viewElement.getChild(0).data,
}
);
}
} );
If anyone has any suggestions about what I'm doing wrong I'd appreciate the help!

Related

React-Admin <TranslateInput> how to get the translated label?

following the docs [here][1] I created some en.js and fr.js file to translate the fields of my react-admin app
examples:
const fr = {
resources: {
categories: {
name: 'Catégorie |||| Catégories',
fields: {
code: 'N°',
trigram: 'Trigramme',
description: {
en: 'Description (Anglais)',
fr: 'Description (Français)',
cn: 'Description (Chinois)',
},
},
},
},
};
I have a similar file for English and Chinese with the description as follow
description: {
en: 'Description (English)',
fr: 'Description (French)',
cn: 'Description (Chinese)',
},
and
description: {
en: '类别的描述 (英文)',
fr: '类别的描述 (法文)',
cn: '类别的描述 (中文)',
},
this seems to work fine and I can use the TranslatableInputs as well
<TranslatableInputs locales={['en', 'fr', 'cn']}>
<TextInput source='description' />
</TranslatableInputs>
But, the label in the tab view doesn't seem to be applied (while in the list mode they are shown properly) using description.en for example so the link with the .js files is working fine I guess.
But as shown in the documentation with the Music example the label doesn't change when we switch tab (actually the same problem is show in the documentation's gif
the "name" label doesn't change to "nom"
[![enter image description here][2]][2]
any help to solve that? thanks a lot and thanks a lot to RA team for their wonderful work as well
Nico
[1]: https://marmelab.com/react-admin/TranslatableInputs.html
[2]: https://i.stack.imgur.com/16fWf.gif
That's a great question... And a current limitation of the TranslatableInput component.
The input label translation uses the global user locale, not the one of the selected tab. That would be a great feature to add to react-admin, and I invite you to open a feature request in the react-admin repository.
In the meantime, you should write your own TranslatableInput based on react-admin's, and wrap each tab content with <I18NContext.Provider> in which you force a different locale.
Something like (not tested):
export const TranslatableInputs = (props) => {
const {
className,
defaultLocale,
locales,
groupKey = '',
selector = <TranslatableInputsTabs groupKey={groupKey} />,
children,
variant,
margin,
} = props;
const context = useTranslatable({ defaultLocale, locales });
const i18nProvider = useI18nProvider();
return (
<Root className={className}>
<TranslatableContextProvider value={context}>
{selector}
{locales.map(locale => (
<I18nContext.Provider value={{...i18nProvider, getLocale: () => locale }}>
<TranslatableInputsTabContent
key={locale}
locale={locale}
groupKey={groupKey}
variant={variant}
margin={margin}
>
{children}
</TranslatableInputsTabContent>
</I18nContext.Provider>
))}
</TranslatableContextProvider>
</Root>
);
};

GraphQL StaticQuery of an image in Gatsby broken

In my copy of Gatsby Netlify CMS starter kit I've made a reusable header.js component (components/header.js) which shows my site logo and nav. Problem is my logo image won't show up, with error TypeError: Cannot read property 'childImageSharp' of null which I interpret to mean I'm querying the image path incorrectly.
I have my logo.gif image in the same components folder, and I also added it to content/assets. My static query, which I gather is specifically for querying in components, looks like this:
<StaticQuery
query={graphql`
query LogoQuery {
logo: file(absolutePath: { regex: "logo.gif" }) {
childImageSharp {
fixed(width: 500, height: 350) {
...GatsbyImageSharpFixed
}
}
}
}
`}
render={data => (
....
<Img fixed={data.logo.childImageSharp.fixed} alt="Home" />
....
I also tried relativePath, to no avail:
query LogoQuery {
logo: file(relativePath: { eq: "assets/logo.gif" }) {
childImageSharp {
fixed(width: 500, height: 350) {
...GatsbyImageSharpFixed
}
}
}
}
`}
Guessing I want absolute path since header will be in post subfolders, doesn't say anything about the 2 options in the docs though. Regardless, neither seem to work. Any guidance greatly appreciated, thanks.
ha it turns out that this just doesn't work for gifs... only jpgs and pngs. Strange!

Change button labels in CKEditor 5 toolbar

I have a custom toolbar for CKEditor 5 with options like this:
customToolbar: ['insertTable', 'fileUpload', 'mediaEmbed']
Is there a way to customize the title for these features?
I saw a way to customize heading like this:
heading: {
options: [
{ model: 'paragraph', title: 'Body copy', class: 'ck-heading_paragraph' },
{ model: 'heading2', view: 'h2', title: 'Sub Header', class: 'ck-heading_heading2' }
]
}
But not sure how to do it with other features.
Buttons in CKEditor5 UI have the (observable) #label property. Changing it will be immediately reflected in DOM.
yourToolbar.items.get( 2 ).label = "Foo"
Posting this as it took me a sec to figure out how to get a hold of the toolbar object from #oleq post.
ClassicEditor
.create( document.querySelector('#editor'))
.then(editor => {
editor.ui.view.toolbar.items.get(0).label = 'YEAH BUDDY'
});

How to get 'Last Update Date' of a blog post in GATSBY.js

Hello I'm not a dev so may the question will be easy for you guys. I used the advance starter from gatsby site. The blog is working perfect but I need to provide the LAST UPDATED time under my title. Searched for some solutions but none of them worked. Could you Provide some help?
gatsby-node.js
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type !== 'MarkdownRemark') {
return;
}
const fileNode = getNode(node.parent);
createNodeField({
node,
name: 'modifiedTime',
value: fileNode.mtime
});
};
`````````````````````````
PostListing.jsx
class PostListing extends React.Component {
getPostList() {
const postList = [];
this.props.postEdges.forEach(postEdge => {
postList.push({
path: postEdge.node.fields.slug,
tags: postEdge.node.frontmatter.tags,
cover: postEdge.node.frontmatter.cover,
title: postEdge.node.frontmatter.title,
date: postEdge.node.fields.date,
excerpt: postEdge.node.excerpt,
timeToRead: postEdge.node.timeToRead,
modifiedTime:postEdge.node.modifiedTime
});
});
return postList;
}
render() {
const postList = this.getPostList();
return (
<div className='posts'>
{/* Your post list here. */
postList.map(post => (
<Fragment>
<div className='singlePost__date'>
<h4 style={{color:'white'}}> {post.modifiedTime}</h4>
</div>
<div className='singlePost__Title'>
<Link classname='singlePost' to={post.path} key={post.title}>
<h1 className='singlePost__title'>{post.title}</h1>
</Link>
</div>
</Fragment>
))}
</div>
);
}
}
export default PostListing;
I expect something like
TITLE
last updated : 3/2/2019
You can use information stored in Git to get the latest time when a file was modified.
1st approach
Track it manually, but this can be error-prone if you forget to edit the modified time. So I would recommend that as the last option if you can't get others to work.
2nd approach
You can edit your gatsby-node.js to pull information from Git like so:
const { execSync } = require("child_process")
exports.onCreateNode = ({ node, actions }) => {
// ...
if (node.internal.type === "MarkdownRemark") {
const gitAuthorTime = execSync(
`git log -1 --pretty=format:%aI ${node.fileAbsolutePath}`
).toString()
actions.createNodeField({
node,
name: "gitAuthorTime",
value: gitAuthorTime,
})
}
// ...
}
Then, in your template, you can fetch it:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
fields {
gitAuthorTime
}
# ...
}
}
And, finally, use it in JSX like so:
import React from "react"
const BlogPost = (props) => {
const { gitAuthorTime } = props.data.markdownRemark.fields
render(<p>Updated at: ${gitAuthorTime}</p>)
}
export default BlogPost
3rd approach
Similar to the previous one but it uses a plugin gatsby-transformer-info. It does a similar thing as in the 2nd approach, but you need to access the modified time differently this time. Like so:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
parent {
... on File {
fields {
gitLogLatestDate
}
}
}
# ...
}
}
I wrote more about this in my blog post "Add Updated At To Your Gatsby Blog" if you want to check it out.
Edit: The answer below is actually wrong, since File. modifiedTime is the modifiedTime of the markdown file itself & not the modifiedTime for your content. For example, if you deploy your blog on say, Netlify, then the modifiedTime of your files there will be different than in your local environment.
I think the right answer is to track it separately. If you're using a CMS like NetlifyCMS, you can create a field that automatically update the date/time on every edit.
Wherever you're querying for your markdown files, you can use the below field:
query {
allMarkdownRemark {
edges {
node {
frontmatter { /* other stuff */ }
parent {
... on File {
modifiedTime(formatString: "MM/DD/YYYY")
}
}
}
}
}
}
And access it in your via postEdge.node.parent.modifiedTime

Get label of vue-i18n in JS

In my laravel 5.6 / vue.js 2.5.7/ Bootstrap4.1.0 application I added i18n support using
https://github.com/kazupon/vue-i18n
plugin
It works ok, but if in vue code I use syntax, like :
<p>{{ $t('message.hello', {name: 'visitor'}) }}</p>
I would like to get label in Javascript, say in computed , as some labels depends on values of other data.
Can I do this and if yes how?
UPDATED :
I want to detalize my task:
I have editor in 2 modes for insert and update and in the template I give a parameter to other subcomponent editor-header :
<template>
...
<editor-header :header_title="header_title"></editor-header>
...
mounted() {
...
this.setAppTitle(this.header_title, false);
}, // mounted() {
...
computed: {
header_title: function () {
// if (!this.is_insert) return "Edit " + t("document_category.document_category");
// return "Add "+t("document_category.document_category");
if (!this.is_insert) return "Edit document category";
return "Add document category";
},
In my i18n files I have both labels : “Edit/Add” and “document category”, but I do not know if there is a way to manipulate as I tried in header_title.
Can it be done in some other way ?
Thanks!

Resources