momer 1.0.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/README.md +76 -0
- package/bin/momer.js +19 -0
- package/package.json +40 -0
- package/src/app.tsx +75 -0
- package/src/components/ChatView.tsx +283 -0
- package/src/components/ConversationList.tsx +211 -0
- package/src/components/ImageMessage.tsx +57 -0
- package/src/components/NewMessage.tsx +137 -0
- package/src/components/Onboarding.tsx +185 -0
- package/src/config.js +147 -0
- package/src/db.js +341 -0
- package/src/formatter.js +95 -0
- package/src/imageRenderer.js +129 -0
- package/src/index.tsx +187 -0
- package/src/sender.js +178 -0
- package/src/watcher.js +62 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { renderImage, isImagePath } from '../imageRenderer.js';
|
|
4
|
+
|
|
5
|
+
export default function ImageMessage({ path, maxWidth = 25 }) {
|
|
6
|
+
const [imageLines, setImageLines] = useState(null);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(false);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let mounted = true;
|
|
12
|
+
|
|
13
|
+
async function loadImage() {
|
|
14
|
+
if (!path || !isImagePath(path)) {
|
|
15
|
+
setLoading(false);
|
|
16
|
+
setError(true);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const result = await renderImage(path, maxWidth, 10);
|
|
22
|
+
if (mounted) {
|
|
23
|
+
if (result) {
|
|
24
|
+
setImageLines(result.lines);
|
|
25
|
+
} else {
|
|
26
|
+
setError(true);
|
|
27
|
+
}
|
|
28
|
+
setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
if (mounted) {
|
|
32
|
+
setError(true);
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
loadImage();
|
|
39
|
+
return () => { mounted = false; };
|
|
40
|
+
}, [path, maxWidth]);
|
|
41
|
+
|
|
42
|
+
if (loading) {
|
|
43
|
+
return <Text dimColor>loading image...</Text>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (error || !imageLines) {
|
|
47
|
+
return <Text dimColor>π· [image]</Text>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Box flexDirection="column">
|
|
52
|
+
{imageLines.map((line, i) => (
|
|
53
|
+
<Text key={i}>{line}</Text>
|
|
54
|
+
))}
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import { sendMessage } from '../sender.js';
|
|
5
|
+
import { findChat } from '../db.js';
|
|
6
|
+
import { getTheme, isCuteMode } from '../config.js';
|
|
7
|
+
|
|
8
|
+
export default function NewMessage({ onBack, onSent }) {
|
|
9
|
+
const [step, setStep] = useState('contact');
|
|
10
|
+
const [contact, setContact] = useState('');
|
|
11
|
+
const [message, setMessage] = useState('');
|
|
12
|
+
const [sending, setSending] = useState(false);
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
|
|
15
|
+
const theme = getTheme();
|
|
16
|
+
const cute = isCuteMode();
|
|
17
|
+
|
|
18
|
+
useInput((input, key) => {
|
|
19
|
+
if (key.escape) {
|
|
20
|
+
if (step === 'message') {
|
|
21
|
+
setStep('contact');
|
|
22
|
+
setError(null);
|
|
23
|
+
} else {
|
|
24
|
+
onBack();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const handleContactSubmit = (value) => {
|
|
30
|
+
if (!value.trim()) return;
|
|
31
|
+
setContact(value.trim());
|
|
32
|
+
setStep('message');
|
|
33
|
+
setError(null);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleMessageSubmit = async (value) => {
|
|
37
|
+
if (!value.trim() || sending) return;
|
|
38
|
+
|
|
39
|
+
setSending(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
|
|
42
|
+
const result = sendMessage(contact, value.trim());
|
|
43
|
+
|
|
44
|
+
if (result.success) {
|
|
45
|
+
const existingChat = findChat(contact);
|
|
46
|
+
const contactObj = existingChat || {
|
|
47
|
+
chatId: null,
|
|
48
|
+
identifier: contact,
|
|
49
|
+
displayName: contact,
|
|
50
|
+
contactId: contact
|
|
51
|
+
};
|
|
52
|
+
onSent(contactObj);
|
|
53
|
+
} else {
|
|
54
|
+
setError(result.error);
|
|
55
|
+
setSending(false);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Box flexDirection="column">
|
|
61
|
+
{/* Decorative top */}
|
|
62
|
+
{cute && theme.decorTop && (
|
|
63
|
+
<Box justifyContent="center">
|
|
64
|
+
<Text color={theme.primary}>{theme.decorTop}</Text>
|
|
65
|
+
</Box>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{/* Header */}
|
|
69
|
+
<Box borderStyle="round" borderColor={theme.headerBorder} paddingX={1}>
|
|
70
|
+
<Text bold color={theme.primary}>
|
|
71
|
+
{cute ? 'π new message β¨' : 'New Message'}
|
|
72
|
+
</Text>
|
|
73
|
+
</Box>
|
|
74
|
+
|
|
75
|
+
{/* Content */}
|
|
76
|
+
<Box
|
|
77
|
+
flexDirection="column"
|
|
78
|
+
borderStyle="round"
|
|
79
|
+
borderColor={theme.border}
|
|
80
|
+
paddingX={1}
|
|
81
|
+
>
|
|
82
|
+
{step === 'contact' ? (
|
|
83
|
+
<Box flexDirection="column">
|
|
84
|
+
<Text>{cute ? 'π who do you wanna text?' : 'Enter phone number or email:'}</Text>
|
|
85
|
+
<Box>
|
|
86
|
+
<Text color={theme.secondary}>{cute ? 'π To: ' : 'To: '}</Text>
|
|
87
|
+
<TextInput
|
|
88
|
+
value={contact}
|
|
89
|
+
onChange={setContact}
|
|
90
|
+
onSubmit={handleContactSubmit}
|
|
91
|
+
placeholder={cute ? '+1234567890 or bestie@email.com' : '+1234567890 or email@example.com'}
|
|
92
|
+
/>
|
|
93
|
+
</Box>
|
|
94
|
+
<Text dimColor>
|
|
95
|
+
{cute
|
|
96
|
+
? 'β¨ tip: use full phone number with country code!'
|
|
97
|
+
: 'Tip: Use full phone number with country code (e.g., +1 for US)'
|
|
98
|
+
}
|
|
99
|
+
</Text>
|
|
100
|
+
</Box>
|
|
101
|
+
) : (
|
|
102
|
+
<Box flexDirection="column">
|
|
103
|
+
<Box>
|
|
104
|
+
<Text dimColor>{cute ? 'π To: ' : 'To: '}</Text>
|
|
105
|
+
<Text bold color={theme.secondary}>{contact}</Text>
|
|
106
|
+
</Box>
|
|
107
|
+
<Box>
|
|
108
|
+
<Text color={theme.secondary}>
|
|
109
|
+
{sending ? (cute ? 'π« ' : 'β³ ') : (cute ? `${theme.inputPrefix} ` : 'βΈ ')}
|
|
110
|
+
</Text>
|
|
111
|
+
<TextInput
|
|
112
|
+
value={message}
|
|
113
|
+
onChange={setMessage}
|
|
114
|
+
onSubmit={handleMessageSubmit}
|
|
115
|
+
placeholder={sending ? theme.sending : (cute ? 'say something sweet...' : 'Type your message...')}
|
|
116
|
+
/>
|
|
117
|
+
</Box>
|
|
118
|
+
</Box>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{/* Error display */}
|
|
122
|
+
{error && (
|
|
123
|
+
<Text color="red">{cute ? 'π ' : 'β '}{error}</Text>
|
|
124
|
+
)}
|
|
125
|
+
</Box>
|
|
126
|
+
|
|
127
|
+
{/* Status Bar */}
|
|
128
|
+
<Box borderStyle="round" borderColor={theme.border} paddingX={1}>
|
|
129
|
+
{cute && <Text color={theme.primary}>{theme.decorBottom} </Text>}
|
|
130
|
+
<Text dimColor>
|
|
131
|
+
<Text color={theme.secondary}>{theme.back}</Text> {step === 'contact' ? 'cancel' : 'back'}
|
|
132
|
+
<Text color={theme.secondary}> {theme.send}</Text> {step === 'contact' ? 'next' : 'send'}
|
|
133
|
+
</Text>
|
|
134
|
+
</Box>
|
|
135
|
+
</Box>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { saveConfig, completeOnboarding } from '../config.js';
|
|
4
|
+
import { checkDatabaseAccess } from '../db.js';
|
|
5
|
+
|
|
6
|
+
export default function Onboarding({ onComplete }) {
|
|
7
|
+
const [step, setStep] = useState('checking'); // 'checking', 'no_access', 0, 1, 2, 3
|
|
8
|
+
const [theme, setTheme] = useState('cute');
|
|
9
|
+
const [viewMode, setViewMode] = useState('compact');
|
|
10
|
+
const [accessError, setAccessError] = useState(null);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const result = checkDatabaseAccess();
|
|
14
|
+
if (result.success) {
|
|
15
|
+
setStep(0);
|
|
16
|
+
} else {
|
|
17
|
+
setAccessError(result);
|
|
18
|
+
setStep('no_access');
|
|
19
|
+
}
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (step === 'no_access') {
|
|
24
|
+
// Retry check on enter
|
|
25
|
+
if (key.return) {
|
|
26
|
+
setStep('checking');
|
|
27
|
+
const result = checkDatabaseAccess();
|
|
28
|
+
if (result.success) {
|
|
29
|
+
setStep(0);
|
|
30
|
+
} else {
|
|
31
|
+
setAccessError(result);
|
|
32
|
+
setStep('no_access');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else if (step === 0) {
|
|
36
|
+
// Welcome screen
|
|
37
|
+
if (key.return) {
|
|
38
|
+
setStep(1);
|
|
39
|
+
}
|
|
40
|
+
} else if (step === 1) {
|
|
41
|
+
// Theme selection
|
|
42
|
+
if (key.leftArrow || input === '1') {
|
|
43
|
+
setTheme('cute');
|
|
44
|
+
} else if (key.rightArrow || input === '2') {
|
|
45
|
+
setTheme('minimal');
|
|
46
|
+
} else if (key.return) {
|
|
47
|
+
setStep(2);
|
|
48
|
+
}
|
|
49
|
+
} else if (step === 2) {
|
|
50
|
+
// View mode selection
|
|
51
|
+
if (key.leftArrow || input === '1') {
|
|
52
|
+
setViewMode('compact');
|
|
53
|
+
} else if (key.rightArrow || input === '2') {
|
|
54
|
+
setViewMode('spaced');
|
|
55
|
+
} else if (key.return) {
|
|
56
|
+
setStep(3);
|
|
57
|
+
}
|
|
58
|
+
} else if (step === 3) {
|
|
59
|
+
// Final screen
|
|
60
|
+
if (key.return) {
|
|
61
|
+
saveConfig({ theme, viewMode });
|
|
62
|
+
completeOnboarding();
|
|
63
|
+
onComplete();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Box flexDirection="column" alignItems="center" justifyContent="center">
|
|
70
|
+
<Box marginBottom={1}>
|
|
71
|
+
<Text color="magenta">qο½₯:*:ο½₯οΎβ
,qο½₯:*:ο½₯οΎβ</Text>
|
|
72
|
+
</Box>
|
|
73
|
+
|
|
74
|
+
{step === 'checking' && (
|
|
75
|
+
<Box flexDirection="column" alignItems="center">
|
|
76
|
+
<Text color="magenta">checking permissions...</Text>
|
|
77
|
+
</Box>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{step === 'no_access' && (
|
|
81
|
+
<Box flexDirection="column" alignItems="center">
|
|
82
|
+
<Text bold color="red">π can't access messages</Text>
|
|
83
|
+
<Text> </Text>
|
|
84
|
+
{accessError?.error === 'no_permission' ? (
|
|
85
|
+
<>
|
|
86
|
+
<Text>momer needs Full Disk Access to read your messages</Text>
|
|
87
|
+
<Text> </Text>
|
|
88
|
+
<Text bold color="cyan">how to fix:</Text>
|
|
89
|
+
<Text> </Text>
|
|
90
|
+
<Text>1. Open <Text bold>System Settings</Text></Text>
|
|
91
|
+
<Text>2. Go to <Text bold>Privacy & Security</Text></Text>
|
|
92
|
+
<Text>3. Click <Text bold>Full Disk Access</Text></Text>
|
|
93
|
+
<Text>4. Enable your terminal app:</Text>
|
|
94
|
+
<Text dimColor> (Terminal, iTerm2, Warp, etc.)</Text>
|
|
95
|
+
<Text>5. Restart your terminal</Text>
|
|
96
|
+
</>
|
|
97
|
+
) : (
|
|
98
|
+
<>
|
|
99
|
+
<Text>{accessError?.message}</Text>
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
<Text> </Text>
|
|
103
|
+
<Text dimColor>press enter to retry</Text>
|
|
104
|
+
</Box>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{step === 0 && (
|
|
108
|
+
<Box flexDirection="column" alignItems="center">
|
|
109
|
+
<Text bold color="magenta">β¨ welcome to momer β¨</Text>
|
|
110
|
+
<Text> </Text>
|
|
111
|
+
<Text>your cute iMessage terminal client</Text>
|
|
112
|
+
<Text> </Text>
|
|
113
|
+
<Text dimColor>press enter to set up</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{step === 1 && (
|
|
118
|
+
<Box flexDirection="column" alignItems="center">
|
|
119
|
+
<Text bold color="magenta">pick your vibe</Text>
|
|
120
|
+
<Text> </Text>
|
|
121
|
+
<Box>
|
|
122
|
+
<Box
|
|
123
|
+
borderStyle={theme === 'cute' ? 'double' : 'single'}
|
|
124
|
+
borderColor={theme === 'cute' ? 'magenta' : 'gray'}
|
|
125
|
+
paddingX={2}
|
|
126
|
+
marginRight={2}
|
|
127
|
+
>
|
|
128
|
+
<Text color={theme === 'cute' ? 'magenta' : 'white'}>β¨ cute β¨</Text>
|
|
129
|
+
</Box>
|
|
130
|
+
<Box
|
|
131
|
+
borderStyle={theme === 'minimal' ? 'double' : 'single'}
|
|
132
|
+
borderColor={theme === 'minimal' ? 'blue' : 'gray'}
|
|
133
|
+
paddingX={2}
|
|
134
|
+
>
|
|
135
|
+
<Text color={theme === 'minimal' ? 'blue' : 'white'}>minimal</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
</Box>
|
|
138
|
+
<Text> </Text>
|
|
139
|
+
<Text dimColor>β β to choose, enter to continue</Text>
|
|
140
|
+
</Box>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{step === 2 && (
|
|
144
|
+
<Box flexDirection="column" alignItems="center">
|
|
145
|
+
<Text bold color="magenta">message style</Text>
|
|
146
|
+
<Text> </Text>
|
|
147
|
+
<Box>
|
|
148
|
+
<Box
|
|
149
|
+
borderStyle={viewMode === 'compact' ? 'double' : 'single'}
|
|
150
|
+
borderColor={viewMode === 'compact' ? 'magenta' : 'gray'}
|
|
151
|
+
paddingX={2}
|
|
152
|
+
marginRight={2}
|
|
153
|
+
>
|
|
154
|
+
<Text color={viewMode === 'compact' ? 'magenta' : 'white'}>compact</Text>
|
|
155
|
+
</Box>
|
|
156
|
+
<Box
|
|
157
|
+
borderStyle={viewMode === 'spaced' ? 'double' : 'single'}
|
|
158
|
+
borderColor={viewMode === 'spaced' ? 'magenta' : 'gray'}
|
|
159
|
+
paddingX={2}
|
|
160
|
+
>
|
|
161
|
+
<Text color={viewMode === 'spaced' ? 'magenta' : 'white'}>spaced</Text>
|
|
162
|
+
</Box>
|
|
163
|
+
</Box>
|
|
164
|
+
<Text> </Text>
|
|
165
|
+
<Text dimColor>β β to choose, enter to continue</Text>
|
|
166
|
+
</Box>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{step === 3 && (
|
|
170
|
+
<Box flexDirection="column" alignItems="center">
|
|
171
|
+
<Text bold color="magenta">you're all set! π</Text>
|
|
172
|
+
<Text> </Text>
|
|
173
|
+
<Text>theme: <Text color="cyan">{theme}</Text></Text>
|
|
174
|
+
<Text>view: <Text color="cyan">{viewMode}</Text></Text>
|
|
175
|
+
<Text> </Text>
|
|
176
|
+
<Text dimColor>press enter to start</Text>
|
|
177
|
+
</Box>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<Box marginTop={1}>
|
|
181
|
+
<Text color="magenta">βοΎο½₯:*:ο½₯q momer β
οΎο½₯:*:ο½₯q</Text>
|
|
182
|
+
</Box>
|
|
183
|
+
</Box>
|
|
184
|
+
);
|
|
185
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.config', 'momer');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
theme: 'cute', // 'cute' or 'minimal'
|
|
10
|
+
viewMode: 'compact', // 'compact' or 'spaced'
|
|
11
|
+
showEmojis: true,
|
|
12
|
+
sparkles: true,
|
|
13
|
+
refreshInterval: 2000
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let config = null;
|
|
17
|
+
|
|
18
|
+
export function getConfig() {
|
|
19
|
+
if (config) return config;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (existsSync(CONFIG_FILE)) {
|
|
23
|
+
const data = readFileSync(CONFIG_FILE, 'utf-8');
|
|
24
|
+
config = { ...DEFAULT_CONFIG, ...JSON.parse(data) };
|
|
25
|
+
} else {
|
|
26
|
+
config = { ...DEFAULT_CONFIG };
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
config = { ...DEFAULT_CONFIG };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isFirstRun() {
|
|
36
|
+
return !existsSync(CONFIG_FILE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function completeOnboarding() {
|
|
40
|
+
// Just save the config to mark onboarding as complete
|
|
41
|
+
saveConfig(getConfig());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function saveConfig(newConfig) {
|
|
45
|
+
config = { ...getConfig(), ...newConfig };
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
49
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isCuteMode() {
|
|
59
|
+
const cfg = getConfig();
|
|
60
|
+
return cfg.theme === 'cute';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isCompactMode() {
|
|
64
|
+
const cfg = getConfig();
|
|
65
|
+
return cfg.viewMode === 'compact';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Theme colors and decorations
|
|
69
|
+
export const themes = {
|
|
70
|
+
cute: {
|
|
71
|
+
primary: 'magenta',
|
|
72
|
+
secondary: 'cyan',
|
|
73
|
+
accent: '#ff69b4', // hot pink
|
|
74
|
+
sent: 'magenta',
|
|
75
|
+
received: 'cyan',
|
|
76
|
+
border: 'magenta',
|
|
77
|
+
headerBorder: 'magenta',
|
|
78
|
+
title: 'β¨ momer β¨',
|
|
79
|
+
subtitle: 'π',
|
|
80
|
+
msgSent: 'π',
|
|
81
|
+
msgReceived: 'π¬',
|
|
82
|
+
group: 'π―ββοΈ',
|
|
83
|
+
newMsg: 'n',
|
|
84
|
+
back: 'esc',
|
|
85
|
+
send: 'β',
|
|
86
|
+
typing: '>',
|
|
87
|
+
refresh: 'r',
|
|
88
|
+
quit: 'q',
|
|
89
|
+
divider: 'ο½₯οΎβ§',
|
|
90
|
+
sparkle: 'β¨',
|
|
91
|
+
heart: 'β‘',
|
|
92
|
+
star: 'β',
|
|
93
|
+
flower: 'βΏ',
|
|
94
|
+
bullet: 'β‘',
|
|
95
|
+
arrow: 'β₯',
|
|
96
|
+
time: '',
|
|
97
|
+
you: 'π You',
|
|
98
|
+
inputPrefix: 'π',
|
|
99
|
+
welcome: 'hey cutie! π',
|
|
100
|
+
noMessages: 'no messages yet... be the first! π',
|
|
101
|
+
sending: 'sending with love...',
|
|
102
|
+
sent: 'sent! π',
|
|
103
|
+
decorTop: 'qο½₯:*:ο½₯οΎβ
,qο½₯:*:ο½₯οΎβ',
|
|
104
|
+
decorBottom: 'β momer β
'
|
|
105
|
+
},
|
|
106
|
+
minimal: {
|
|
107
|
+
primary: 'blue',
|
|
108
|
+
secondary: 'gray',
|
|
109
|
+
accent: 'cyan',
|
|
110
|
+
sent: 'blue',
|
|
111
|
+
received: 'white',
|
|
112
|
+
border: 'gray',
|
|
113
|
+
headerBorder: 'blue',
|
|
114
|
+
title: 'momer',
|
|
115
|
+
subtitle: '',
|
|
116
|
+
msgSent: '',
|
|
117
|
+
msgReceived: '',
|
|
118
|
+
group: 'π₯',
|
|
119
|
+
newMsg: 'n',
|
|
120
|
+
back: 'esc',
|
|
121
|
+
send: 'β',
|
|
122
|
+
typing: '>',
|
|
123
|
+
refresh: 'r',
|
|
124
|
+
quit: 'q',
|
|
125
|
+
divider: 'β',
|
|
126
|
+
sparkle: '',
|
|
127
|
+
heart: '',
|
|
128
|
+
star: '',
|
|
129
|
+
flower: '',
|
|
130
|
+
bullet: 'β’',
|
|
131
|
+
arrow: '>',
|
|
132
|
+
time: '',
|
|
133
|
+
you: 'You',
|
|
134
|
+
inputPrefix: '>',
|
|
135
|
+
welcome: '',
|
|
136
|
+
noMessages: 'No messages yet',
|
|
137
|
+
sending: 'Sending...',
|
|
138
|
+
sent: 'Sent',
|
|
139
|
+
decorTop: '',
|
|
140
|
+
decorBottom: ''
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export function getTheme() {
|
|
145
|
+
const cfg = getConfig();
|
|
146
|
+
return themes[cfg.theme] || themes.cute;
|
|
147
|
+
}
|