clean-mla-gdoc 1.0.4 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -61
- package/dist/clean-mla-gdoc.js +205 -0
- package/dist/clean-mla-gdoc.min.js +1 -0
- package/package.json +26 -26
- package/dist/cleanMLAGDoc.js +0 -246
- package/dist/cleanMLAGDoc.min.js +0 -1
package/README.md
CHANGED
|
@@ -1,61 +1,55 @@
|
|
|
1
|
-
# cleanMLAGDoc.js v1.
|
|
2
|
-
|
|
3
|
-
A lightweight JavaScript library specifically designed to reformat **Google Docs HTML exports** into the **MLA (Modern Language Association) Style** layout. It handles physical pagination, alphabetical sorting of Works Cited, and provides a "DRAFT" watermark feature.
|
|
4
|
-
|
|
5
|
-
> [!IMPORTANT]
|
|
6
|
-
> **Compatibility Note:** This library is specifically tailored for the
|
|
7
|
-
|
|
8
|
-
## Features
|
|
9
|
-
* **Dynamic Tab Title**: Automatically renames the browser tab to `[LastName] [FirstName] [AssignmentName]`.
|
|
10
|
-
* **Auto-Pagination**: Breaks content into 8.5" x 11" pages with 1-inch margins.
|
|
11
|
-
* **MLA Header**: Processes the 4-line header and title from Google Docs classes.
|
|
12
|
-
* **Smart Date Mapping**: Converts numerical dates (e.g., `12/
|
|
13
|
-
* **Works Cited Sorting**: Automatically alphabetizes the "Works Cited" section.
|
|
14
|
-
* **Draft Mode**: Trigger a "DRAFT" watermark by adding `| draft` to your document header.
|
|
15
|
-
|
|
16
|
-
## How the Tab Title Works
|
|
17
|
-
The script automatically updates the browser tab title following a professional file-naming convention. It extracts the student's name from the first paragraph and the assignment title from the header. To maintain a professional look, the script **strips the `| draft` flag** from the tab title while retaining it for the watermark logic.
|
|
18
|
-
|
|
19
|
-
**Final Format:** `[LastName] [FirstName] [AssignmentName]`
|
|
20
|
-
|
|
21
|
-
## Usage & Formatting Rules
|
|
22
|
-
|
|
23
|
-
### Enabling Draft Mode
|
|
24
|
-
To enable the "DRAFT" watermark across all pages, include the string `| draft` (case-insensitive) in the document header.
|
|
25
|
-
* **Requirement**: The `| draft` flag **must be placed after** the assignment name.
|
|
26
|
-
* *Example:* `English Essay | draft`
|
|
27
|
-
|
|
28
|
-
### Works Cited
|
|
29
|
-
Any content following an element containing the text "Works Cited" will be moved to its own page and sorted alphabetically based on the first word of each entry.
|
|
30
|
-
|
|
31
|
-
## Installation
|
|
32
|
-
Include the minified script in your project. The library will automatically detect if jQuery is present; if not, it will load it along with other necessary assets from a CDN.
|
|
33
|
-
|
|
34
|
-
```html
|
|
35
|
-
<!-- Include the library
|
|
36
|
-
<script src="https://cdn.jsdelivr.net/npm/clean-mla-gdoc@v1.
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## License
|
|
57
|
-
This project is licensed under the **GNU General Public License v3.0**.
|
|
58
|
-
Full license text available at: [https://www.gnu.org](https://www.gnu.org)
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
**Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io>**
|
|
1
|
+
# cleanMLAGDoc.js v1.1.3
|
|
2
|
+
|
|
3
|
+
A lightweight JavaScript library specifically designed to reformat **Google Docs HTML exports** into the **MLA (Modern Language Association) Style** layout. It handles physical pagination, alphabetical sorting of Works Cited, and provides a "DRAFT" watermark feature.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Compatibility Note:** This library is specifically tailored for the **Report** preset generated by **Google Docs** when before exporting as HTML.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
* **Dynamic Tab Title**: Automatically renames the browser tab to `[LastName] [FirstName] [AssignmentName]`.
|
|
10
|
+
* **Auto-Pagination**: Breaks content into 8.5" x 11" pages with 1-inch margins.
|
|
11
|
+
* **MLA Header**: Processes the 4-line header and title from Google Docs classes.
|
|
12
|
+
* **Smart Date Mapping**: Converts numerical dates (e.g., `25/12/2026`) to MLA format (`25 Dec. 2026`).
|
|
13
|
+
* **Works Cited Sorting**: Automatically alphabetizes the "Works Cited" section.
|
|
14
|
+
* **Draft Mode**: Trigger a "DRAFT" watermark by adding `| draft` to your document header.
|
|
15
|
+
|
|
16
|
+
## How the Tab Title Works
|
|
17
|
+
The script automatically updates the browser tab title following a professional file-naming convention. It extracts the student's name from the first paragraph and the assignment title from the header. To maintain a professional look, the script **strips the `| draft` flag** from the tab title while retaining it for the watermark logic.
|
|
18
|
+
|
|
19
|
+
**Final Format:** `[LastName] [FirstName] [AssignmentName]`
|
|
20
|
+
|
|
21
|
+
## Usage & Formatting Rules
|
|
22
|
+
|
|
23
|
+
### Enabling Draft Mode
|
|
24
|
+
To enable the "DRAFT" watermark across all pages, include the string `| draft` (case-insensitive) in the document header.
|
|
25
|
+
* **Requirement**: The `| draft` flag **must be placed after** the assignment name.
|
|
26
|
+
* *Example:* `English Essay | draft`
|
|
27
|
+
|
|
28
|
+
### Works Cited
|
|
29
|
+
Any content following an element containing the text "Works Cited" will be moved to its own page and sorted alphabetically based on the first word of each entry.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
Include the minified script in your project. The library will automatically detect if jQuery is present; if not, it will load it along with other necessary assets from a CDN.
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!-- Include the library after the meta tag of your head tag -->
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/clean-mla-gdoc@v1.1.3/dist/clean-mla-gdoc.min.js"></script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
Contributions are welcome! If Google Docs updates its HTML export structure, please feel free to:
|
|
41
|
+
1. **Fork** the repository.
|
|
42
|
+
2. **Update** the selectors in the unminified `cleanMLAGDoc.js`.
|
|
43
|
+
3. Submit a **Pull Request** with a description of the changes.
|
|
44
|
+
|
|
45
|
+
## Dependencies
|
|
46
|
+
The library dynamically loads the following assets via [jsDelivr](https://www.jsdelivr.com) if they are not already present in the environment:
|
|
47
|
+
* [jQuery 3.7.1](https://cdn.jsdelivr.net/npm/jquery@3.7.1/)
|
|
48
|
+
* [Favicon.js 1.0.0](https://cdn.jsdelivr.net/npm/favicon.js@1.0.0/)
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
This project is licensed under the **GNU General Public License v3.0**.
|
|
52
|
+
Full license text available at: [https://www.gnu.org](https://www.gnu.org)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
**Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io>**
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* clean-mla-gdoc.js v1.1.3
|
|
3
|
+
* Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io>
|
|
4
|
+
* Licensed under the GNU General Public License v3.0
|
|
5
|
+
* Full license text: https://www.gnu.org
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function() {
|
|
9
|
+
// Configuration for external dependencies and the Google Docs CSS signature
|
|
10
|
+
const L = {
|
|
11
|
+
jqueryUrl: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js",
|
|
12
|
+
faviconUrl: "https://cdn.jsdelivr.net/npm/favicon.js@1.0.0/dist/favicon.min.js",
|
|
13
|
+
iconFrames: [
|
|
14
|
+
'https://img.icons8.com/?size=16&id=1395&format=png&color=FA5252',
|
|
15
|
+
'https://img.icons8.com/?size=16&id=1395&format=png&color=20C997',
|
|
16
|
+
'https://img.icons8.com/?size=16&id=1395&format=png&color=339AF0'
|
|
17
|
+
],
|
|
18
|
+
signatureCSS: "padding-top:0pt;text-indent:36pt;padding-bottom:0pt;line-height:2.0;orphans:2;widows:2;text-align:right"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Helper to load external scripts dynamically
|
|
22
|
+
const loadScript = (url, callback) => {
|
|
23
|
+
let script = document.createElement("script");
|
|
24
|
+
script.src = url;
|
|
25
|
+
script.onload = callback;
|
|
26
|
+
document.head.appendChild(script);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Main formatting logic
|
|
30
|
+
const initializeCleanMLAG = () => {
|
|
31
|
+
/**
|
|
32
|
+
* CSS INJECTION
|
|
33
|
+
*/
|
|
34
|
+
$('<style>').html(`
|
|
35
|
+
@page { size: 8.5in 11in; margin: 0; }
|
|
36
|
+
html { background: #d0d0d0 !important; width: 100% !important; }
|
|
37
|
+
body {
|
|
38
|
+
visibility: hidden;
|
|
39
|
+
margin: 0 !important;
|
|
40
|
+
padding: 40px 0 !important;
|
|
41
|
+
display: flex !important;
|
|
42
|
+
flex-direction: column !important;
|
|
43
|
+
align-items: center !important;
|
|
44
|
+
width: 100% !important;
|
|
45
|
+
max-width: none !important;
|
|
46
|
+
background: transparent !important;
|
|
47
|
+
font-family: "Times New Roman", serif !important;
|
|
48
|
+
line-height: 2.0 !important;
|
|
49
|
+
color: #000 !important;
|
|
50
|
+
}
|
|
51
|
+
.mla-page-container {
|
|
52
|
+
background: #fff !important;
|
|
53
|
+
box-shadow: 0 0 20px rgba(0,0,0,.2) !important;
|
|
54
|
+
width: 8.5in !important;
|
|
55
|
+
height: 11in !important;
|
|
56
|
+
margin: 0 auto 40px auto !important;
|
|
57
|
+
padding: 1in !important;
|
|
58
|
+
box-sizing: border-box !important;
|
|
59
|
+
position: relative;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
flex-shrink: 0;
|
|
62
|
+
}
|
|
63
|
+
.watermark::before {
|
|
64
|
+
content: 'DRAFT' !important;
|
|
65
|
+
position: absolute !important;
|
|
66
|
+
top: 50% !important;
|
|
67
|
+
left: 50% !important;
|
|
68
|
+
transform: translate(-50%, -50%) rotate(-45deg) !important;
|
|
69
|
+
font-size: 150px !important;
|
|
70
|
+
color: rgba(0,0,0,0.12) !important;
|
|
71
|
+
z-index: 99 !important;
|
|
72
|
+
pointer-events: none !important;
|
|
73
|
+
white-space: nowrap !important;
|
|
74
|
+
display: block !important;
|
|
75
|
+
}
|
|
76
|
+
.no-indent { text-indent: 0 !important; }
|
|
77
|
+
p { text-indent: .5in !important; margin: 0 !important; }
|
|
78
|
+
.header-wrapper {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: .5in;
|
|
81
|
+
right: 1in;
|
|
82
|
+
text-align: right !important;
|
|
83
|
+
text-indent: 0 !important;
|
|
84
|
+
width: 6.5in;
|
|
85
|
+
height: .5in;
|
|
86
|
+
line-height: .5in !important;
|
|
87
|
+
z-index: 10;
|
|
88
|
+
}
|
|
89
|
+
.hanging-indent { text-indent: -.5in !important; padding-left: .5in !important; }
|
|
90
|
+
.centered-title { text-align: center !important; text-indent: 0 !important; }
|
|
91
|
+
@media print {
|
|
92
|
+
html { background: #fff !important; }
|
|
93
|
+
body { padding: 0 !important; visibility: visible !important; }
|
|
94
|
+
.mla-page-container { margin: 0 auto !important; box-shadow: none !important; page-break-after: always !important; }
|
|
95
|
+
}
|
|
96
|
+
`).appendTo('head');
|
|
97
|
+
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
/**
|
|
100
|
+
* HEADER DETECTION VIA CSS SIGNATURE
|
|
101
|
+
*/
|
|
102
|
+
let dH = '';
|
|
103
|
+
const targetSigClean = L.signatureCSS.replace(/\s+/g, '');
|
|
104
|
+
|
|
105
|
+
$('style').each(function() {
|
|
106
|
+
const cssText = $(this).text().replace(/\s+/g, '');
|
|
107
|
+
// Find the class (e.g. .c5) matching the orphans/widows/text-align:right signature
|
|
108
|
+
const match = cssText.match(new RegExp(`\\.([^\\{]+)\\{[^\\}]*${targetSigClean.replace(/;/g, '[^\\}]*;')}`));
|
|
109
|
+
if (match && match[1]) {
|
|
110
|
+
dH = match[1];
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Use detected class or fallback to .c1
|
|
116
|
+
const sel = dH ? `.${dH}` : '.c1';
|
|
117
|
+
const h = $(sel).first(), ht = h.text().trim();
|
|
118
|
+
const isD = /\|\s*draft/i.test(ht);
|
|
119
|
+
const an = String(ht.replace(/\|\s*draft.*/i, '').trim() || "Assignment");
|
|
120
|
+
|
|
121
|
+
// Extract Name and metadata (exclude the detected header paragraph)
|
|
122
|
+
let pgs = $('p').filter(function() {
|
|
123
|
+
return $(this).text().trim().length > 0 && !$(this).hasClass(dH);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let sLine = pgs.first().text().trim(),
|
|
127
|
+
nParts = sLine.split(' '),
|
|
128
|
+
fName = String(nParts[0] || "First"),
|
|
129
|
+
lName = String(nParts[nParts.length - 1] || "Last");
|
|
130
|
+
|
|
131
|
+
document.title = lName + " " + fName + " " + an;
|
|
132
|
+
|
|
133
|
+
// Cleanup raw header elements
|
|
134
|
+
$(sel).remove();
|
|
135
|
+
$('*').filter(function() {
|
|
136
|
+
return ($(this).css('text-align') === 'right' || $(this).css('position') === 'absolute') && $(this).text().includes(lName);
|
|
137
|
+
}).remove();
|
|
138
|
+
|
|
139
|
+
let e = $('.doc-content > *,.c7 > *,body > *').not('script,style,.mla-page-container').get();
|
|
140
|
+
$('body').children().not('script,style').remove();
|
|
141
|
+
|
|
142
|
+
const pMaker = (v) => {
|
|
143
|
+
let g = $('<div class="mla-page-container"></div>');
|
|
144
|
+
if (isD) g.addClass('watermark');
|
|
145
|
+
g.append(`<div class="header-wrapper">${lName} ${v}</div>`);
|
|
146
|
+
return g
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let c = 1, u = pMaker(c), y = 0, x = 860, z = false, ph = false, pt = false, ci = [];
|
|
150
|
+
$('body').append(u);
|
|
151
|
+
|
|
152
|
+
const gm = (n) => ["", "Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."][parseInt(n)] || "",
|
|
153
|
+
dr = /^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2,4})$/,
|
|
154
|
+
re = (o) => {
|
|
155
|
+
let k = $('<div style="width:6.5in;position:absolute;visibility:hidden;line-height:2.0"></div>').append(o.clone()).appendTo('body'),
|
|
156
|
+
q = k.outerHeight(true);
|
|
157
|
+
k.remove();
|
|
158
|
+
if (y + q > x) {
|
|
159
|
+
c++; u = pMaker(c); $('body').append(u); y = 0
|
|
160
|
+
}
|
|
161
|
+
u.append(o); y += q
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
$(e).each(function() {
|
|
165
|
+
let o = $(this), v = o.text().trim();
|
|
166
|
+
if (v.length === 0 && !o.is('img,table')) return;
|
|
167
|
+
|
|
168
|
+
if (v.toLowerCase().includes("works cited") && !z) {
|
|
169
|
+
z = true; c++; u = pMaker(c); $('body').append(u); y = 0;
|
|
170
|
+
o.addClass('centered-title no-indent'); re(o); return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!z) {
|
|
174
|
+
if (!ph) {
|
|
175
|
+
let m = v.match(dr);
|
|
176
|
+
if (m) { o.text(m[1] + " " + gm(m[2]) + " " + m[3]); ph = true }
|
|
177
|
+
o.addClass('no-indent')
|
|
178
|
+
} else if (!pt) {
|
|
179
|
+
o.addClass('centered-title no-indent'); pt = true
|
|
180
|
+
} else {
|
|
181
|
+
let h = o.html();
|
|
182
|
+
if (h.includes(' ')) o.html(h.replace(/ /g, ' '))
|
|
183
|
+
}
|
|
184
|
+
re(o)
|
|
185
|
+
} else {
|
|
186
|
+
ci.push(o.addClass('hanging-indent no-indent'))
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (ci.length > 0) {
|
|
191
|
+
ci.sort((a, b) => a.text().trim().localeCompare(b.text().trim())).forEach(i => re(i))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$('body').css('visibility', 'visible');
|
|
195
|
+
|
|
196
|
+
// Execute Favicon animation
|
|
197
|
+
loadScript(L.faviconUrl, () => {
|
|
198
|
+
if (window.favicon || window.favico) (window.favicon || window.favico).animate(L.iconFrames, 1750);
|
|
199
|
+
});
|
|
200
|
+
}, 600);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Entry point
|
|
204
|
+
window.jQuery ? initializeCleanMLAG() : loadScript(L.jqueryUrl, initializeCleanMLAG);
|
|
205
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* clean-mla-gdoc.min.js v1.1.3 | Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io> | Licensed under the GNU General Public License v3.0 | Full license text: https://www.gnu.org */(function(){const L={j:"https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js",f:"https://cdn.jsdelivr.net/npm/favicon.js@1.0.0/dist/favicon.min.js",i:['https://img.icons8.com/?size=16&id=1395&format=png&color=FA5252','https://img.icons8.com/?size=16&id=1395&format=png&color=20C997','https://img.icons8.com/?size=16&id=1395&format=png&color=339AF0'],s:"padding-top:0pt;text-indent:36pt;padding-bottom:0pt;line-height:2.0;orphans:2;widows:2;text-align:right"};const l=(u,c)=>{let s=document.createElement("script");s.src=u;s.onload=c;document.head.appendChild(s)};const r=()=>{$('<style>').html(`@page{size:8.5in 11in;margin:0}html{background:#d0d0d0!important;width:100%!important}body{visibility:hidden;margin:0!important;padding:40px 0!important;display:flex!important;flex-direction:column!important;align-items:center!important;width:100%!important;max-width:none!important;background:transparent!important;font-family:"Times New Roman",serif!important;line-height:2.0!important;color:#000!important}.mla-page-container{background:#fff!important;box-shadow:0 0 20px rgba(0,0,0,.2)!important;width:8.5in!important;height:11in!important;margin:0 auto 40px auto!important;padding:1in!important;box-sizing:border-box!important;position:relative;overflow:hidden;flex-shrink:0}.watermark::before{content:'DRAFT'!important;position:absolute!important;top:50%!important;left:50%!important;transform:translate(-50%,-50%) rotate(-45deg)!important;font-size:150px!important;color:rgba(0,0,0,0.12)!important;z-index:99!important;pointer-events:none!important;white-space:nowrap!important;display:block!important}.no-indent{text-indent:0!important}p{text-indent:.5in!important;margin:0!important}.header-wrapper{position:absolute;top:.5in;right:1in;text-align:right!important;text-indent:0!important;width:6.5in;height:.5in;line-height:.5in!important;z-index:10}.hanging-indent{text-indent:-.5in!important;padding-left:.5in!important}.centered-title{text-align:center!important;text-indent:0!important}@media print{html{background:#fff!important}body{padding:0!important;visibility:visible!important}.mla-page-container{margin:0 auto!important;box-shadow:none!important;page-break-after:always!important}}`).appendTo('head');setTimeout(()=>{let dH='';const tC=L.s.replace(/\s+/g,'');$('style').each(function(){const cT=$(this).text().replace(/\s+/g,'');const m=cT.match(new RegExp(`\\.([^\\{]+)\\{[^\\}]*${tC.replace(/;/g,'[^\\}]*;')}`));if(m)dH=m[1]});const sel=dH?`.${dH}`:'.c1',h=$(sel).first(),ht=h.text().trim(),isD=/\|\s*draft/i.test(ht),an=String(ht.replace(/\|\s*draft.*/i,'').trim()||"Assignment");let pgs=$('p').filter(function(){return $(this).text().trim().length>0&&!$(this).hasClass(dH)});let sLine=pgs.first().text().trim(),n=sLine.split(' '),f=String(n[0]||"First"),ln=String(n[n.length-1]||"Last");document.title=`${ln} ${f} ${an}`;$(sel).remove();$('*').filter(function(){return($(this).css('text-align')==='right'||$(this).css('position')==='absolute')&&$(this).text().includes(ln)}).remove();let e=$('.doc-content > *,.c7 > *,body > *').not('script,style,.mla-page-container').get();$('body').children().not('script,style').remove();const pMaker=(v)=>{let g=$('<div class="mla-page-container"></div>');if(isD)g.addClass('watermark');g.append(`<div class="header-wrapper">${ln} ${v}</div>`);return g};let c=1,u=pMaker(c),y=0,x=860,z=false,ph=false,pt=false,ci=[];$('body').append(u);const gm=(n)=>["","Jan.","Feb.","Mar.","Apr.","May","June","July","Aug.","Sept.","Oct.","Nov.","Dec."][parseInt(n)]||"",dr=/^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2,4})$/,re=(o)=>{let k=$('<div style="width:6.5in;position:absolute;visibility:hidden;line-height:2.0"></div>').append(o.clone()).appendTo('body'),q=k.outerHeight(true);k.remove();if(y+q>x){c++;u=pMaker(c);$('body').append(u);y=0}u.append(o);y+=q};$(e).each(function(){let o=$(this),v=o.text().trim();if(v.length===0&&!o.is('img,table'))return;if(v.toLowerCase().includes("works cited")&&!z){z=true;c++;u=pMaker(c);$('body').append(u);y=0;o.addClass('centered-title no-indent');re(o);return}if(!z){if(!ph){let m=v.match(dr);if(m){o.text(`${m[1]} ${gm(m[2])} ${m[3]}`);ph=true}o.addClass('no-indent')}else if(!pt){o.addClass('centered-title no-indent');pt=true}else{let h=o.html();if(h.includes(' '))o.html(h.replace(/ /g,' '))}re(o)}else{ci.push(o.addClass('hanging-indent no-indent'))}});if(ci.length>0){ci.sort((a,b)=>a.text().trim().localeCompare(b.text().trim())).forEach(i=>re(i))}$('body').css('visibility','visible');l(L.f,()=>{if(window.favicon||window.favico)(window.favicon||window.favico).animate(L.i,1750)})},600)};window.jQuery?r():l(L.j,r)})();
|
package/package.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "clean-mla-gdoc",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A lightweight JavaScript library to reformat Google Docs HTML exports into MLA Style layout with auto-pagination and Works Cited sorting.",
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"browser": "dist/
|
|
7
|
-
"keywords": [
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
],
|
|
12
|
-
"homepage": "https://github.com/Musicalisk/clean-mla-gdoc#
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/Musicalisk/clean-mla-gdoc/issues"
|
|
15
|
-
},
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/Musicalisk/clean-mla-gdoc.git"
|
|
19
|
-
},
|
|
20
|
-
"license": "GPL-3.0",
|
|
21
|
-
"author": "
|
|
22
|
-
"type": "commonjs",
|
|
23
|
-
"scripts": {
|
|
24
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "clean-mla-gdoc",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "A lightweight JavaScript library to reformat Google Docs HTML exports into MLA Style layout with auto-pagination and Works Cited sorting.",
|
|
5
|
+
"main": "dist/clean-mla-gdoc.js",
|
|
6
|
+
"browser": "dist/clean-mla-gdoc.min.js",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mla",
|
|
9
|
+
"google-docs",
|
|
10
|
+
"academic"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/Musicalisk/clean-mla-gdoc#README",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Musicalisk/clean-mla-gdoc/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Musicalisk/clean-mla-gdoc.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "GPL-3.0",
|
|
21
|
+
"author": "Musicalisk <Musicalisk.travail@tuta.io>",
|
|
22
|
+
"type": "commonjs",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/cleanMLAGDoc.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* cleanMLAGDoc.js v1.0.4
|
|
3
|
-
* Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io>
|
|
4
|
-
* Licensed under the GNU General Public License v3.0
|
|
5
|
-
* Full license text: https://www.gnu.org
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
(function() {
|
|
9
|
-
// Configuration for external dependencies and assets
|
|
10
|
-
const CONFIG = {
|
|
11
|
-
jqueryUrl: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js",
|
|
12
|
-
faviconUrl: "https://cdn.jsdelivr.net/npm/favicon.js@1.0.0/dist/favicon.min.js",
|
|
13
|
-
iconFrames: [
|
|
14
|
-
'https://img.icons8.com/?size=16&id=1395&format=png&color=FA5252',
|
|
15
|
-
'https://img.icons8.com/?size=16&id=1395&format=png&color=20C997',
|
|
16
|
-
'https://img.icons8.com/?size=16&id=1395&format=png&color=339AF0'
|
|
17
|
-
]
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Helper to load external scripts dynamically
|
|
21
|
-
const loadScript = (url, callback) => {
|
|
22
|
-
let script = document.createElement("script");
|
|
23
|
-
script.src = url;
|
|
24
|
-
script.onload = callback;
|
|
25
|
-
document.head.appendChild(script);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Main formatting logic
|
|
29
|
-
const initializeCleanMLAG = () => {
|
|
30
|
-
/**
|
|
31
|
-
* CSS INJECTION
|
|
32
|
-
* Defines the physical 8.5x11 inch page structure and the "DRAFT" watermark.
|
|
33
|
-
*/
|
|
34
|
-
$('<style>').html(`
|
|
35
|
-
@page { size: 8.5in 11in; margin: 0; }
|
|
36
|
-
html { background: #d0d0d0 !important; width: 100% !important; }
|
|
37
|
-
body {
|
|
38
|
-
visibility: hidden;
|
|
39
|
-
margin: 0 !important;
|
|
40
|
-
padding: 40px 0 !important;
|
|
41
|
-
display: flex !important;
|
|
42
|
-
flex-direction: column !important;
|
|
43
|
-
align-items: center !important;
|
|
44
|
-
width: 100% !important;
|
|
45
|
-
max-width: none !important;
|
|
46
|
-
background: transparent !important;
|
|
47
|
-
font-family: "Times New Roman", serif !important;
|
|
48
|
-
line-height: 2.0 !important;
|
|
49
|
-
color: #000 !important;
|
|
50
|
-
}
|
|
51
|
-
.mla-page-container {
|
|
52
|
-
background: #fff !important;
|
|
53
|
-
box-shadow: 0 0 20px rgba(0,0,0,.2) !important;
|
|
54
|
-
width: 8.5in !important;
|
|
55
|
-
height: 11in !important;
|
|
56
|
-
margin: 0 auto 40px auto !important;
|
|
57
|
-
padding: 1in !important;
|
|
58
|
-
box-sizing: border-box !important;
|
|
59
|
-
position: relative;
|
|
60
|
-
overflow: hidden;
|
|
61
|
-
flex-shrink: 0;
|
|
62
|
-
}
|
|
63
|
-
.watermark::before {
|
|
64
|
-
content: 'DRAFT' !important;
|
|
65
|
-
position: absolute !important;
|
|
66
|
-
top: 50% !important;
|
|
67
|
-
left: 50% !important;
|
|
68
|
-
transform: translate(-50%, -50%) rotate(-45deg) !important;
|
|
69
|
-
font-size: 150px !important;
|
|
70
|
-
color: rgba(0,0,0,0.12) !important;
|
|
71
|
-
z-index: 99 !important;
|
|
72
|
-
pointer-events: none !important;
|
|
73
|
-
white-space: nowrap !important;
|
|
74
|
-
display: block !important;
|
|
75
|
-
}
|
|
76
|
-
.no-indent { text-indent: 0 !important; }
|
|
77
|
-
p { text-indent: .5in !important; margin: 0 !important; }
|
|
78
|
-
.header-wrapper {
|
|
79
|
-
position: absolute;
|
|
80
|
-
top: .5in;
|
|
81
|
-
right: 1in;
|
|
82
|
-
text-align: right !important;
|
|
83
|
-
text-indent: 0 !important;
|
|
84
|
-
width: 6.5in;
|
|
85
|
-
height: .5in;
|
|
86
|
-
line-height: .5in !important;
|
|
87
|
-
z-index: 10;
|
|
88
|
-
}
|
|
89
|
-
.hanging-indent { text-indent: -.5in !important; padding-left: .5in !important; }
|
|
90
|
-
.centered-title { text-align: center !important; text-indent: 0 !important; }
|
|
91
|
-
@media print {
|
|
92
|
-
html { background: #fff !important; }
|
|
93
|
-
body { padding: 0 !important; visibility: visible !important; }
|
|
94
|
-
.mla-page-container { margin: 0 auto !important; box-shadow: none !important; page-break-after: always !important; }
|
|
95
|
-
}
|
|
96
|
-
`).appendTo('head');
|
|
97
|
-
|
|
98
|
-
setTimeout(() => {
|
|
99
|
-
/**
|
|
100
|
-
* DRAFT & METADATA DETECTION
|
|
101
|
-
* Checks the header for the "| draft" string and extracts the assignment title.
|
|
102
|
-
*/
|
|
103
|
-
const headerElement = $('.c1').first();
|
|
104
|
-
const headerText = headerElement.text().trim();
|
|
105
|
-
const isDraft = /\|\s*draft/i.test(headerText);
|
|
106
|
-
const assignmentName = String(headerText.replace(/\|\s*draft.*/i, '').trim() || "Assignment");
|
|
107
|
-
|
|
108
|
-
// Extract Name and Page Content
|
|
109
|
-
let paragraphs = $('p').filter(function() {
|
|
110
|
-
return $(this).text().trim().length > 0 && !$(this).hasClass('c1');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
let firstLine = paragraphs.first().text().trim();
|
|
114
|
-
let nameParts = firstLine.split(' ');
|
|
115
|
-
let firstName = String(nameParts[0] || "First");
|
|
116
|
-
let lastName = String(nameParts[nameParts.length - 1] || "Last");
|
|
117
|
-
|
|
118
|
-
document.title = lastName + " " + firstName + " " + assignmentName;
|
|
119
|
-
|
|
120
|
-
// Cleanup: remove raw elements and duplicate headers
|
|
121
|
-
$('.c1').remove();
|
|
122
|
-
$('*').filter(function() {
|
|
123
|
-
return ($(this).css('text-align') === 'right' || $(this).css('position') === 'absolute') && $(this).text().includes(lastName);
|
|
124
|
-
}).remove();
|
|
125
|
-
|
|
126
|
-
let elementsToProcess = $('.doc-content > *,.c7 > *,body > *').not('script,style,.mla-page-container').get();
|
|
127
|
-
$('body').children().not('script,style').remove();
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* PAGE FACTORY
|
|
131
|
-
* Creates a new page and applies the "DRAFT" watermark if flagged.
|
|
132
|
-
*/
|
|
133
|
-
const createPage = (pageNum) => {
|
|
134
|
-
let page = $('<div class="mla-page-container"></div>');
|
|
135
|
-
if (isDraft) page.addClass('watermark');
|
|
136
|
-
page.append(`<div class="header-wrapper">${lastName} ${pageNum}</div>`);
|
|
137
|
-
return page;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Pagination state
|
|
141
|
-
let currentPageNum = 1;
|
|
142
|
-
let currentPage = createPage(currentPageNum);
|
|
143
|
-
let currentHeight = 0;
|
|
144
|
-
const maxHeight = 860; // Max vertical content height before page break
|
|
145
|
-
let worksCitedStarted = false;
|
|
146
|
-
let processedHeader = false;
|
|
147
|
-
let processedTitle = false;
|
|
148
|
-
let worksCitedItems = [];
|
|
149
|
-
|
|
150
|
-
$('body').append(currentPage);
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* DATE LOGIC
|
|
154
|
-
* Converts "MM/DD/YYYY" or "DD/MM/YYYY" styles to "DD Mon. YYYY".
|
|
155
|
-
*/
|
|
156
|
-
const getMonthName = (n) => ["", "Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."][parseInt(n)] || "";
|
|
157
|
-
const dateRegex = /^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2,4})$/;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* RENDERING ENGINE
|
|
161
|
-
* Measures element height in a hidden container to determine if it fits on the current page.
|
|
162
|
-
*/
|
|
163
|
-
const renderElement = (element) => {
|
|
164
|
-
let tempWrapper = $('<div style="width:6.5in;position:absolute;visibility:hidden;line-height:2.0"></div>')
|
|
165
|
-
.append(element.clone())
|
|
166
|
-
.appendTo('body');
|
|
167
|
-
let elementHeight = tempWrapper.outerHeight(true);
|
|
168
|
-
tempWrapper.remove();
|
|
169
|
-
|
|
170
|
-
if (currentHeight + elementHeight > maxHeight) {
|
|
171
|
-
currentPageNum++;
|
|
172
|
-
currentPage = createPage(currentPageNum);
|
|
173
|
-
$('body').append(currentPage);
|
|
174
|
-
currentHeight = 0;
|
|
175
|
-
}
|
|
176
|
-
currentPage.append(element);
|
|
177
|
-
currentHeight += elementHeight;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// Content processing loop
|
|
181
|
-
$(elementsToProcess).each(function() {
|
|
182
|
-
let $el = $(this);
|
|
183
|
-
let text = $el.text().trim();
|
|
184
|
-
|
|
185
|
-
if (text.length === 0 && !$el.is('img,table')) return;
|
|
186
|
-
|
|
187
|
-
// Detect start of Works Cited
|
|
188
|
-
if (text.toLowerCase().includes("works cited") && !worksCitedStarted) {
|
|
189
|
-
worksCitedStarted = true;
|
|
190
|
-
currentPageNum++;
|
|
191
|
-
currentPage = createPage(currentPageNum);
|
|
192
|
-
$('body').append(currentPage);
|
|
193
|
-
currentHeight = 0;
|
|
194
|
-
$el.addClass('centered-title no-indent');
|
|
195
|
-
renderElement($el);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (!worksCitedStarted) {
|
|
200
|
-
// Logic for first-page MLA header
|
|
201
|
-
if (!processedHeader) {
|
|
202
|
-
$el.addClass('no-indent');
|
|
203
|
-
let match = text.match(dateRegex);
|
|
204
|
-
if (match) {
|
|
205
|
-
// match[1] = Day, match[2] = Month, match[3] = Year
|
|
206
|
-
$el.text(match[1] + " " + getMonthName(match[2]) + " " + match[3]);
|
|
207
|
-
processedHeader = true;
|
|
208
|
-
}
|
|
209
|
-
} else if (!processedTitle) {
|
|
210
|
-
$el.addClass('centered-title no-indent');
|
|
211
|
-
processedTitle = true;
|
|
212
|
-
} else {
|
|
213
|
-
let html = $el.html();
|
|
214
|
-
if (html.includes(' ')) $el.html(html.replace(/ /g, ' '));
|
|
215
|
-
}
|
|
216
|
-
renderElement($el);
|
|
217
|
-
} else {
|
|
218
|
-
// Gather entries for alphabetical sorting
|
|
219
|
-
worksCitedItems.push($el.addClass('hanging-indent no-indent'));
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* WORKS CITED ALPHABETICAL SORT
|
|
225
|
-
* Sorts entries and renders them to the final pages.
|
|
226
|
-
*/
|
|
227
|
-
if (worksCitedItems.length > 0) {
|
|
228
|
-
worksCitedItems.sort((a, b) => a.text().trim().localeCompare(b.text().trim()))
|
|
229
|
-
.forEach(item => renderElement(item));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Reveal document
|
|
233
|
-
$('body').css('visibility', 'visible');
|
|
234
|
-
|
|
235
|
-
// Optional Favicon Animation
|
|
236
|
-
loadScript(CONFIG.faviconUrl, () => {
|
|
237
|
-
if (window.favicon || window.favico) {
|
|
238
|
-
(window.favicon || window.favico).animate(CONFIG.iconFrames, 1750);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
}, 600);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Entry point: ensure jQuery is loaded
|
|
245
|
-
window.jQuery ? initializeCleanMLAG() : loadScript(CONFIG.jqueryUrl, initializeCleanMLAG);
|
|
246
|
-
})();
|
package/dist/cleanMLAGDoc.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/* cleanMLAGDoc.min.js v1.0.4 | Copyright (c) 2026 Musicalisk <Musicalisk.travail@tuta.io> | Licensed under the GNU General Public License v3.0 | Full license text: https://www.gnu.org */(function(){const L={j:"https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js",f:"https://cdn.jsdelivr.net/npm/favicon.js@1.0.0/dist/favicon.min.js",i:['https://img.icons8.com/?size=16&id=1395&format=png&color=FA5252','https://img.icons8.com/?size=16&id=1395&format=png&color=20C997','https://img.icons8.com/?size=16&id=1395&format=png&color=339AF0']};const l=(u,c)=>{let s=document.createElement("script");s.src=u;s.onload=c;document.head.appendChild(s)};const r=()=>{$('<style>').html(`@page{size:8.5in 11in;margin:0}html{background:#d0d0d0!important;width:100%!important}body{visibility:hidden;margin:0!important;padding:40px 0!important;display:flex!important;flex-direction:column!important;align-items:center!important;width:100%!important;max-width:none!important;background:transparent!important;font-family:"Times New Roman",serif!important;line-height:2.0!important;color:#000!important}.mla-page-container{background:#fff!important;box-shadow:0 0 20px rgba(0,0,0,.2)!important;width:8.5in!important;height:11in!important;margin:0 auto 40px auto!important;padding:1in!important;box-sizing:border-box!important;position:relative;overflow:hidden;flex-shrink:0}.watermark::before{content:'DRAFT'!important;position:absolute!important;top:50%!important;left:50%!important;transform:translate(-50%,-50%) rotate(-45deg)!important;font-size:150px!important;color:rgba(0,0,0,0.12)!important;z-index:99!important;pointer-events:none!important;white-space:nowrap!important;display:block!important}.no-indent{text-indent:0!important}p{text-indent:.5in!important;margin:0!important}.header-wrapper{position:absolute;top:.5in;right:1in;text-align:right!important;text-indent:0!important;width:6.5in;height:.5in;line-height:.5in!important;z-index:10}.hanging-indent{text-indent:-.5in!important;padding-left:.5in!important}.centered-title{text-align:center!important;text-indent:0!important}@media print{html{background:#fff!important}body{padding:0!important;visibility:visible!important}.mla-page-container{margin:0 auto!important;box-shadow:none!important;page-break-after:always!important}}`).appendTo('head');setTimeout(()=>{const h=$('.c1').first(),ht=h.text().trim();const isD=/\|\s*draft/i.test(ht);const an=String(ht.replace(/\|\s*draft.*/i,'').trim()||"Assignment");let pgs=$('p').filter(function(){return $(this).text().trim().length>0&&!$(this).hasClass('c1')});let s=pgs.first().text().trim(),n=s.split(' '),f=String(n[0]||"First"),ln=String(n[n.length-1]||"Last");document.title=ln+" "+f+" "+an;$('.c1').remove();$('*').filter(function(){return($(this).css('text-align')==='right'||$(this).css('position')==='absolute')&&$(this).text().includes(ln)}).remove();let e=$('.doc-content > *,.c7 > *,body > *').not('script,style,.mla-page-container').get();$('body').children().not('script,style').remove();const p=(v)=>{let g=$('<div class="mla-page-container"></div>');if(isD)g.addClass('watermark');g.append(`<div class="header-wrapper">${ln} ${v}</div>`);return g};let c=1,u=p(c),y=0,x=860,z=false,ph=false,pt=false,ci=[];$('body').append(u);const gm=(n)=>["","Jan.","Feb.","Mar.","Apr.","May","June","July","Aug.","Sept.","Oct.","Nov.","Dec."][parseInt(n)]||"",dr=/^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2,4})$/,re=(o)=>{let k=$('<div style="width:6.5in;position:absolute;visibility:hidden;line-height:2.0"></div>').append(o.clone()).appendTo('body'),q=k.outerHeight(true);k.remove();if(y+q>x){c++;u=p(c);$('body').append(u);y=0}u.append(o);y+=q};$(e).each(function(j){let o=$(this),v=o.text().trim();if(v.length===0&&!o.is('img,table'))return;if(v.toLowerCase().includes("works cited")&&!z){z=true;c++;u=p(c);$('body').append(u);y=0;o.addClass('centered-title no-indent');re(o);return}if(!z){if(!ph){let m=v.match(dr);if(m){o.text(m[1]+" "+gm(m[2])+" "+m[3]);ph=true}o.addClass('no-indent')}else if(!pt){o.addClass('centered-title no-indent');pt=true}else{let h=o.html();if(h.includes(' '))o.html(h.replace(/ /g,' '))}re(o)}else{ci.push(o.addClass('hanging-indent no-indent'))}});if(ci.length>0){ci.sort((a,b)=>a.text().trim().localeCompare(b.text().trim())).forEach(i=>re(i))} $('body').css('visibility','visible');l(L.f,()=>{if(window.favicon||window.favico)(window.favicon||window.favico).animate(L.i,1750)})},600)};window.jQuery?r():l(L.j,r)})();
|