Gulp-wrap: how to use dynamic template files? - gulp

gulp-wrap is an excellent tool allowing me to use template layout to populate HTML files:
gulp.src('/my/project/**/*.html')
.pipe(wrap({ src: '/my/project/layout.tpl' }))
.pipe(gulp.dest('/my/webapp/'));
Now my situation is: for some HTML files (say, files with name containing 'special'), I need to use a different template layout special.tpl. How to incorporate into the workflow above? I have searched but got no result.
Please help, thanks.

Solved the problem by using gulp-switch:
function getLayout(file) {
return file.path.indexOf('special') >= 0 ? 'special' : 'common';
}
return gulp.src(dpwFiles.pages)
.pipe(gulpSwitch(getLayout, {
common: wrap({src: 'layout.tpl' }),
special: wrap({src: 'special.tpl' })
}))
.pipe(gulp.dest('/my/webapp'));

Related

Gatsby mdx pages not rendering fully when placed in subfolder of src/pages

I have been converting a WordPress site to Gatsby, and everything works nicely with gatsby develop, however after building with gatsby build some pages seem to render with only the page body and no wrapper layout or styling. I am using markdown pages with mdx, and I have all my markdown files under subfolders of the src/pages directory, like this:
src/pages/
--project/
--contact.md
--outputs.md
--project.md
--sources.md
--software/
--apps.md
--frontend.md
--system.md
The above structure is more for organizational reasons than anything else (there are many more mdx files in reality). It does also correspond to the overall path structure of the site, however. In my built site, when I go to http://localhost:9000/contact the page renders perfectly, but when I visit http://localhost:9000/project or any other pages relating to that folder I only see the page body (the text content), with no layout component wrapper or styling. Everything under the software folder renders fine.
Each markdown file has a slug defined in the usual way in the frontmatter. The slug defined in src/project/project.md is just '/project'. The slug for src/project/contact.md is '/project/contact'.
Clearly the presence of src/pages/project/project.md is causing problems, but I can't figure out exactly why. I tried renaming that to src/pages/project/index.md, but that did nothing. Interestingly, when I look at public/project I see an index.html at the top level, with subfolders for each subpage, each containing its index.html. For public/software there is no index.html at the top level.
My gatsby-config.js (relevant parts):
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages`,
},
},
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`, `.markdown`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1024,
},
},
],
},
},
My template (under templates/page.js - I use some MUI components):
export default function SitePageTemplate({ data: { mdx } }) {
const { frontmatter, body } = mdx;
const { title } = frontmatter;
return (
<Layout>
<Seo title={title} />
<Container fixed>
<Stack direction="row" justifyContent="space-between">
<SideBar/>
<div style={ { padding: "0 0 0 3.5%", width: "75%" } }>
<MDXRenderer>{body}</MDXRenderer>
</div>
</Stack>
</Container>
</Layout>
);
}
export const pageQuery = graphql`
query ($id: String!) {
mdx(id: { eq: $id }) {
body
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}`
My gatsby-node.js:
const path = require("path");
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
createPage({
path: "/using-dsg",
component: require.resolve("./src/templates/using-dsg.js"),
context: {},
defer: true,
})
const result = await graphql(`
query MARKDOWN {
allMdx {
edges {
node {
id
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}
}
}
`);
if (result.errors) {
reporter.panicOnBuild("🚨 ERROR: Loading \"createPages\" query");
}
const md = result.data.allMdx.edges;
md.forEach(({ node }, index) => {
createPage({
// This component will wrap our MDX content
component: path.resolve("./src/templates/page.js"),
// Pass any value you want to access inside the template. They'll be available via `props`.
context: {
id: node.id
},
// Slug defined with frontmatter in each MDX file.
path: node.frontmatter.slug
});
});
}
If I place all the markdown files flat under the src/pages directory the problem goes away. But I would like to retain the above folder layout so that the markdown is organized properly. How can I do this whilst at the same time avoiding this problem?
OK, returning to this issue after a few months, I think I finally solved it. A warning I was also getting turned out to be the real clue - initially I had thought it unrelated to this issue. At develop and at build time I was getting a warning in the following format:
warn Non-deterministic routing danger: Attempting to create page: "/project/contact/", but page
"/project/contact" already exists
Others have reported this warning, but none of the reasons or propsed fixes seemed to relate to my problem. Looking at my gatsby-config.js, however, I noticed that I had at some time included the gatsby-plugin-page-creator plugin. I suspected that somehow this might be generating pages in addition to the mdx plugin. And it seemed as if this was right - removing the plugin removed both the warnings about duplicate page creation and also fixed my rendering problems. Everything looks fine now, for both development and production versions of my site.
I can't remember why I originally included this plugin - I was originally using the mdx extension for my markdown files, and I think I needed gatsby-plugin-page-creator so that files with that extension would be correctly interpreted as markdown. I now use the standard md extension, and removing gatsby-plugin-page-creator doesn't cause any problems.

