Skip to content

Commit

Permalink
First setup 🔥
Browse files Browse the repository at this point in the history
  • Loading branch information
JelteMX committed Jun 27, 2020
0 parents commit daffd88
Show file tree
Hide file tree
Showing 24 changed files with 12,823 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const base = require("./node_modules/@mendix/pluggable-widgets-tools/configs/eslint.ts.base.json");

base["rules"]["@typescript-eslint/ban-ts-ignore"] = "warn";
base["rules"]["@typescript-eslint/no-var-requires"] = "off";

module.exports = {
...base
};
15 changes: 15 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
* text=auto
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.json text eol=lf
*.xml text eol=lf
*.md text eol=lf
*.gitattributes eol=lf
*.gitignore eol=lf
*.png binary
*.jpg binary
*.gif binary
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist/
node_modules/
*.log
.env
.vscode
package-lock.json
15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
The Apache License v2.0

Copyright 2020 Mendix Technology BV

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[![Apache License](https://img.shields.io/badge/license-Apache%202.0-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Dependencies](https://david-dm.org/JelteMX/mendix-text-template.svg)]([https://david-dm.org/JelteMX/mendix-text-template](https://david-dm.org/JelteMX/mendix-text-template))
[![DevDependencies](https://david-dm.org/JelteMX/mendix-text-template/dev-status.svg)]([https://david-dm.org/JelteMX/mendix-text-template?type=dev](https://david-dm.org/JelteMX/mendix-text-template?type=dev))
[![Support](https://img.shields.io/badge/Support-Community%20(no%20active%20support)-orange.svg)](https://docs.mendix.com/developerportal/app-store/app-store-content-support)
[![Studio](https://img.shields.io/badge/Studio%20version-8.6.4%2B-blue.svg)](https://appstore.home.mendix.com/link/modeler/)
![GitHub release](https://img.shields.io/github/release/JelteMX/mendix-text-template)
![GitHub issues](https://img.shields.io/github/issues/JelteMX/mendix-text-template)
[![Available](https://img.shields.io/badge/Test%20Project-available-green.svg)](https://github.com/JelteMX/widget-test-projects)

# Mendix Text Template

Render Markdown/HTML from a Text template. As powerful as the HTMLSnippet/Formatstring widget, but with more flexibility.

This uses the [react-markdown](https://github.com/rexxars/react-markdown) library (MIT License) and [react-shortcodes](https://github.com/djm/remark-shortcodes) library (MIT License)

![logo](/assets/AppStoreIcon.png)

> See test-project (**_to be published_**) for a live demo!
![preview](/assets/template.png)

Tested in the following Browsers:

- IE11 / Edge
- Chrome, Firefox, Safari
- Should work on Mobile Web

> This widget is built in Mendix 8.6.4. It would probably work in 8.0.0, but there are no guarantees.
## Features

- Render Markdown (string) as HTML. Markdown is [Github Flavored Markdown](https://guides.github.com/features/mastering-markdown/)
- Render HTML from a text template. It will try to parse it as proper React elements.
- Use your standard text template in Mendix, where values from your Mendix objects are replaced by `{1}` etc
- Want to use static/dynamic images? You can get the direct url in your template using the `$$[placeholder]$$` strings
- Using a file? You can get a direct url to it with the `$$file$$` string

## TODO

- Currently the `[[ shortcode ]]` template (everything between `[[` and `]]`) is reserved for future shortcode support. You can think of something like `[[ youtube id="" ]]` for a YouTube embed. This is not there yet. Any shortcode that you will put in there will not be rendered.
- There might be useful other strings (`$$key$$`) added later. Currently I am only using this for images and a file
- File is only a single one, because of a bug in Mendix Studio 8.6.4 (cannot create a list of files like images)
- It always renders a container with the class name. This could be made optional (although not ideal)
- Mendix Studio support will be added later

## What is it __NOT__?

Although this is very powerful, this widget does __NOT__ support inline scripts like you can do with the HTMLSnippet/Javascript snippet. The reason for that is simple: It should not be done in the Mendix page itself. I might add a ``[[ script ]]`` shortcode later where you can load a script from an external source.

By default HTML is escaped and should not render. You can switch this off. If you happen to find an XSS vulnerability (e.g. you can somehow get `alert(1)` working), please contact me and I will see how we can prevent this!

## Usage

- Place the Text Template widget on your page (doesn't need a context like dataview)
- Define your template. If you have a context, you can define Parameters. These parameters can then be used in your template with brackets (`{1}` etc). __Note: Because Mendix uses the brackets, you cannot use them for other purposes in your template!__
- Want to use HTML? `HTML -> Escape HTML: No`. Use with caution. Also, for some elements (like in the Test-project with an SVG) you might want to surround them with a `<div>` element
- Misc settings may or may not be usefull. They will add certain `data-` tags to all generated elements. Also, if there are certain elements prohibited, you can disable them (This is a list like `image,strong`). When disabling certain elements, you can choose whether or not to show them (if you want to, set **Unwrap disallowed** to Yes)
- This element has Conditional Visibility built-in

## Test project

The Test-project can be downloaded here: __*To be published*__

## Issues, suggestions and feature requests

Please report your issues [here](https://github.com/JelteMX/mendix-text-template/issues)

## License

Apache 2
Binary file added assets/AppStoreIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/in_page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "texttemplateelement",
"widgetName": "TextTemplateElement",
"version": "1.0.0",
"description": "My widget description",
"copyright": "2020 Mendix Technology BV",
"author": "Jelte Lagendijk <jelte.lagendijk@mendix.com>",
"config": {
"widgetPath": "./dist/MxTestProject/widgets",
"projectPath": "./dist/MxTestProject/",
"mendixHost": "http://windows:8080",
"developmentPort": "3000"
},
"packagePath": "mendix",
"scripts": {
"start": "concurrently \"pluggable-widgets-tools start:server --open\" \"npm:dev\"",
"dev": "pluggable-widgets-tools start:ts",
"build": "pluggable-widgets-tools build:ts",
"lint": "pluggable-widgets-tools lint",
"lint:fix": "pluggable-widgets-tools lint:fix",
"prerelease": "npm run lint",
"release": "pluggable-widgets-tools release:ts"
},
"license": "Apache-2.0",
"devDependencies": {
"@mendix/pluggable-widgets-tools": "^8.9.2",
"@types/big.js": "^4.0.5",
"@types/classnames": "^2.2.10",
"@types/jest": "^24.0.0",
"@types/react": "~16.9.0",
"@types/react-dom": "~16.9.0",
"@types/react-test-renderer": "~16.9.0"
},
"dependencies": {
"classnames": "^2.2.6",
"react-markdown": "^4.3.1",
"remark-shortcodes": "^0.3.1"
}
}
5 changes: 5 additions & 0 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const base = require("./node_modules/@mendix/pluggable-widgets-tools/configs/prettier.base.json");

module.exports = {
...base
};
14 changes: 14 additions & 0 deletions src/TextTemplateElement.editorPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component, ReactNode, createElement } from "react";
import { TextTemplateElementPreviewProps } from "../typings/TextTemplateElementProps";

declare function require(name: string): string;

export class preview extends Component<TextTemplateElementPreviewProps> {
render(): ReactNode {
return <div />;
}
}

export function getPreviewCss(): string {
return require("./ui/TextTemplateElement.scss");
}
69 changes: 69 additions & 0 deletions src/TextTemplateElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createElement, FunctionComponent } from "react";
import { hot } from "react-hot-loader/root";
import classNames from "classnames";
import ReactMarkdown from "react-markdown/with-html";

const shortcodes = require("remark-shortcodes");

import { TextTemplateElementContainerProps } from "../typings/TextTemplateElementProps";

import "./ui/TextTemplateElement.scss";

import { filteredList } from "./util/react-markdown";
import { renderers } from "./util/shortcodes";
import { replaceImages, replaceFile } from "./util/replacer";

const TextTemplateElement: FunctionComponent<TextTemplateElementContainerProps> = ({
class: className,
dataTemplate,
resImages,
fileFile,
optDisallowedTypes,
optSkipHTML,
optEscapeHTML,
optSourcePos,
optRawSourcePos,
optIncludeNodeIndex,
optUnwrapDisallowed
}) => {
const disallowedArr = filteredList(optDisallowedTypes);

const disallowed = disallowedArr.length > 0 ? disallowedArr : undefined;
const containerClass = classNames(className, {
empty: !dataTemplate.value,
loading: dataTemplate.status === "loading",
unavailable: dataTemplate.status === "unavailable"
});

if (dataTemplate.status !== "unavailable" && dataTemplate.value) {
let content = dataTemplate.value;

if (resImages && resImages.length > 0) {
content = replaceImages(content, resImages);
}

if (typeof fileFile !== "undefined") {
content = replaceFile(content, fileFile);
}

return (
<ReactMarkdown
source={content}
className={containerClass}
skipHtml={optSkipHTML}
escapeHtml={optEscapeHTML}
sourcePos={optSourcePos}
rawSourcePos={optRawSourcePos}
includeNodeIndex={optIncludeNodeIndex}
unwrapDisallowed={optUnwrapDisallowed}
disallowedTypes={disallowed}
plugins={[shortcodes]}
renderers={renderers}
/>
);
}

return <div className={containerClass}></div>;
};

export default hot(TextTemplateElement);
75 changes: 75 additions & 0 deletions src/TextTemplateElement.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8" ?>
<widget id="mendix.texttemplateelement.TextTemplateElement" pluginWidget="true" needsEntityContext="false" offlineCapable="true"
supportedPlatform="Web"
xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../node_modules/mendix/custom_widget.xsd">
<name>Text Template</name>
<description>Render Markdown/HTML in Mendix using a text template</description>
<icon>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</icon>
<properties>
<propertyGroup caption="Main">
<property key="dataTemplate" type="textTemplate" required="true" multiline="true">
<caption>Template</caption>
<description>This is the markdown/HTML that is rendered as React elements in your application</description>
</property>
</propertyGroup>
<propertyGroup caption="Resources">
<property key="resImages" type="object" isList="true" required="false">
<caption>Images</caption>
<description></description>
<properties>
<property key="imgKey" type="string" required="true" defaultValue="">
<caption>Key</caption>
<category>Image</category>
<description>Key should only consist of lowercase letters and numbers, no spaces. (a-z,0-1). You can then use the image in your text template by surrounding it with $$. You key should be unique in all resources.

Example: set key to 'image1'. You can then use the image url in your text template as '$$image1$$'</description>
</property>
<property key="imgImage" type="image" required="true">
<caption>Image</caption>
<category>Image</category>
<description></description>
</property>
</properties>
</property>
<property key="fileFile" type="file" required="false">
<caption>File</caption>
<description>You can associate 1 file (list is not supported for now due to platform related issues) to the text-template.

This is translated in the template to '$$file$$'</description>
</property>
</propertyGroup>
<propertyGroup caption="HTML">
<property key="optEscapeHTML" type="boolean" defaultValue="true">
<caption>Escape HTML</caption>
<description>Setting to "No" will cause HTML (in Markdown) to be rendered. Be aware this might cause security/UI issues if the input is user-generated. Use at your own risk. The library will block any &lt;script&gt; elements, but you cannot purely rely on it.</description>
</property>
<property key="optSkipHTML" type="boolean" defaultValue="false">
<caption>Skip HTML</caption>
<description>Setting to true will skip inlined and blocks of HTML.</description>
</property>
</propertyGroup>
<propertyGroup caption="Misc">
<property key="optSourcePos" type="boolean" defaultValue="false">
<caption>Source Pos</caption>
<description>Setting to true will add data-sourcepos attributes to all elements, indicating where in the markdown source they were rendered from</description>
</property>
<property key="optRawSourcePos" type="boolean" defaultValue="false">
<caption>Raw Source Pos</caption>
<description>Setting to true will pass a sourcePosition property to all renderers with structured source position information</description>
</property>
<property key="optIncludeNodeIndex" type="boolean" defaultValue="false">
<caption>Include Node index</caption>
<description>Setting to true will pass index and parentChildCount props to all renderers</description>
</property>
<property key="optDisallowedTypes" type="string" required="false">
<caption>Disallowed nodes</caption>
<description>Comma seperated list of nodes that are disallowed.</description>
</property>
<property key="optUnwrapDisallowed" type="boolean" defaultValue="false">
<caption>Unwrap disallowed</caption>
<description>Setting to true will try to extract/unwrap the children of disallowed nodes. For instance, if disallowing Strong, the default behaviour is to simply skip the text within the strong altogether, while the behaviour some might want is to simply have the text returned without the strong wrapping it.</description>
</property>
</propertyGroup>
</properties>
</widget>
19 changes: 19 additions & 0 deletions src/components/ErrorElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createElement, FunctionComponent, ReactElement } from "react";
import classNames from "classnames";

export const ErrorElement: FunctionComponent<{ className: string; text: string }> = ({ className, text }) => {
return (
<div className={classNames(className, "error")}>
<div className="alert alert-danger">{text}</div>
</div>
);
};

export const renderNodeError = (className: string): ReactElement => (
<ErrorElement
className={className}
text={
"Widget Error: Markdown Element can only use one of two lists for nodes: allowed or disallowed! Please remove one of them."
}
/>
);
Empty file added src/components/render/.gitkeep
Empty file.
11 changes: 11 additions & 0 deletions src/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="TextTemplateElement" version="1.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="TextTemplateElement.xml"/>
</widgetFiles>
<files>
<file path="mendix/texttemplateelement"/>
</files>
</clientModule>
</package>
Empty file added src/ui/TextTemplateElement.scss
Empty file.
39 changes: 39 additions & 0 deletions src/util/react-markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NodeType } from "react-markdown";

export const nodeTypes: NodeType[] = [
"root",
"text",
"break",
"paragraph",
"emphasis",
"strong",
"thematicBreak",
"blockquote",
"delete",
"link",
"image",
"linkReference",
"imageReference",
"table",
"tableHead",
"tableBody",
"tableRow",
"tableCell",
"list",
"listItem",
"definition",
"heading",
"inlineCode",
"code",
"html",
"virtualHtml"
];

export const filteredList = (str = ""): NodeType[] => {
const types = str
.trim()
.split(",")
.map(st => st.trim());
const filtered = nodeTypes.filter(t => types.indexOf(t) !== -1);
return filtered;
};
Loading

0 comments on commit daffd88

Please sign in to comment.