Skip to content

Commit a5c4e3d

Browse files
authored
Merge pull request #1077 from Patternslib/modernize-markdown
Modernize markdown
2 parents 910e3e9 + 7fcfdc4 commit a5c4e3d

31 files changed

+1064
-426
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@
1414
"@fullcalendar/timegrid": "^5.11.3",
1515
"@juggle/resize-observer": "^3.4.0",
1616
"@stomp/stompjs": "^6.1.2",
17+
"dompurify": "^2.4.0",
1718
"google-code-prettify": "^1.0.5",
19+
"highlight.js": "<11",
1820
"imagesloaded": "^4.1.4",
1921
"intersection-observer": "^0.12.2",
2022
"jquery": "^3.6.1",
2123
"jquery-jcrop": "^0.9.13",
2224
"luxon": "2.4.0",
25+
"marked": "^4.1.0",
2326
"masonry-layout": "^4.2.2",
2427
"moment": "^2.29.4",
2528
"moment-timezone": "^0.5.37",
2629
"photoswipe": "^4.1.3",
2730
"pikaday": "^1.8.0",
31+
"prettier": "^2.7.1",
32+
"prismjs": "^1.29.0",
2833
"promise-polyfill": "^8.2.3",
2934
"screenfull": "^6.0.2",
3035
"select2": "^3.5.1",

src/core/registry.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ const registry = {
140140
patterns.splice(patterns.indexOf("validation"), 1);
141141
patterns.unshift("validation");
142142
}
143+
// Add clone-code to the very beginning - we want to copy the markup
144+
// before any other patterns changed the markup.
145+
if (patterns.includes("clone-code")) {
146+
patterns.splice(patterns.indexOf("clone-code"), 1);
147+
patterns.unshift("clone-code");
148+
}
149+
143150
return patterns;
144151
},
145152