How can I display dynamic HTML having Vue variables inside?

I am trying to make the page content dynamic. I am using ck-editor in which i added html content and used the same vue variables inside it which i declared in the vue file where i want to show ck-editor data. I found a similar post vuejs - “editing” html inside variable
which works fine if i write the html inside a variable. But in my case, i am saving data in database. It is saving properly with html tags, without converting the tags. When i get data using axios it returns it in form of string. And i used vue variable to display that html.
Here is my code for better understanding:
<div v-html="htmlText"></div>
new Vue({
el: '#app',
created() {
this.getSalesContent();
},
data: {
salesContent: '',
pageName: 'Sales',
salesNumber: '987-586-4511'
},
computed: {
htmlText() {
return `${this.salesContent}`;
//return this.salesContent;
}
},
methods: {
getSalesContent(){
axios.get('api/Sales').then(({ data }) => { // getting data from DB
this.salesContent = data.sales; //data.sales have this.pageName and this.salesNumber variables
});
}
}
});
Here is the example of data saved in db:
<p style="font-weight:bold"><span style="color:red">{{pageName}}</span>,</p>
<p style="font-weight:bold"><span style="color:red">${this.pageName} ${this.pageName}</span></p>
<p style="font-weight:bold">Contact Sales at ${this.salesNumber} {{salesNumber}}</span></p>
I used variables in all possible ways. But on the page they are printing in it the same way i saved it. Here is the output:
screenshot
Can anyone help me make it working.
Thanks in Advance.
According to the docs this does not seem possible:
https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
Particularly:
The contents of the span will be replaced with the value of the
rawHtml property, interpreted as plain HTML - data bindings are
ignored.
You could as suggested in that answer just use a computed based on what you get from the server.
IMHO since the salesContent is fetched from db, it's a plain String. Thus nor vuejs or vanilla javascript will replace the inline variables with their values. (It may be possible by using eval, but it's totally out of question...) You should manually do that with String replace function. Like the following:
<p style="font-weight:bold"><span style="color:red">{{pageName}}</span>,</p>
<p style="font-weight:bold">Contact Sales at {{salesNumber}}</span></p>
methods: {
getSalesContent(){
axios.get('api/Sales').then(({ data }) => { // getting data from DB
let salesContent = data.sales; //data.sales have this.pageName and this.salesNumber variables
salesContent = salesContent.replace(/{{pageName}}/g, this.pageName)
salesContent = salesContent.replace(/{{salesNumber}}/g, this.salesNumber)
this.salesContent = salesContent
});
}
}

Angular Conditional Views?

