create-lupine 1.0.13 → 1.0.15
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/index.js +32 -2
- package/package.json +1 -1
- package/templates/common/AI_CONTEXT.md +245 -50
- package/templates/common/dev/cp-folder.js +2 -6
- package/templates/cv-starter/web/src/index.html +1 -1
- package/templates/doc-starter/web/src/index.html +1 -1
- package/templates/hello-world/web/src/index.html +1 -1
- package/templates/hello-world/web/src/index.tsx +54 -15
- package/templates/hello-world/web/src/styles/global.css +63 -0
- package/templates/responsive-starter/api/package.json +6 -0
- package/templates/responsive-starter/api/resources/config_default.json +6 -0
- package/templates/responsive-starter/api/resources/install.sqlite.sql +4 -0
- package/templates/responsive-starter/api/src/index.ts +4 -0
- package/templates/responsive-starter/api/src/service/root-api.ts +18 -0
- package/templates/responsive-starter/lupine.json +23 -0
- package/templates/responsive-starter/web/assets/favicon.ico +0 -0
- package/templates/responsive-starter/web/package.json +6 -0
- package/templates/responsive-starter/web/src/app-icons.ts +72 -0
- package/templates/responsive-starter/web/src/components/input-history-component.tsx +93 -0
- package/templates/responsive-starter/web/src/components/mine-about-page.tsx +86 -0
- package/templates/responsive-starter/web/src/components/mine-premium-page.tsx +80 -0
- package/templates/responsive-starter/web/src/components/mine-settings-page.tsx +219 -0
- package/templates/responsive-starter/web/src/components/mobile-top-search-menu.tsx +119 -0
- package/templates/responsive-starter/web/src/components/note-detail.tsx +116 -0
- package/templates/responsive-starter/web/src/components/note-edit.tsx +154 -0
- package/templates/responsive-starter/web/src/components/note-search-page.tsx +193 -0
- package/templates/responsive-starter/web/src/components/search-input.tsx +95 -0
- package/templates/responsive-starter/web/src/components/side-menu-content.tsx +178 -0
- package/templates/responsive-starter/web/src/frames/app-responsive-frame.tsx +46 -0
- package/templates/responsive-starter/web/src/index.html +16 -0
- package/templates/responsive-starter/web/src/index.tsx +42 -0
- package/templates/responsive-starter/web/src/pages/about-page.tsx +43 -0
- package/templates/responsive-starter/web/src/pages/finance-page.tsx +46 -0
- package/templates/responsive-starter/web/src/pages/home-page.tsx +452 -0
- package/templates/responsive-starter/web/src/pages/mine-page.tsx +325 -0
- package/templates/responsive-starter/web/src/pages/tools-page.tsx +46 -0
- package/templates/responsive-starter/web/src/services/local-notes-service.ts +87 -0
- package/templates/responsive-starter/web/src/services/mine-service.ts +45 -0
- package/templates/responsive-starter/web/src/styles/app.css +0 -0
- package/templates/responsive-starter/web/src/styles/base-css.ts +65 -0
- package/templates/responsive-starter/web/src/styles/global.css +2143 -0
- package/templates/responsive-starter/web/src/styles/theme.ts +16 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CssProps,
|
|
3
|
+
HtmlVar,
|
|
4
|
+
PageProps,
|
|
5
|
+
RefProps,
|
|
6
|
+
SliderFrame,
|
|
7
|
+
SliderFrameHookProps,
|
|
8
|
+
MobileHeaderCenter,
|
|
9
|
+
MobileHeaderEmptyIcon,
|
|
10
|
+
MobileHeaderTitleIcon,
|
|
11
|
+
MobileTopSysIcon,
|
|
12
|
+
} from 'lupine.components';
|
|
13
|
+
import { MineService, MineStats } from '../services/mine-service';
|
|
14
|
+
import { MineSettingsPage } from '../components/mine-settings-page';
|
|
15
|
+
import { MinePremiumPage } from '../components/mine-premium-page';
|
|
16
|
+
|
|
17
|
+
export const MinePage = async (props: PageProps) => {
|
|
18
|
+
const dom = new HtmlVar('');
|
|
19
|
+
const sliderFrameHook: SliderFrameHookProps = {};
|
|
20
|
+
|
|
21
|
+
const renderDashboard = () => {
|
|
22
|
+
const stats: MineStats = MineService.getStats();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div class='mine-dashboard'>
|
|
26
|
+
<div class='dashboard-title'>Data Overview</div>
|
|
27
|
+
|
|
28
|
+
<div class='stat-grid'>
|
|
29
|
+
<div class='stat-card' style={{ borderTopColor: 'var(--primary-accent-color)' }}>
|
|
30
|
+
<div class='stat-icon'>
|
|
31
|
+
<i class='ifc-icon ma-text-box-outline' style={{ color: 'var(--primary-accent-color)' }} />
|
|
32
|
+
</div>
|
|
33
|
+
<div class='stat-value'>{stats.noteCount}</div>
|
|
34
|
+
<div class='stat-label'>Notes</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const refreshRender = () => {
|
|
42
|
+
dom.value = (
|
|
43
|
+
<div class='mine-page-container'>
|
|
44
|
+
<SliderFrame hook={sliderFrameHook} />
|
|
45
|
+
|
|
46
|
+
{/* Profile Header Block */}
|
|
47
|
+
<div class='profile-header'>
|
|
48
|
+
<div class='profile-info'>
|
|
49
|
+
<div class='avatar'>
|
|
50
|
+
<i class='ifc-icon ma-account' />
|
|
51
|
+
</div>
|
|
52
|
+
<div class='user-details'>
|
|
53
|
+
<div class='user-name'>Demo User</div>
|
|
54
|
+
<div class='user-tagline'>Local Workspace Offline</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class='profile-nav'>
|
|
58
|
+
<div class='flex-1'></div>
|
|
59
|
+
<div
|
|
60
|
+
class='settings-btn'
|
|
61
|
+
onClick={() => {
|
|
62
|
+
sliderFrameHook.load!(
|
|
63
|
+
<MineSettingsPage sliderFrameHook={sliderFrameHook} onDataChanged={refreshRender} />
|
|
64
|
+
);
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<i class='ifc-icon ma-cog-outline' />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class='mine-scroll-content'>
|
|
73
|
+
{/* Dashboard Blocks */}
|
|
74
|
+
{renderDashboard()}
|
|
75
|
+
|
|
76
|
+
{/* Premium Banner Mock */}
|
|
77
|
+
<div class='premium-banner'>
|
|
78
|
+
<div class='banner-content'>
|
|
79
|
+
<div class='banner-title'>
|
|
80
|
+
<i class='ifc-icon ma-star' /> Upgrade to Premium
|
|
81
|
+
</div>
|
|
82
|
+
<div class='banner-desc'>Unlock cloud sync, custom themes, and unlimited media storage.</div>
|
|
83
|
+
</div>
|
|
84
|
+
<button
|
|
85
|
+
class='banner-btn'
|
|
86
|
+
onClick={() => {
|
|
87
|
+
sliderFrameHook.load!(<MinePremiumPage sliderFrameHook={sliderFrameHook} />);
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
View Plans
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const css: CssProps = {
|
|
99
|
+
display: 'flex',
|
|
100
|
+
flexDirection: 'column',
|
|
101
|
+
height: '100%',
|
|
102
|
+
position: 'relative',
|
|
103
|
+
|
|
104
|
+
'.mine-page-container': {
|
|
105
|
+
display: 'flex',
|
|
106
|
+
flexDirection: 'column',
|
|
107
|
+
height: '100%',
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// 1. Profile Header
|
|
111
|
+
'.profile-header': {
|
|
112
|
+
padding: '16px 20px 24px 20px',
|
|
113
|
+
borderBottomLeftRadius: '24px',
|
|
114
|
+
borderBottomRightRadius: '24px',
|
|
115
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
|
|
116
|
+
zIndex: 10,
|
|
117
|
+
},
|
|
118
|
+
'.profile-nav': {
|
|
119
|
+
display: 'flex',
|
|
120
|
+
marginBottom: '16px',
|
|
121
|
+
},
|
|
122
|
+
'.settings-btn': {
|
|
123
|
+
width: '36px',
|
|
124
|
+
height: '36px',
|
|
125
|
+
borderRadius: '18px',
|
|
126
|
+
backgroundColor: 'var(--secondary-bg-color)',
|
|
127
|
+
display: 'flex',
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
justifyContent: 'center',
|
|
130
|
+
cursor: 'pointer',
|
|
131
|
+
color: 'var(--primary-color)',
|
|
132
|
+
transition: 'background-color 0.2s',
|
|
133
|
+
'&:active': {
|
|
134
|
+
backgroundColor: 'rgba(0,0,0,0.05)',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
'.profile-info': {
|
|
138
|
+
display: 'flex',
|
|
139
|
+
alignItems: 'center',
|
|
140
|
+
gap: '16px',
|
|
141
|
+
},
|
|
142
|
+
'.avatar': {
|
|
143
|
+
width: '64px',
|
|
144
|
+
height: '64px',
|
|
145
|
+
borderRadius: '32px',
|
|
146
|
+
backgroundColor: 'var(--primary-accent-color)',
|
|
147
|
+
color: 'white',
|
|
148
|
+
display: 'flex',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
justifyContent: 'center',
|
|
151
|
+
fontSize: '36px',
|
|
152
|
+
boxShadow: '0 4px 12px rgba(24, 144, 255, 0.3)',
|
|
153
|
+
},
|
|
154
|
+
'.user-name': {
|
|
155
|
+
fontSize: '22px',
|
|
156
|
+
fontWeight: 'bold',
|
|
157
|
+
color: 'var(--primary-color)',
|
|
158
|
+
marginBottom: '4px',
|
|
159
|
+
},
|
|
160
|
+
'.user-tagline': {
|
|
161
|
+
fontSize: '14px',
|
|
162
|
+
color: 'var(--secondary-color)',
|
|
163
|
+
opacity: 0.8,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Scrollable Rest of the Page
|
|
167
|
+
'.mine-scroll-content': {
|
|
168
|
+
flex: 1,
|
|
169
|
+
overflowY: 'auto',
|
|
170
|
+
padding: '24px 16px',
|
|
171
|
+
display: 'flex',
|
|
172
|
+
flexDirection: 'column',
|
|
173
|
+
gap: '24px',
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// 2. Dashboard
|
|
177
|
+
'.mine-dashboard': {
|
|
178
|
+
display: 'flex',
|
|
179
|
+
flexDirection: 'column',
|
|
180
|
+
gap: '16px',
|
|
181
|
+
},
|
|
182
|
+
'.dashboard-title': {
|
|
183
|
+
fontSize: '18px',
|
|
184
|
+
fontWeight: 'bold',
|
|
185
|
+
color: 'var(--primary-color)',
|
|
186
|
+
},
|
|
187
|
+
'.stat-grid': {
|
|
188
|
+
display: 'grid',
|
|
189
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
190
|
+
gap: '12px',
|
|
191
|
+
},
|
|
192
|
+
'.stat-card': {
|
|
193
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
194
|
+
borderRadius: '12px',
|
|
195
|
+
padding: '16px 12px',
|
|
196
|
+
display: 'flex',
|
|
197
|
+
flexDirection: 'column',
|
|
198
|
+
alignItems: 'center',
|
|
199
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
|
200
|
+
borderTop: '4px solid transparent',
|
|
201
|
+
},
|
|
202
|
+
'.stat-icon': {
|
|
203
|
+
fontSize: '24px',
|
|
204
|
+
marginBottom: '8px',
|
|
205
|
+
},
|
|
206
|
+
'.stat-value': {
|
|
207
|
+
fontSize: '20px',
|
|
208
|
+
fontWeight: 'bold',
|
|
209
|
+
color: 'var(--primary-color)',
|
|
210
|
+
marginBottom: '4px',
|
|
211
|
+
},
|
|
212
|
+
'.stat-label': {
|
|
213
|
+
fontSize: '12px',
|
|
214
|
+
color: 'var(--secondary-color)',
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
'.usage-bar-container': {
|
|
218
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
219
|
+
borderRadius: '12px',
|
|
220
|
+
padding: '16px',
|
|
221
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
|
222
|
+
},
|
|
223
|
+
'.usage-bar-labels': {
|
|
224
|
+
display: 'flex',
|
|
225
|
+
justifyContent: 'space-between',
|
|
226
|
+
fontSize: '13px',
|
|
227
|
+
color: 'var(--secondary-color)',
|
|
228
|
+
marginBottom: '12px',
|
|
229
|
+
fontWeight: '500',
|
|
230
|
+
},
|
|
231
|
+
'.usage-bar': {
|
|
232
|
+
height: '12px',
|
|
233
|
+
borderRadius: '6px',
|
|
234
|
+
display: 'flex',
|
|
235
|
+
overflow: 'hidden',
|
|
236
|
+
backgroundColor: 'rgba(0,0,0,0.05)',
|
|
237
|
+
gap: '2px', // tiny visual gap between segments
|
|
238
|
+
},
|
|
239
|
+
'.usage-bar-segment': {
|
|
240
|
+
height: '100%',
|
|
241
|
+
transition: 'width 0.5s ease',
|
|
242
|
+
},
|
|
243
|
+
'.usage-bar-segment.empty': { backgroundColor: 'transparent' },
|
|
244
|
+
'.usage-bar-segment.note': { backgroundColor: 'var(--primary-accent-color)' },
|
|
245
|
+
'.usage-bar-segment.diary': { backgroundColor: '#eb2f96' },
|
|
246
|
+
'.usage-bar-segment.finance': { backgroundColor: '#52c41a' },
|
|
247
|
+
|
|
248
|
+
// 3. Premium Banner
|
|
249
|
+
'.premium-banner': {
|
|
250
|
+
background: 'linear-gradient(135deg, #FF9A9E 0%, #FECFEF 99%, #FECFEF 100%)',
|
|
251
|
+
borderRadius: '16px',
|
|
252
|
+
padding: '20px',
|
|
253
|
+
display: 'flex',
|
|
254
|
+
flexDirection: 'column',
|
|
255
|
+
gap: '16px',
|
|
256
|
+
color: '#333',
|
|
257
|
+
boxShadow: '0 8px 16px rgba(255, 154, 158, 0.3)',
|
|
258
|
+
position: 'relative',
|
|
259
|
+
overflow: 'hidden',
|
|
260
|
+
},
|
|
261
|
+
'.banner-content': {
|
|
262
|
+
position: 'relative',
|
|
263
|
+
zIndex: 2,
|
|
264
|
+
},
|
|
265
|
+
'.banner-title': {
|
|
266
|
+
fontSize: '18px',
|
|
267
|
+
fontWeight: 'bold',
|
|
268
|
+
marginBottom: '8px',
|
|
269
|
+
display: 'flex',
|
|
270
|
+
alignItems: 'center',
|
|
271
|
+
gap: '8px',
|
|
272
|
+
},
|
|
273
|
+
'.banner-title i': {
|
|
274
|
+
color: '#FFD700',
|
|
275
|
+
},
|
|
276
|
+
'.banner-desc': {
|
|
277
|
+
fontSize: '14px',
|
|
278
|
+
opacity: 0.9,
|
|
279
|
+
lineHeight: '1.4',
|
|
280
|
+
},
|
|
281
|
+
'.banner-btn': {
|
|
282
|
+
alignSelf: 'flex-start',
|
|
283
|
+
backgroundColor: '#fff',
|
|
284
|
+
color: '#FF9A9E',
|
|
285
|
+
border: 'none',
|
|
286
|
+
padding: '8px 20px',
|
|
287
|
+
borderRadius: '20px',
|
|
288
|
+
fontWeight: 'bold',
|
|
289
|
+
fontSize: '14px',
|
|
290
|
+
cursor: 'pointer',
|
|
291
|
+
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
|
|
292
|
+
position: 'relative',
|
|
293
|
+
zIndex: 2,
|
|
294
|
+
},
|
|
295
|
+
'.mine-page-content-wrapper': {
|
|
296
|
+
flex: 1,
|
|
297
|
+
overflowY: 'auto',
|
|
298
|
+
display: 'flex',
|
|
299
|
+
flexDirection: 'column',
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const ref: RefProps = {
|
|
304
|
+
onLoad: async () => {
|
|
305
|
+
refreshRender();
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<div css={css} ref={ref}>
|
|
311
|
+
<MobileHeaderCenter>
|
|
312
|
+
<MobileHeaderTitleIcon
|
|
313
|
+
title='Mine'
|
|
314
|
+
left={<MobileHeaderEmptyIcon />}
|
|
315
|
+
right={
|
|
316
|
+
<div class='flex-center-gap-12'>
|
|
317
|
+
<MobileTopSysIcon />
|
|
318
|
+
</div>
|
|
319
|
+
}
|
|
320
|
+
/>
|
|
321
|
+
</MobileHeaderCenter>
|
|
322
|
+
<div class='mine-page-content-wrapper no-scrollbar-container'>{dom.node}</div>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CssProps,
|
|
3
|
+
MobileHeaderCenter,
|
|
4
|
+
MobileHeaderEmptyIcon,
|
|
5
|
+
MobileHeaderTitleIcon,
|
|
6
|
+
MobileTopSysIcon,
|
|
7
|
+
PageProps,
|
|
8
|
+
RefProps,
|
|
9
|
+
} from 'lupine.components';
|
|
10
|
+
|
|
11
|
+
export const ToolsPage = async (props: PageProps) => {
|
|
12
|
+
const css: CssProps = {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexDirection: 'column',
|
|
15
|
+
height: '100%',
|
|
16
|
+
position: 'relative',
|
|
17
|
+
'.tools-list-container': {
|
|
18
|
+
padding: '32px 24px',
|
|
19
|
+
display: 'flex',
|
|
20
|
+
flexDirection: 'column',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ref: RefProps = {
|
|
26
|
+
onLoad: async () => {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div css={css} ref={ref}>
|
|
31
|
+
<MobileHeaderCenter>
|
|
32
|
+
<MobileHeaderTitleIcon
|
|
33
|
+
title='Tools'
|
|
34
|
+
left={<MobileHeaderEmptyIcon />}
|
|
35
|
+
right={
|
|
36
|
+
<div class='flex-center-gap-12'>
|
|
37
|
+
<MobileTopSysIcon />
|
|
38
|
+
</div>
|
|
39
|
+
}
|
|
40
|
+
/>
|
|
41
|
+
</MobileHeaderCenter>
|
|
42
|
+
|
|
43
|
+
<div class='tools-list-container no-scrollbar-container'>This is a demo tools page.</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface LocalNoteProps {
|
|
2
|
+
id: number;
|
|
3
|
+
title: string;
|
|
4
|
+
content: string; // HTML string
|
|
5
|
+
updatedAt: number;
|
|
6
|
+
images?: string[]; // Optional base64 strings
|
|
7
|
+
color?: string; // Optional color tag
|
|
8
|
+
orderIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const STORAGE_KEY = 'lj_notes';
|
|
12
|
+
|
|
13
|
+
export const LocalNotesService = {
|
|
14
|
+
getAllNotes: (): LocalNoteProps[] => {
|
|
15
|
+
try {
|
|
16
|
+
const data = localStorage.getItem(STORAGE_KEY);
|
|
17
|
+
const notes: LocalNoteProps[] = data ? JSON.parse(data) : [];
|
|
18
|
+
return notes.sort((a, b) => {
|
|
19
|
+
const orderA = a.orderIndex !== undefined ? a.orderIndex : a.id;
|
|
20
|
+
const orderB = b.orderIndex !== undefined ? b.orderIndex : b.id;
|
|
21
|
+
// Default sort: descending (newest/highest first), unless orderIndex dictates otherwise.
|
|
22
|
+
// If we want manual ordering, let's just sort descending by orderIndex.
|
|
23
|
+
return orderB - orderA;
|
|
24
|
+
});
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error('Failed to load notes', e);
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
getNoteById: (id: number): LocalNoteProps | undefined => {
|
|
32
|
+
return LocalNotesService.getAllNotes().find((n) => n.id === id);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
saveNote: (note: LocalNoteProps): LocalNoteProps => {
|
|
36
|
+
const notes = LocalNotesService.getAllNotes();
|
|
37
|
+
note.updatedAt = Date.now();
|
|
38
|
+
|
|
39
|
+
if (note.id <= 0) {
|
|
40
|
+
note.id = Date.now();
|
|
41
|
+
// Calculate next highest order index
|
|
42
|
+
const maxOrder = notes.length > 0 ? Math.max(...notes.map((n) => n.orderIndex ?? n.id)) : Date.now();
|
|
43
|
+
note.orderIndex = maxOrder + 1;
|
|
44
|
+
notes.unshift(note);
|
|
45
|
+
} else {
|
|
46
|
+
const index = notes.findIndex((n) => n.id === note.id);
|
|
47
|
+
if (index > -1) {
|
|
48
|
+
notes[index] = note;
|
|
49
|
+
} else {
|
|
50
|
+
notes.unshift(note);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
|
|
55
|
+
return note;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
updateNoteOrders: (orderedIds: number[]): boolean => {
|
|
59
|
+
// orderedIds is passed from top to bottom. Top has highest orderIndex.
|
|
60
|
+
const notes = LocalNotesService.getAllNotes();
|
|
61
|
+
let updated = false;
|
|
62
|
+
const len = orderedIds.length;
|
|
63
|
+
orderedIds.forEach((id, idx) => {
|
|
64
|
+
const note = notes.find((n) => n.id === id);
|
|
65
|
+
if (note) {
|
|
66
|
+
note.orderIndex = len - idx; // Highest index for first item
|
|
67
|
+
updated = true;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (updated) {
|
|
72
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
|
|
73
|
+
}
|
|
74
|
+
return updated;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
deleteNote: (id: number): boolean => {
|
|
78
|
+
let notes = LocalNotesService.getAllNotes();
|
|
79
|
+
const len = notes.length;
|
|
80
|
+
notes = notes.filter((n) => n.id !== id);
|
|
81
|
+
if (notes.length !== len) {
|
|
82
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { LocalNotesService } from './local-notes-service';
|
|
2
|
+
|
|
3
|
+
export interface MineStats {
|
|
4
|
+
noteCount: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface BackupData {
|
|
8
|
+
notes: any[];
|
|
9
|
+
timestamp: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const MineService = {
|
|
13
|
+
getStats: (): MineStats => {
|
|
14
|
+
const notes = LocalNotesService.getAllNotes();
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
noteCount: notes.length,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
exportBackup: (): string => {
|
|
22
|
+
const data: BackupData = {
|
|
23
|
+
notes: LocalNotesService.getAllNotes(),
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
};
|
|
26
|
+
return JSON.stringify(data, null, 2);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
importBackup: (jsonStr: string): boolean => {
|
|
30
|
+
try {
|
|
31
|
+
const data: BackupData = JSON.parse(jsonStr);
|
|
32
|
+
if (Array.isArray(data.notes)) {
|
|
33
|
+
localStorage.setItem('lj_notes', JSON.stringify(data.notes));
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Failed to import backup', e);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
clearAllData: () => {
|
|
43
|
+
localStorage.removeItem('lj_notes');
|
|
44
|
+
},
|
|
45
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.web';
|
|
2
|
+
import { appIconsCss } from '../app-icons';
|
|
3
|
+
|
|
4
|
+
export const baseCss: CssProps = {
|
|
5
|
+
...appIconsCss,
|
|
6
|
+
'.user-page-placeholder': {
|
|
7
|
+
width: '100%',
|
|
8
|
+
height: '100%',
|
|
9
|
+
},
|
|
10
|
+
'.flex-center-gap-12': {
|
|
11
|
+
display: 'flex',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
gap: '12px',
|
|
14
|
+
},
|
|
15
|
+
'.icon-14': {
|
|
16
|
+
fontSize: '14px',
|
|
17
|
+
},
|
|
18
|
+
'.icon-20-pointer': {
|
|
19
|
+
fontSize: '20px',
|
|
20
|
+
cursor: 'pointer',
|
|
21
|
+
},
|
|
22
|
+
'.icon-btn-secondary': {
|
|
23
|
+
fontSize: '14px',
|
|
24
|
+
marginLeft: '8px',
|
|
25
|
+
color: 'var(--secondary-color)',
|
|
26
|
+
},
|
|
27
|
+
'.text-danger': {
|
|
28
|
+
color: '#ff4d4f',
|
|
29
|
+
},
|
|
30
|
+
'.color-preview-box': {
|
|
31
|
+
width: '20px',
|
|
32
|
+
height: '20px',
|
|
33
|
+
borderRadius: '4px',
|
|
34
|
+
border: '1px solid var(--primary-border-color)',
|
|
35
|
+
},
|
|
36
|
+
'.fab-button': {
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
right: '24px',
|
|
39
|
+
bottom: '24px',
|
|
40
|
+
width: '56px',
|
|
41
|
+
height: '56px',
|
|
42
|
+
borderRadius: '28px',
|
|
43
|
+
color: 'white',
|
|
44
|
+
display: 'flex',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
|
|
48
|
+
cursor: 'pointer',
|
|
49
|
+
zIndex: 10,
|
|
50
|
+
fontFamily: 'monospace',
|
|
51
|
+
},
|
|
52
|
+
'.fab-icon': {
|
|
53
|
+
fontSize: '32px',
|
|
54
|
+
lineHeight: '1',
|
|
55
|
+
},
|
|
56
|
+
'.note-card-wrapper': {
|
|
57
|
+
position: 'relative',
|
|
58
|
+
marginBottom: '16px',
|
|
59
|
+
borderRadius: '12px',
|
|
60
|
+
overflow: 'hidden',
|
|
61
|
+
boxShadow: 'var(--cover-box-shadow)',
|
|
62
|
+
backgroundColor: 'transparent',
|
|
63
|
+
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
|
64
|
+
},
|
|
65
|
+
};
|