-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmicrotypography.js
More file actions
178 lines (147 loc) · 5.91 KB
/
microtypography.js
File metadata and controls
178 lines (147 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import QuoteMarksReplacer from './quotemarks-replacer.js';
class Microtypography {
constructor(replacementsUrl, options = {}) {
const defaults = {
rootSelector: '#replacements',
successIconSelector: '#successIcon',
copyButtonSelector: '#copyButton',
inputTextSelector: '#inputText',
outputTextSelector: '#outputText',
openQuote: '“',
closeQuote: '”'
};
const config = Object.assign({}, defaults, options);
this.replacements = [];
this.replacementsTable = document.querySelector(config.rootSelector);
this.successIcon = document.querySelector(config.successIconSelector);
this.copyButton = document.querySelector(config.copyButtonSelector);
this.inputTextarea = document.querySelector(config.inputTextSelector);
this.outputTextarea = document.querySelector(config.outputTextSelector);
this.inputTextTimeout = null;
this.quotesReplacer = new QuoteMarksReplacer(config.openQuote, config.closeQuote);
// Initialize the application
this.loadReplacements(replacementsUrl);
this.attachEventListeners();
}
attachEventListeners() {
this.inputTextarea.addEventListener("input", this.performSearchAndReplace);
this.copyButton.addEventListener("click", this.copyToClipboard);
}
makeid(length) {
let result = '';
const characters = 'abcdef0123456789';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
loadReplacements(url) {
// Add random parameter to prevent caching
const cacheBustUrl = `${url}?${this.makeid(6)}`;
return fetch(cacheBustUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
this.replacements = data;
console.table(this.replacements);
this.renderReplacementTable();
return data; // Return data for chaining if needed
})
.catch(error => {
console.error("Error loading replacements:", error);
});
}
renderReplacementTable() {
if (!this.replacementsTable || !this.replacements.length) return;
const rows = this.replacements.map(element => {
// Format the search and replace terms for display
const searchHtml = element.search.replace(/[\u00A0-\u9999<>\&]/gim,
i => '&#' + i.charCodeAt(0) + ';');
// Heads up!
// The regexes may *seem* empty, but they are not. They are non-breaking spaces.
const replaceHtml = element.replace
.replace(/ /gim, '<span style="background-color: DeepSkyBlue"> </span>')
.replace(/ /gim, '<span style="background-color: orange"> </span>');
return `<tr>
<td>${element.label}</td>
<td class="s">${searchHtml}</td>
<td>${replaceHtml}</td>
</tr>`;
}).join('');
const tbody = this.replacementsTable.querySelector('tbody');
tbody.innerHTML = rows;
}
performSearchAndReplace = () => {
if (this.inputTextTimeout) {
clearTimeout(this.inputTextTimeout);
}
this.inputTextTimeout = setTimeout(() => {
const inputText = this.inputTextarea.value;
let outputText = inputText;
// Apply each search-and-replace operation
this.replacements.forEach((replacement) => {
let r = new RegExp(replacement.search, "gim");
let _ot = outputText;
outputText = outputText.replaceAll(r, replacement.replace);
if (_ot !== outputText) {
console.info("Applied replacement '" + replacement.label + "'");
}
});
// Replace quotes in the output text
outputText = this.applyTypographicQuotes(outputText);
// Update the output code
this.outputTextarea.value = outputText;
this.successIcon.classList.remove("visible");
}, 500);
}
applyTypographicQuotes = (text) => {
let outputText = this.quotesReplacer.replaceQuotesInHTML(text);
console.info("Applied typographic quotes: " + this.quotesReplacer.openQuote + " and " + this.quotesReplacer.closeQuote);
// Add according entry to replacements table as FIRST row
if (this.replacementsTable && this.replacementsTable.querySelector('tbody').children.length > 0 && !this.replacementsTable.querySelector('td.typoquotes-applied')) {
const firstRow = this.replacementsTable.querySelector('tbody').children[0];
const newRow = document.createElement('tr');
newRow.innerHTML = `<td class="typoquotes-applied">Typographic Quotes</td>
<td class="s">"Zollzeichen" quotes</td>
<td>${this.quotesReplacer.openQuote} and ${this.quotesReplacer.closeQuote}</td>`;
this.replacementsTable.querySelector('tbody').insertBefore(newRow, firstRow);
}
return outputText;
}
copyToClipboard = async () => {
const outputText = this.outputTextarea.value;
try {
// Use the modern Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(outputText);
} else {
// Fallback to the deprecated execCommand for older browsers
const tempTextarea = document.createElement("textarea");
tempTextarea.value = outputText;
document.body.appendChild(tempTextarea);
tempTextarea.select();
const success = document.execCommand("copy");
document.body.removeChild(tempTextarea);
if (!success) {
throw new Error("Copy command was unsuccessful");
}
}
// Show the success icon
this.successIcon.classList.add("visible");
setTimeout(() => {
this.successIcon.classList.remove("visible");
}, 1500);
} catch (error) {
console.error("Failed to copy text: ", error);
}
}
}
// Export for use in other modules
export default Microtypography;