I have inherited a mess of a Angular project. I've never really messed with Angular too much but know MVC well enough to feel like I can learn. My question is I have a property of a JSON object that I want to return a different views for. (one is an archived state and one is a non-archived state) As they both have different view templates, how would I return the non-archive template if the json.status == 'archived'
I have the following as my current StateProvider's templateURL property.
templateUrl: appConfig.viewPath + 'non-archived.html?v=' + appConfig.version
should I just return multiple template urls here? Or do I have to create a whole new url path?
Thanks!
I've gone down this road a few times, I don't think I've found the optimal way yet, but I've learned a few things.
It really all depends on when you have access to your json-object. You can pass a function to templateUrl, and send in a service.. (A service that returns your current json-object could be great, but how would you update it? Probably when you change route right? Then you have a egg-hen problem. You can't decide route until you have the json-object, but you don't have the json-object until you change route.)
But IF you have access to the json-object you could do something like this:
templateUrl: function(){
var tpl = (json.status == 'archived') ? 'archived.html?v=' : 'non-archived.html?v=';
return appConfig.viewPath + tpl + appConfig.version
}
But my guess is that you don't have access to the json-object until after the route has loaded.
Then I'd say the easiest way (but maybe not so pretty) is to have just one template. $scope.json = json in the controller:
<div ng-if="json.status == 'archived'">
<h1>ARCHIVED</h1>
...
</div>
<div ng-if="json.status != 'archived'">
<h1>NOT ARCHIVED</h1>
...
</div>
Or if you think that is too cheap, declare two routes. The whole "create a whole new url path" is not as painful as you might think. It'll be considerably less complex than trying to wedge out a value from a route before it has loaded.
1: Try this. send json.status in $stateParams and apply condition inside stateProvider :
$stateProvider.state('home', {
templateProvider: ['$stateParams', 'restService' , function ($stateParams, restService) {
restService.getJson().then(function(json) {
if (status.status == 'archived') {
return '<div ng-include="first.html"></div>';
} else {
return '<div ng-include="second.html"></div>';
}
})
}]
});
2 : or simply in view you can try this:
<div ng-if="json.status == 'archived'">
<h1>ARCHIVED</h1>
...
</div>
<div ng-if="json.status != 'archived'">
<h1>NOT ARCHIVED</h1>
...
</div>

What tools are available for translating (human) languages within HTML code?

Is there a tool or task runner that can take an HTML document in one language, parse out the content in general/specific HTML tags, run that content through Google translate, then put it back into the markup in the right place in new files? Basically, digest one source file and output multiple variations in different (non-computer) languages.
What I'm hoping for is:
index.html
<!DOCTYPE html>
<html>
<body>
привет мир!
</body>
</html>
Gets compiled to:
en/index.html
<!DOCTYPE html>
<html>
<body>
Hello World!
</body>
</html>
ru/index.html
<!DOCTYPE html>
<html>
<body>
привет мир!
</body>
</html>
ch/index.html
<!DOCTYPE html>
<html>
<body>
你好世界!
</body>
</html>
I obviously don't mind setting up some sort of Gruntfile or whatever that dictates the languages, destinations, etc.
Take a look at get-translation or grunt-google-translate. Also Google Translate has simple REST API, you can write your own plugin from the scratch or use something like grunt-restful.
I was able to piece together a solution using Ruby and Grunt. This could be refactored to be more robust, but it was a working solution, so I ran with it. Keep in mind, the translating script overwrites the source files. Only the Grunt starts making duplicates in new destinations.
Using the easy_translate Rub gem I wrote this script:
#!/usr/bin/ruby
require 'easy_translate'
EasyTranslate.api_key = '' # Get from google
target_path = '' # path to translate
Dir.glob(target_path) do |item|
next if item == '.' or item == '..' or item == '.svn'
contents = ""
update = ""
File.open(item, "r") do |file|
contents += file.read
update += EasyTranslate.translate(contents, :from => :russian, :to => :en)
end
File.open(item, "w"){ }
File.open(item, "w") do |file|
file.write(update)
end
end
Walking through this, each file in the target_path is checked if it’s a worthwhile item we make variables contents and update we’ll use to place in the old and new versions of the contents of the file, respectively. Then we open the file and fill up those variable. On line 20 we empty the file, then in the last block on lines 22-24 we write the update string into the file. I used File.open(item, "w") {} instead of .truncate(0) because I was getting random Unicode characters added to the contents of the file with the latter option. Again, overwriting the files in the translation process is not ideal. It'd be better to make a copy in a new destination, but I didn't do that.
The Ruby script returned a single line of minified HTML and broke Smarty templating, so I used used grunt-prettify and grunt-text-replace to prettify the HTML for ease of use and to make the Smarty stuff work again. The Gruntfile looks like this:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
'prettify': {
options: {
indent: 4,
indent_char: ' ',
wrap_line_length: 0,
brace_style: 'expand',
"unformatted": [
"a",
"code",
"pre"
]
},
all: {
expand: true,
cwd: 'static_ugly',
ext: '.html',
src: ['*.html'],
dest: 'static_pretty'
}
},
'replace': {
fix_smarty: {
src: ['static_pretty/*.html'],
overwrite: true,
replacements: [{
from: '{/ Strip',
to: '{/strip'
},{
from: '{$ This-> setLayout ',
to: '{$this->setLayout'
},{
from: '{$ this- > setPageTitle ',
to: '{$this->setPageTitle'
},{
from: '$ this-> setPageTitle ',
to: '$this->setPageTitle'
},{
from: '{$ smarty.block.footer ',
to: '{$smarty.block.footer'
}]
}
}
});
grunt.loadNpmTasks('grunt-prettify');
grunt.loadNpmTasks('grunt-text-replace');
grunt.registerTask('default', ['prettify', 'replace']);
};
There was a lot more find/replace stuff that I left out since it's project-specific. This solution pretty much does what I wanted in the original question. I did some manual moving around of files in the name of getting it done, but I believe I'll be using a variation of this moving forward and consider it a work in progress.

