moracarta 2.1.0
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/LICENSE +21 -0
- package/README.md +84 -0
- package/index.html +31 -0
- package/package.json +35 -0
- package/public/assets/images/corner.webp +0 -0
- package/public/assets/images/custom-text-demo1.png +0 -0
- package/public/assets/images/custom-text-demo2.png +0 -0
- package/public/assets/images/demo-animation.gif +0 -0
- package/public/assets/images/demo-main-page.png +0 -0
- package/public/assets/images/googleDocsTabs.webp +0 -0
- package/public/assets/images/setup-demo.png +0 -0
- package/public/robots.txt +2 -0
- package/src/commands/add.js +170 -0
- package/src/commands/cliConfig.js +68 -0
- package/src/commands/deploy.js +41 -0
- package/src/commands/remove.js +198 -0
- package/src/commands/setup.js +257 -0
- package/src/config/fonts.js +7 -0
- package/src/data/letters.example.js +28 -0
- package/src/data/lettersLoader.js +7 -0
- package/src/i18n/de.js +8 -0
- package/src/i18n/en.js +8 -0
- package/src/i18n/es.js +8 -0
- package/src/i18n/fr.js +8 -0
- package/src/i18n/it.js +8 -0
- package/src/i18n/ja.js +8 -0
- package/src/i18n/ko.js +8 -0
- package/src/i18n/nl.js +0 -0
- package/src/i18n/pr.js +0 -0
- package/src/i18n/pt-BR.js +8 -0
- package/src/i18n/ru.js +8 -0
- package/src/i18n/tl.js +0 -0
- package/src/i18n/zh.js +8 -0
- package/src/scripts/data.variable.js +24 -0
- package/src/scripts/i18n.js +67 -0
- package/src/scripts/letters.script.js +257 -0
- package/src/scripts/main.script.js +101 -0
- package/src/services/googleDocs.js +76 -0
- package/src/styles/letters.css +405 -0
- package/src/styles/main.css +338 -0
- package/src/views/letters.view.html +83 -0
- package/vite.config.mjs +16 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
select,
|
|
3
|
+
input,
|
|
4
|
+
confirm
|
|
5
|
+
} from "@inquirer/prompts";
|
|
6
|
+
import { writeFile, mkdir, cp, readFile } from "node:fs/promises";
|
|
7
|
+
import { resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const PACKAGE_ROOT = resolve(__dirname, "..", "..");
|
|
13
|
+
|
|
14
|
+
/** TODO
|
|
15
|
+
const LANGUAGE = await select({
|
|
16
|
+
message: "What is the main language of the application?",
|
|
17
|
+
choices: [
|
|
18
|
+
{ name: "English", value: "en" },
|
|
19
|
+
{ name: "Português (Brasil)", value: "pt-BR" }
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
*/
|
|
23
|
+
const LANGUAGE = "en";
|
|
24
|
+
|
|
25
|
+
const PROJECT_NAME = await input({
|
|
26
|
+
message: "What should your project be called? (Cloudflare project name, do not use 'moracarta')",
|
|
27
|
+
default: "romantic-gift",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const TITLE = await input({
|
|
31
|
+
message: "Enter the title of the application:",
|
|
32
|
+
default: "Letters to my love"
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const FONT = await select({
|
|
36
|
+
message: "What font would you like to use?",
|
|
37
|
+
choices: [
|
|
38
|
+
{ name: "Handwritten", value: "handwritten" },
|
|
39
|
+
{ name: "Classic", value: "classic" },
|
|
40
|
+
{ name: "Modern", value: "modern" }
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const TOP_MESSAGES = {
|
|
45
|
+
top_1: "Every letter here holds a piece of everything I feel for you.",
|
|
46
|
+
top_2: "If my love could fit into words, these would just be the beginning.",
|
|
47
|
+
top_3: "Open slowly — every envelope has a little piece of me inside.",
|
|
48
|
+
top_4: "I didn't write letters. I wrote promises.",
|
|
49
|
+
top_5: "Made by hand, kept in my heart — for you."
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TOP_CHOICE = await select({
|
|
53
|
+
message: "Choose the message at the top of the main page:",
|
|
54
|
+
choices: [
|
|
55
|
+
{
|
|
56
|
+
name: "Insert custom text",
|
|
57
|
+
value: "custom"
|
|
58
|
+
},
|
|
59
|
+
...Object.entries(TOP_MESSAGES).map(([value, name]) => ({
|
|
60
|
+
name,
|
|
61
|
+
value
|
|
62
|
+
}))
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const TEXT_TOP = TOP_CHOICE === "custom"
|
|
67
|
+
? await input({
|
|
68
|
+
message: "Enter your custom message:"
|
|
69
|
+
})
|
|
70
|
+
: TOP_MESSAGES[TOP_CHOICE];
|
|
71
|
+
|
|
72
|
+
const BOTTOM_MESSAGES = {
|
|
73
|
+
bottom_1: "If I could choose again, I'd choose you in every version of my life. ♡",
|
|
74
|
+
bottom_2: "This is only a fraction of what I feel. I show you the rest every day. ♡",
|
|
75
|
+
bottom_3: "Thank you for being the reason for so many letters still to come. ♡",
|
|
76
|
+
bottom_4: "I love you in prose, in silence, and in every line I wrote here. ♡",
|
|
77
|
+
bottom_5: "End of the letters, not of the love. That one keeps going every day. ♡"
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const BOTTOM_CHOICE = await select({
|
|
81
|
+
message: "Choose the message at the bottom of the main page:",
|
|
82
|
+
choices: [
|
|
83
|
+
{
|
|
84
|
+
name: "Insert custom text",
|
|
85
|
+
value: "custom"
|
|
86
|
+
},
|
|
87
|
+
...Object.entries(BOTTOM_MESSAGES).map(([value, name]) => ({
|
|
88
|
+
name,
|
|
89
|
+
value
|
|
90
|
+
}))
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const TEXT_BOTTOM = BOTTOM_CHOICE === "custom"
|
|
95
|
+
? await input({
|
|
96
|
+
message: "Enter your custom message:"
|
|
97
|
+
})
|
|
98
|
+
: BOTTOM_MESSAGES[BOTTOM_CHOICE];
|
|
99
|
+
|
|
100
|
+
const FIRST_NAME = await input({
|
|
101
|
+
message: "Enter your first name: "
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const YOUR_NAME = "~ "+FIRST_NAME+" ♡";
|
|
105
|
+
|
|
106
|
+
const RECIPIENT_NAME = await input({
|
|
107
|
+
message: "What is their name?"
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const DEFAULT_CLOSED_LETTER_TEXT_TOP_LINE =
|
|
111
|
+
`❤️ To ${RECIPIENT_NAME} ❤️`;
|
|
112
|
+
|
|
113
|
+
const DEFAULT_CLOSED_LETTER_TEXT_BOTTOM_LINE =
|
|
114
|
+
"❤️ I love you ❤️";
|
|
115
|
+
|
|
116
|
+
const EDIT_ENVELOPE_TEXT = await confirm({
|
|
117
|
+
message: "Would you like to customize the envelope text?",
|
|
118
|
+
default: false
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let CLOSED_LETTER_TEXT_TOP_LINE = DEFAULT_CLOSED_LETTER_TEXT_TOP_LINE;
|
|
122
|
+
let CLOSED_LETTER_TEXT_BOTTOM_LINE = DEFAULT_CLOSED_LETTER_TEXT_BOTTOM_LINE;
|
|
123
|
+
|
|
124
|
+
if (EDIT_ENVELOPE_TEXT) {
|
|
125
|
+
CLOSED_LETTER_TEXT_TOP_LINE = await input({
|
|
126
|
+
message: "Top line of the closed envelope (press TAB to edit):",
|
|
127
|
+
default: DEFAULT_CLOSED_LETTER_TEXT_TOP_LINE
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
CLOSED_LETTER_TEXT_BOTTOM_LINE = await input({
|
|
131
|
+
message: "Bottom line of the closed envelope (press TAB to edit):",
|
|
132
|
+
default: DEFAULT_CLOSED_LETTER_TEXT_BOTTOM_LINE
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const config = `/**
|
|
137
|
+
* This file was generated automatically by Moracarta setup.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
const LANGUAGE = ${JSON.stringify(LANGUAGE)};
|
|
141
|
+
|
|
142
|
+
const PROJECT_NAME = ${JSON.stringify(PROJECT_NAME)};
|
|
143
|
+
const TITLE = ${JSON.stringify(TITLE)};
|
|
144
|
+
|
|
145
|
+
const FONT = ${JSON.stringify(FONT)};
|
|
146
|
+
|
|
147
|
+
const TEXT_TOP = ${JSON.stringify(TEXT_TOP)};
|
|
148
|
+
const TEXT_BOTTOM = ${JSON.stringify(TEXT_BOTTOM)};
|
|
149
|
+
const YOUR_NAME = ${JSON.stringify(YOUR_NAME)};
|
|
150
|
+
|
|
151
|
+
const CLOSED_LETTER_TEXT_TOP_LINE =
|
|
152
|
+
${JSON.stringify(CLOSED_LETTER_TEXT_TOP_LINE)};
|
|
153
|
+
|
|
154
|
+
const CLOSED_LETTER_TEXT_BOTTOM_LINE =
|
|
155
|
+
${JSON.stringify(CLOSED_LETTER_TEXT_BOTTOM_LINE)};
|
|
156
|
+
|
|
157
|
+
const globalVariables = {
|
|
158
|
+
LANGUAGE,
|
|
159
|
+
PROJECT_NAME,
|
|
160
|
+
TITLE,
|
|
161
|
+
YOUR_NAME,
|
|
162
|
+
FONT,
|
|
163
|
+
TEXT_TOP,
|
|
164
|
+
TEXT_BOTTOM,
|
|
165
|
+
CLOSED_LETTER_TEXT_TOP_LINE,
|
|
166
|
+
CLOSED_LETTER_TEXT_BOTTOM_LINE
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export default globalVariables;
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
const configPath = resolve(
|
|
173
|
+
process.cwd(),
|
|
174
|
+
"src",
|
|
175
|
+
"config",
|
|
176
|
+
"globalVariables.js"
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
180
|
+
await writeFile(configPath, config, "utf8");
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Files that make up the actual site. These get copied out of the
|
|
184
|
+
* installed package and into the user's own project so `dev`/`build`
|
|
185
|
+
* run against a real local copy — never against node_modules.
|
|
186
|
+
*/
|
|
187
|
+
const TEMPLATE_ENTRIES = [
|
|
188
|
+
"index.html",
|
|
189
|
+
"vite.config.mjs",
|
|
190
|
+
"public",
|
|
191
|
+
"src/config/fonts.js",
|
|
192
|
+
"src/data/letters.example.js",
|
|
193
|
+
"src/data/lettersLoader.js",
|
|
194
|
+
"src/i18n",
|
|
195
|
+
"src/scripts",
|
|
196
|
+
"src/services",
|
|
197
|
+
"src/styles",
|
|
198
|
+
"src/views"
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
async function copyTemplateFiles() {
|
|
202
|
+
// Running the CLI from inside the moracarta source repo itself
|
|
203
|
+
// (e.g. via `npm run setup` while developing moracarta) — the
|
|
204
|
+
// "package" and the "project" are the same folder, nothing to copy.
|
|
205
|
+
if (resolve(PACKAGE_ROOT) === resolve(process.cwd())) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log("\nCopying project files...");
|
|
210
|
+
|
|
211
|
+
for (const entry of TEMPLATE_ENTRIES) {
|
|
212
|
+
const source = resolve(PACKAGE_ROOT, entry);
|
|
213
|
+
const destination = resolve(process.cwd(), entry);
|
|
214
|
+
|
|
215
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
216
|
+
await cp(source, destination, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log("✓ Project files copied.");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function ensureModuleType() {
|
|
223
|
+
const userPackageJsonPath = resolve(process.cwd(), "package.json");
|
|
224
|
+
|
|
225
|
+
let userPackageJson;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
userPackageJson = JSON.parse(
|
|
229
|
+
await readFile(userPackageJsonPath, "utf8")
|
|
230
|
+
);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error.code === "ENOENT") {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (userPackageJson.type === "module") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
userPackageJson.type = "module";
|
|
244
|
+
|
|
245
|
+
await writeFile(
|
|
246
|
+
userPackageJsonPath,
|
|
247
|
+
JSON.stringify(userPackageJson, null, 2) + "\n",
|
|
248
|
+
"utf8"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await copyTemplateFiles();
|
|
253
|
+
await ensureModuleType();
|
|
254
|
+
|
|
255
|
+
console.log("\n❤️ Moracarta setup complete!");
|
|
256
|
+
console.log(`Configuration saved to: ${configPath}`);
|
|
257
|
+
console.log("Run moracarta dev to test the application");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const letters = [
|
|
2
|
+
{
|
|
3
|
+
id: 1,
|
|
4
|
+
date: 'Aug 28, 2026',
|
|
5
|
+
title: 'Custom Letter Title 1',
|
|
6
|
+
content: `My Dearest Mary Jane,
|
|
7
|
+
As I sit down to write these words, I am thinking of you and how grateful I am for everything we experience together. Life moves quickly, but whenever I pause to reflect on what brings me the most joy, you are always at the top of my mind.
|
|
8
|
+
You bring so much inspiration and comfort into my life. Whether we are sharing long conversations or simply enjoying quiet moments together, being with you feels natural and right. Your support and kindness mean more to me than I can easily put into words.
|
|
9
|
+
I wanted to remind you today of how deeply appreciated and loved you are. No matter where life takes us, know that you hold a very special place in my heart.
|
|
10
|
+
|
|
11
|
+
Yours always,
|
|
12
|
+
Peter Parker`
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 2,
|
|
16
|
+
date: 'Aug 28, 2026',
|
|
17
|
+
title: 'Custom Letter Title 2',
|
|
18
|
+
content: `Dearest Mary Jane,
|
|
19
|
+
|
|
20
|
+
I hope this letter finds you well. I wanted to take a quiet moment today to write to you and express just how much you mean to me. Even in the midst of everyday life, my thoughts frequently return to you and the wonderful moments we share.
|
|
21
|
+
Your presence brings warmth, kindness, and light into my world. Every memory with you is something I truly cherish, and I am constantly reminded of how fortunate I am to have you in my life. You have a way of making ordinary days feel special just by being yourself.
|
|
22
|
+
Thank you for your affection, your understanding, and all the happiness you bring into my life. I look forward to creating many more beautiful memories together in the time ahead.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
With all my love,
|
|
26
|
+
Peter Parker`
|
|
27
|
+
}
|
|
28
|
+
];
|
package/src/i18n/de.js
ADDED
package/src/i18n/en.js
ADDED
package/src/i18n/es.js
ADDED
package/src/i18n/fr.js
ADDED
package/src/i18n/it.js
ADDED
package/src/i18n/ja.js
ADDED
package/src/i18n/ko.js
ADDED
package/src/i18n/nl.js
ADDED
|
File without changes
|
package/src/i18n/pr.js
ADDED
|
File without changes
|
package/src/i18n/ru.js
ADDED
package/src/i18n/tl.js
ADDED
|
File without changes
|
package/src/i18n/zh.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import globalVariables from '../config/globalVariables.js';
|
|
2
|
+
import fonts from '../config/fonts.js';
|
|
3
|
+
|
|
4
|
+
document.documentElement.style.setProperty(
|
|
5
|
+
'--main-font',
|
|
6
|
+
fonts[globalVariables.FONT] || fonts.handwritten
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
// Data variables are injected from `src/config/globalVariables.js` into
|
|
10
|
+
// HTML elements using the `data-variable` attribute.
|
|
11
|
+
// HTML entities are preserved when the value contains markup characters.
|
|
12
|
+
document.querySelectorAll("[data-variable]").forEach(element => {
|
|
13
|
+
const value = globalVariables[element.dataset.variable];
|
|
14
|
+
|
|
15
|
+
if (typeof value === 'string' && (value.includes('&') || value.includes('<') || value.includes('>'))) {
|
|
16
|
+
element.innerHTML = value;
|
|
17
|
+
} else {
|
|
18
|
+
element.textContent = value;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
document.querySelectorAll("[data-variable-src]").forEach(element => {
|
|
23
|
+
element.src = globalVariables[element.dataset.variableSrc];
|
|
24
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import globalVariables from "../config/globalVariables.js";
|
|
2
|
+
|
|
3
|
+
import en from "../i18n/en.js";
|
|
4
|
+
import ptbr from "../i18n/pt-BR.js";
|
|
5
|
+
import es from "../i18n/es.js";
|
|
6
|
+
import fr from "../i18n/fr.js";
|
|
7
|
+
import de from "../i18n/de.js";
|
|
8
|
+
import it from "../i18n/it.js";
|
|
9
|
+
import ja from "../i18n/ja.js";
|
|
10
|
+
import ko from "../i18n/ko.js";
|
|
11
|
+
import zh from "../i18n/zh.js";
|
|
12
|
+
import ru from "../i18n/ru.js";
|
|
13
|
+
|
|
14
|
+
// Language files are selected by the configured LANGUAGE value.
|
|
15
|
+
// Missing translation keys fall back to English, then to the key name.
|
|
16
|
+
const translations = {
|
|
17
|
+
en,
|
|
18
|
+
"pt-BR": ptbr,
|
|
19
|
+
es,
|
|
20
|
+
fr,
|
|
21
|
+
de,
|
|
22
|
+
it,
|
|
23
|
+
ja,
|
|
24
|
+
ko,
|
|
25
|
+
zh,
|
|
26
|
+
ru
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getCurrentLanguage = () => globalVariables?.LANGUAGE || "en";
|
|
30
|
+
|
|
31
|
+
export const getTranslation = (key, ...args) => {
|
|
32
|
+
const language = translations[getCurrentLanguage()] || translations.en;
|
|
33
|
+
|
|
34
|
+
const translation =
|
|
35
|
+
language?.[key] ??
|
|
36
|
+
translations.en?.[key] ??
|
|
37
|
+
key;
|
|
38
|
+
|
|
39
|
+
// Support translated functions such as PROGRESS(current, total)
|
|
40
|
+
return typeof translation === "function"
|
|
41
|
+
? translation(...args)
|
|
42
|
+
: translation;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const translateElement = (element) => {
|
|
46
|
+
const key = element.dataset.i18n;
|
|
47
|
+
|
|
48
|
+
if (key) {
|
|
49
|
+
element.textContent = getTranslation(key);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
document
|
|
54
|
+
.querySelectorAll("[data-i18n]")
|
|
55
|
+
.forEach(translateElement);
|
|
56
|
+
|
|
57
|
+
export const refreshTranslations = () => {
|
|
58
|
+
document
|
|
59
|
+
.querySelectorAll("[data-i18n]")
|
|
60
|
+
.forEach(translateElement);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
window.i18n = {
|
|
64
|
+
getTranslation,
|
|
65
|
+
translateElement,
|
|
66
|
+
refreshTranslations
|
|
67
|
+
};
|