@@ -150,6 +157,9 @@ const registry = {
150157

151158
if (typeof content === "string") {
152159
content = document.querySelector(content);
160+
} else if (content instanceof Text) {
161+
// No need to scan a TextNode.
162+
return;
153163
} else if (content.jquery) {
154164
content = content[0];
155165
}

src/pat/clone-code/clone-code.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { BasePattern } from "../../core/basepattern";
2+
import code_wrapper_template from "./templates/code-wrapper.html";
3+
import dom from "../../core/dom";
4+
import Parser from "../../core/parser";
5+
import registry from "../../core/registry";
6+
import utils from "../../core/utils";
7+
8+
export const parser = new Parser("clone-code");
9+
parser.addArgument("source", ":first-child");
10+
parser.addArgument("features", null, ["format"]);
11+
12+
class Pattern extends BasePattern {
13+
static name = "clone-code";
14+
static trigger = ".pat-clone-code";
15+
parser = parser;
16+
17+
async init() {
18+
// Source
19+
if (this.options.source.lastIndexOf(":", 0) === 0) {
20+
this.source = this.el.querySelector(this.options.source);
21+
} else {
22+
this.source = document.querySelector(this.options.source);
23+
}
24+
await this.clone();
25+
}
26+
27+
async clone() {
28+
// Clone the template.
29+
let markup =
30+
this.source.nodeName === "TEMPLATE"
31+
? this.source.innerHTML
32+
: this.source.outerHTML;
33+
34+
// Create temporary wrapper.
35+
let tmp_wrapper;
36+
if (this.source.nodeName === "HTML") {
37+
// We have a full HTML document which we cannot wrap into a div.
38+
tmp_wrapper = new DOMParser().parseFromString(markup, "text/html");
39+
} else {
40+
tmp_wrapper = document.createElement("div");
41+
tmp_wrapper.innerHTML = markup;
42+
}
43+
44+
// Remove elements with the class ``clone-ignore``.
45+
const ignore = tmp_wrapper.querySelectorAll(".clone-ignore");
46+
for (const _el of ignore) {
47+
_el.remove();
48+
}
49+
50+
// Get back the clone string depending of what the wrapper is.
51+
markup =
52+
tmp_wrapper instanceof HTMLDocument
53+
? tmp_wrapper.documentElement.outerHTML
54+
: tmp_wrapper.innerHTML;
55+
56+
if (this.options.features?.includes("format")) {
57+
// Format the markup.
58+
const prettier = (await import("prettier/standalone")).default;
59+
const parser_html = (await import("prettier/parser-html")).default;
60+
markup = prettier.format(markup, {
61+
parser: "html",
62+
plugins: [parser_html],
63+
});
64+
}
65+
66+
markup = utils.escape_html(markup);
67+
const pre_code_markup = dom.template(code_wrapper_template, { markup: markup });
68+
69+
// Now we need to wrap the contents in any case in a div.
70+
tmp_wrapper = document.createElement("div");
71+
tmp_wrapper.innerHTML = pre_code_markup;
72+
const pre_code_el = tmp_wrapper.children[0];
73+
74+
this.el.appendChild(pre_code_el);
75+
registry.scan(pre_code_el);
76+
}
77+
}
78+
79+
registry.register(Pattern);
80+
export default Pattern;
81+
export { Pattern };
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import Pattern from "./clone-code";
2+
import utils from "@patternslib/patternslib/src/core/utils";
3+
4+
describe("pat-clone-code", () => {
5+
afterEach(() => {
6+
document.body.innerHTML = "";
7+
});
8+
9+
it("is initialized correctly", async () => {
10+
document.body.innerHTML = `
11+
<div class="pat-clone-code">
12+
<p>hello world</p>
13+
</div>
14+
`;
15+
const el = document.querySelector(".pat-clone-code");
16+
17+
new Pattern(el);
18+
await utils.timeout(1); // wait a tick for async to settle.
19+
20+
const _el = document.body.querySelector(
21+
".pat-clone-code pre code.language-html"
22+
);
23+
expect(_el).toBeTruthy();
24+
expect(_el.innerHTML.trim()).toBe("&lt;p&gt;hello world&lt;/p&gt;");
25+
expect(_el.textContent.trim()).toBe("<p>hello world</p>");
26+
});
27+
28+
it("clones another source", async () => {
29+
document.body.innerHTML = `
30+
<div class="pat-clone-code"
31+
data-pat-clone-code="source: html">
32+
<p>hello world</p>
33+
</div>
34+
`;
35+
const el = document.querySelector(".pat-clone-code");
36+
37+
new Pattern(el);
38+
await utils.timeout(1); // wait a tick for async to settle.
39+
40+
console.log(document.body.innerHTML);
41+
const _el = document.body.querySelector(
42+
".pat-clone-code pre code.language-html"
43+
);
44+
expect(_el).toBeTruthy();
45+
46+
expect(_el.innerHTML.trim().indexOf("&lt;html&gt;")).toBe(0);
47+
expect(_el.innerHTML.trim().indexOf("&lt;body") > 0).toBe(true);
48+
});
49+
50+
it("ignores .clone-ignore", async () => {
51+
document.body.innerHTML = `
52+
<div class="pat-clone-code">
53+
<div>
54+
<div>1</div>
55+
<div class="clone-ignore">2</div>
56+
<div>3</div>
57+
</div>
58+
</div>
59+
`;
60+
const el = document.querySelector(".pat-clone-code");
61+
62+
new Pattern(el);
63+
await utils.timeout(1); // wait a tick for async to settle.
64+
65+
const _el = document.body.querySelector(
66+
".pat-clone-code pre code.language-html"
67+
);
68+
expect(_el).toBeTruthy();
69+
console.log(document.body.innerHTML);
70+
expect(_el.textContent.trim()).toBe(`<div>
71+
<div>1</div>
72+
73+
<div>3</div>
74+
</div>`);
75+
});
76+
77+
it("pretty prints output", async () => {
78+
document.body.innerHTML = `
79+
<div class="pat-clone-code" data-pat-clone-code="features: format">
80+
<div>
81+
<div>1</div>
82+
<div
83+
84+
85+
>2</div>
86+
87+
<div>3</div>
88+
</div>
89+
</div>
90+
`;
91+
const el = document.querySelector(".pat-clone-code");
92+
93+
new Pattern(el);
94+
await utils.timeout(1); // wait a tick for async to settle.
95+
96+
const _el = document.body.querySelector(
97+
".pat-clone-code pre code.language-html"
98+
);
99+
expect(_el).toBeTruthy();
100+
expect(_el.textContent.trim()).toBe(`<div>
101+
<div>1</div>
102+
<div>2</div>
103+
104+
<div>3</div>
105+
</div>`);
106+
});
107+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
## Description
2+
3+
The clone pattern lets the website user clone elements in the page.
4+
5+
## Documentation
6+
7+
The clone pattern is typically used in case you want to create a form on which it is unknown how many instances the user will need of a certain field or group of fields.
8+
For instance if you want to ask the user to fill out the name and birthdate of each family member.
9+
10+
### Usage
11+
12+
This pattern is enabled by adding the `pat-clone` class on a container element which contains the original element and any clones of it that may have beeen added.
13+
The first element inside the .pat-clone container is by default assumed to be the original element may be cloned by the user.
14+
15+
Consider the following markup:
16+
17+
<h3>List of family members</h3>
18+
19+
<div class="pat-clone">
20+
<!-- The first element inside the .pat-clone container is by default
21+
assumed to be the original element which will be cloned.
22+
-->
23+
<fieldset class="clone"> <!-- By default, pat-clone will consider elements with the "clone" class to be clones. -->
24+
<legend>Family member 1</legend>
25+
<input name="name-1" type="text" placeholder="Name" />
26+
<input name="date-1" type="date" placeholder="birthdate" /><br/>
27+
<button type="button" class="remove-clone">Remove</button>
28+
</fieldset>
29+
<!-- Now comes the clone trigger, a button which when clicked will cause
30+
a new clone of the above fieldset to be created.
31+
-->
32+
<button type="button" class="add-clone">Add an extra family member</button>
33+
</div>
34+
35+
Each time the user clicks on the button saying 'Add an extra family member', the
36+
pattern will make a copy of the first element inside the
37+
`.pat-clone` element, unless the `template` property is used to configure a
38+
different clone template. The `template` property takes a CSS selector as
39+
value.
40+
41+
Typically when using a template element, such an element would be hidden from view.
42+
43+
The new clone is always appended at the end, inside the `.pat-clone` element.
44+
45+
When creating a `.pat-clone` element containing existing clones, it's
46+
important that each existing clone either gets the `clone` CSS class or that you
47+
pass in a unique CSS selector for each clone via the `clone-element`
48+
property. This allows the pattern to properly determine how many existing
49+
clones there are to start with.
50+
51+
#### Incrementation of values in clones
52+
53+
The pat-clone pattern will automatically add up any number in the values of name and value attributes.
54+
For instance, if you have `name="item-1"` in your markup, then the first clone will be
55+
`name="item-2"` and the one after that `name="item-3"` etc.. If you want to print a number
56+
— for instance in a header of each clone — then you can use the syntax: `#{1}`. This string
57+
will be replaced by an number that's also increased by 1 for each clone.
58+
59+
### Example with a hidden template
60+
61+
The markup below would have exactly the same effect as the first example, but using a hidden template. This might come in handy when the first instance shown should either contain different information, or if it will have pre-filled values by the server.
62+
63+
<h3>List of family members</h3>
64+
65+
<div class="pat-clone" data-pat-clone="template: #my-template">
66+
<!-- First visible instance and also template -->
67+
<fieldset class="clone">
68+
<legend>Family member 1</legend>
69+
<input name="Mary Johnson" type="text" placeholder="Name" />
70+
<input name="1977-04-16" type="date" placeholder="birthdate" /><br/>
71+
<button type="button" class="remove-clone">Remove</button>
72+
</fieldset>
73+
74+
<!-- Template markup -->
75+
<fieldset id="my-template" hidden>
76+
<legend>Family member #{1}</legend>
77+
<input name="name-1" type="text" placeholder="Name" />
78+
<input name="date-1" type="date" placeholder="birthdate" /><br/>
79+
<button type="button" class="remove-clone">Remove</button>
80+
</fieldset>
81+
82+
<!-- Clone trigger -->
83+
<button type="button" class="add-clone">Add an extra family member</button>
84+
</div>
85+
86+
### Example with a hidden template which includes a pattern
87+
88+
Patterns in templates are initialized after cloning.
89+
However, the patterns in the template itself are not initialized if the template has the attribute ``hidden`` or the class ``disable-patterns``.
90+
This is to prevent double-initialization within the template and after being cloned.
91+
92+
<div id="template" class="disable-patterns" hidden>
93+
<input name="date-1" type="date" class="pat-date-picker" />
94+
</fieldset>
95+
96+
<div class="pat-clone" data-pat-clone="template: #template">
97+
<button type="button" class="add-clone">Add a date</button>
98+
</div>
99+
100+
101+
102+
103+
### Option reference
104+
105+
| Property | Description | Default | Allowed Values | Type |
106+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ----------------- | --------------------------------- |
107+
| template | Selects the element that will be cloned each time. You might often want to refer to a piece of template markup for this that is hidden with though the CSS. | :first | | CSS Selector |
108+
| max | Maximum number of clones that is allowed | | | Integer |
109+
| trigger-element | Selector of the element that will add the clone when clicked upon. | .add-clone | | CSS Selector |
110+
| remove-element | Selector of the element that will remove the clone when clicked upon. | .remove-clone | | CSS Selector |
111+
| remove-behaviour or remove-behavior | What should happen when the user clicks on the element to remove a clone? Two choices exist currently. Show a confirmation dialog, or simply remove the clone immediately. | "confirm" | "confirm", "none" | One of the allowed string values. |
112+
| clone-element | Selector of the individual clone element(s). | .clone | | CSS Selector |

src/pat/clone-code/index.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>pat-clone demo page</title>
5+
<meta charset="utf-8">
6+
<link rel="stylesheet" href="/style/common.css" />
7+
<script>
8+
window.__patternslib_disable_modernizr = true;
9+
</script>
10+
<script src="/bundle.min.js"></script>
11+
</head>
12+
<body>
13+
14+
<h1>Example - clone-code</h1>
15+
16+
<p class="clone-ignore">Demo on how to use pat-clone together with pat-syntax-highlight to show the source of any html snippet</p>
17+
18+
<div class="pat-clone-code"
19+
data-pat-clone-code="source: html">
20+
<p>Test paragraph</p>
21+
</div>
22+
23+
</body>
24+
</html>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<pre class="pat-syntax-highlight">
2+
<code class="language-html">
3+
${this.markup}
4+
</code>
5+
</pre>

0 commit comments

Comments
 (0)