With ng-bind-html-unsafe removed, how do I inject HTML?

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.
However, I can't get it to work.
<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>
I discovered that it is because it was removed from AngularJS (thanks).
But without ng-bind-html-unsafe, I get this error:
http://errors.angularjs.org/undefined/$sce/unsafe
Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
Then you can use it like this :
<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>
And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/
You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.
Instead, you'll want to do something like this:
<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>
In your controller, inject the $sce service, and mark the HTML as "trusted":
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.preview_data.preview.embed.htmlSafe =
$sce.trustAsHtml(preview_data.preview.embed.html);
}
Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)
You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
you need to include ngSanitize module on your app
eg: var app = angular.module('myApp', ['ngSanitize']);
you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
For me, the simplest and most flexible solution is:
<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>
And add function to your controller:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
Don't forget add $sce to your controller's initialization.
The best solution to this in my opinion is this:
Create a custom filter which can be in a common.module.js file for example - used through out your app:
var app = angular.module('common.module', []);
// html filter (render text as html)
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
Usage:
<span ng-bind-html="yourDataValue | html"></span>
Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't
Anyway - that's the way I do it - 67% of the time, it works ever time.
You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
You do not need to use {{ }} inside of ng-bind-html-unsafe:
<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>
Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview
The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.
I've had a similar problem. Still couldn't get content from my markdown files hosted on github.
After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.
Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.
Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)
Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.
Example from the AngularJS documentation on $sce:
angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
// Completely disable SCE. For demonstration purposes only!
// Do not use in new projects.
$sceProvider.enabled(false);
});
Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:
SCE gives you a lot of security benefits for little coding overhead.
It will be much harder to take an SCE disabled application and either
secure it on your own or enable SCE at a later stage. It might make
sense to disable SCE for cases where you have a lot of existing code
that was written before SCE was introduced and you're migrating them a
module at a time.
You can use filter like this
angular.module('app').filter('trustAs', ['$sce',
function($sce) {
return function (input, type) {
if (typeof input === "string") {
return $sce.trustAs(type || 'html', input);
}
console.log("trustAs filter. Error. input isn't a string");
return "";
};
}
]);
usage
<div ng-bind-html="myData | trustAs"></div>
it can be used for other resource types, for example source link for iframes and other types declared here