Skip to content

Commit c9563ef

Browse files
committed
Implement multi-page support with fixed content saving
1 parent 744eb8d commit c9563ef

File tree

7 files changed

+572
-50
lines changed

7 files changed

+572
-50
lines changed

src/components/ContentWrap.jsx

Lines changed: 108 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,15 @@ export default class ContentWrap extends Component {
4141
this.jsMode = JsModes.JS;
4242
this.prefs = {};
4343
this.codeInPreview = { html: null, css: null, js: null };
44-
this.cmCodes = { html: props.currentItem.html, css: '', js: '' };
44+
45+
// Initialize with the current page's content if available
46+
const currentPage = this.getCurrentPage();
47+
this.cmCodes = {
48+
html: props.currentItem.html,
49+
css: currentPage ? currentPage.css : props.currentItem.css || '',
50+
js: currentPage ? currentPage.js : props.currentItem.js || ''
51+
};
52+
4553
this.cm = {};
4654
this.logCount = 0;
4755

@@ -94,31 +102,90 @@ export default class ContentWrap extends Component {
94102
this.onCodeChange(editor, change);
95103
}
96104

97-
onCssCodeChange(editor, change) {
98-
this.cmCodes.css = editor.getValue();
99-
this.props.onCodeChange(
100-
'css',
101-
this.cmCodes.css,
102-
change.origin !== 'setValue',
103-
);
104-
this.onCodeChange(editor, change);
105-
}
106-
107105
async onJsCodeChange(editor, change) {
108106
await this.setState({ lineOfCode: editor.doc.size });
109107
this.cmCodes.js = editor.getValue();
110-
this.props.onCodeChange(
111-
'js',
112-
this.cmCodes.js,
113-
change.origin !== 'setValue',
114-
);
108+
109+
// Update the current page's JS content
110+
const currentPage = this.getCurrentPage();
111+
if (currentPage) {
112+
const updatedPage = {
113+
...currentPage,
114+
js: editor.getValue()
115+
};
116+
117+
// Find the index of the current page
118+
const pageIndex = this.props.currentItem.pages.findIndex(
119+
page => page.id === currentPage.id
120+
);
121+
122+
if (pageIndex !== -1) {
123+
// Create updated pages array
124+
const updatedPages = [...this.props.currentItem.pages];
125+
updatedPages[pageIndex] = updatedPage;
126+
127+
// Update the current item with the new pages array
128+
const updatedItem = {
129+
...this.props.currentItem,
130+
pages: updatedPages,
131+
// Also update the js field for backward compatibility
132+
js: editor.getValue()
133+
};
134+
135+
// Only call onCodeChange once with the updated item
136+
this.props.onCodeChange('js', editor.getValue(), change.origin !== 'setValue', updatedItem);
137+
} else {
138+
this.props.onCodeChange('js', editor.getValue(), change.origin !== 'setValue');
139+
}
140+
} else {
141+
this.props.onCodeChange('js', editor.getValue(), change.origin !== 'setValue');
142+
}
115143

116144
const targetWindow =
117145
this.detachedWindow ||
118146
document.getElementById('demo-frame').contentWindow;
119147
targetWindow.postMessage({ code: this.cmCodes.js }, '*');
120148
}
121149

150+
async onCssCodeChange(editor, change) {
151+
this.cmCodes.css = editor.getValue();
152+
153+
// Update the current page's CSS content
154+
const currentPage = this.getCurrentPage();
155+
if (currentPage) {
156+
const updatedPage = {
157+
...currentPage,
158+
css: editor.getValue()
159+
};
160+
161+
// Find the index of the current page
162+
const pageIndex = this.props.currentItem.pages.findIndex(
163+
page => page.id === currentPage.id
164+
);
165+
166+
if (pageIndex !== -1) {
167+
// Create updated pages array
168+
const updatedPages = [...this.props.currentItem.pages];
169+
updatedPages[pageIndex] = updatedPage;
170+
171+
// Update the current item with the new pages array
172+
const updatedItem = {
173+
...this.props.currentItem,
174+
pages: updatedPages,
175+
// Also update the css field for backward compatibility
176+
css: editor.getValue()
177+
};
178+
179+
// Only call onCodeChange once with the updated item
180+
this.props.onCodeChange('css', editor.getValue(), change.origin !== 'setValue', updatedItem);
181+
} else {
182+
this.props.onCodeChange('css', editor.getValue(), change.origin !== 'setValue');
183+
}
184+
} else {
185+
this.props.onCodeChange('css', editor.getValue(), change.origin !== 'setValue');
186+
}
187+
}
188+
122189
onCursorMove(editor) {
123190
const cursor = editor.getCursor();
124191
const line = cursor.line;
@@ -159,6 +226,19 @@ export default class ContentWrap extends Component {
159226
}, this.updateDelay);
160227
}
161228

229+
/**
230+
* Gets the current active page from the current item
231+
* @returns {Object|null} The current page or null if not found
232+
*/
233+
getCurrentPage() {
234+
const { currentItem } = this.props;
235+
if (!currentItem || !currentItem.pages || !currentItem.currentPageId) {
236+
return null;
237+
}
238+
239+
return currentItem.pages.find(page => page.id === currentItem.currentPageId) || null;
240+
}
241+
162242
// Called for both detached window and non-detached window
163243
async createPreviewFile(html, css, js) {
164244
// isNotChrome
@@ -303,10 +383,18 @@ export default class ContentWrap extends Component {
303383
}
304384

305385
refreshEditor() {
306-
this.cmCodes.css = this.props.currentItem.css;
307-
this.cmCodes.js = this.props.currentItem.js;
308-
this.cm.css.setValue(this.cmCodes.css || '');
309-
this.cm.js.setValue(this.cmCodes.js || '');
386+
const currentPage = this.getCurrentPage();
387+
388+
if (currentPage) {
389+
this.cmCodes.css = currentPage.css || '';
390+
this.cmCodes.js = currentPage.js || '';
391+
} else {
392+
this.cmCodes.css = this.props.currentItem.css || '';
393+
this.cmCodes.js = this.props.currentItem.js || '';
394+
}
395+
396+
this.cm.css.setValue(this.cmCodes.css);
397+
this.cm.js.setValue(this.cmCodes.js);
310398
this.cm.css.refresh();
311399
this.cm.js.refresh();
312400

src/components/app.jsx

Lines changed: 153 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getCompleteHtml,
2020
getFilenameFromUrl,
2121
blobToBase64,
22+
migrateItemToPages,
2223
} from '../utils';
2324
import { itemService } from '../itemService';
2425
import '../db';
@@ -65,8 +66,8 @@ const UNSAVED_WARNING_COUNT = 15;
6566
const version = '3.6.1';
6667

6768
export default class App extends Component {
68-
constructor() {
69-
super();
69+
constructor(props) {
70+
super(props);
7071
this.AUTO_SAVE_INTERVAL = 15000; // 15 seconds
7172
this.modalDefaultStates = {
7273
isModalOpen: false,
@@ -438,20 +439,9 @@ BookLibService.Borrow(id) {
438439

439440
async setCurrentItem(item) {
440441
const d = deferred();
441-
// TODO: remove later
442-
item.htmlMode =
443-
item.htmlMode || this.state.prefs.htmlMode || HtmlModes.HTML;
444-
item.cssMode = item.cssMode || this.state.prefs.cssMode || CssModes.CSS;
445-
item.jsMode = item.jsMode || this.state.prefs.jsMode || JsModes.JS;
446-
447-
await this.setState({ currentItem: item }, d.resolve);
448-
449-
this.saveItem();
450-
451-
// Reset unsaved count, in UI also.
452-
await this.setState({ unsavedEditCount: 0 });
453-
currentBrowserTab.setTitle(item.title);
454-
442+
// Migrate the item to the new pages format if needed
443+
const migratedItem = migrateItemToPages(item);
444+
await this.setState({ currentItem: migratedItem }, d.resolve);
455445
return d.promise;
456446
}
457447

@@ -675,6 +665,9 @@ BookLibService.Borrow(id) {
675665

676666
trackGaSetField('page', '/');
677667
trackPageView();
668+
669+
// Expose app instance for testing
670+
window._app = this;
678671
}
679672

680673
async closeAllOverlays() {
@@ -890,25 +883,36 @@ BookLibService.Borrow(id) {
890883
await this.setState({ currentItem: item });
891884
}
892885

893-
async onCodeChange(type, code, isUserChange) {
894-
this.state.currentItem[type] = code;
895-
if (isUserChange) {
886+
async onCodeChange(type, code, isUserChange, updatedItem) {
887+
// If an updatedItem is provided (with updated pages), use it instead of just updating the type
888+
if (updatedItem) {
896889
await this.setState({
897-
unsavedEditCount: this.state.unsavedEditCount + 1,
890+
currentItem: updatedItem,
891+
unsavedEditCount: isUserChange ? this.state.unsavedEditCount + 1 : this.state.unsavedEditCount
898892
});
899-
900-
if (
901-
this.state.unsavedEditCount % UNSAVED_WARNING_COUNT === 0 &&
902-
this.state.unsavedEditCount >= UNSAVED_WARNING_COUNT
903-
) {
904-
window.saveBtn.classList.add('animated');
905-
window.saveBtn.classList.add('wobble');
906-
window.saveBtn.addEventListener('animationend', () => {
907-
window.saveBtn.classList.remove('animated');
908-
window.saveBtn.classList.remove('wobble');
893+
} else {
894+
// Original behavior
895+
this.state.currentItem[type] = code;
896+
if (isUserChange) {
897+
await this.setState({
898+
unsavedEditCount: this.state.unsavedEditCount + 1,
909899
});
910900
}
911901
}
902+
903+
if (
904+
isUserChange &&
905+
this.state.unsavedEditCount % UNSAVED_WARNING_COUNT === 0 &&
906+
this.state.unsavedEditCount >= UNSAVED_WARNING_COUNT
907+
) {
908+
window.saveBtn.classList.add('animated');
909+
window.saveBtn.classList.add('wobble');
910+
window.saveBtn.addEventListener('animationend', () => {
911+
window.saveBtn.classList.remove('animated');
912+
window.saveBtn.classList.remove('wobble');
913+
});
914+
}
915+
912916
if (this.state.prefs.isJs13kModeOn) {
913917
// Throttling codesize calculation
914918
if (this.codeSizeCalculationTimeout) {
@@ -1503,6 +1507,125 @@ BookLibService.Borrow(id) {
15031507
mixpanel.track({ event: 'openSettingsModal', category: 'ui' });
15041508
}
15051509

1510+
getCurrentPage() {
1511+
const { currentItem } = this.state;
1512+
if (!currentItem || !currentItem.pages || !currentItem.currentPageId) {
1513+
return null;
1514+
}
1515+
1516+
return currentItem.pages.find(page => page.id === currentItem.currentPageId) || null;
1517+
}
1518+
1519+
addNewPage(title = 'New Page') {
1520+
const { currentItem } = this.state;
1521+
if (!currentItem) return null;
1522+
1523+
const newPage = {
1524+
id: generateRandomId(),
1525+
title,
1526+
js: '',
1527+
css: '',
1528+
isDefault: false
1529+
};
1530+
1531+
const updatedItem = {
1532+
...currentItem,
1533+
pages: [...currentItem.pages, newPage]
1534+
};
1535+
1536+
this.setState({ currentItem: updatedItem });
1537+
1538+
// Switch to the new page
1539+
this.switchToPage(newPage.id);
1540+
1541+
return newPage.id;
1542+
}
1543+
1544+
switchToPage(pageId) {
1545+
const { currentItem } = this.state;
1546+
if (!currentItem) return;
1547+
1548+
const pageExists = currentItem.pages.some(page => page.id === pageId);
1549+
if (!pageExists) return;
1550+
1551+
const updatedItem = {
1552+
...currentItem,
1553+
currentPageId: pageId
1554+
};
1555+
1556+
this.setState({ currentItem: updatedItem }, () => {
1557+
// Refresh the editor to show the new page content
1558+
if (this.contentWrap) {
1559+
this.contentWrap.refreshEditor();
1560+
}
1561+
});
1562+
}
1563+
1564+
updatePage(pageId, updates) {
1565+
const { currentItem } = this.state;
1566+
if (!currentItem) return;
1567+
1568+
const pageIndex = currentItem.pages.findIndex(page => page.id === pageId);
1569+
if (pageIndex === -1) return;
1570+
1571+
const updatedPages = [...currentItem.pages];
1572+
updatedPages[pageIndex] = {
1573+
...updatedPages[pageIndex],
1574+
...updates
1575+
};
1576+
1577+
const updatedItem = {
1578+
...currentItem,
1579+
pages: updatedPages
1580+
};
1581+
1582+
// If we're updating the current page's js or css, also update the item's js/css for backward compatibility
1583+
if (pageId === currentItem.currentPageId) {
1584+
if (updates.js !== undefined) {
1585+
updatedItem.js = updates.js;
1586+
}
1587+
if (updates.css !== undefined) {
1588+
updatedItem.css = updates.css;
1589+
}
1590+
}
1591+
1592+
this.setState({ currentItem: updatedItem });
1593+
}
1594+
1595+
deletePage(pageId) {
1596+
const { currentItem } = this.state;
1597+
if (!currentItem) return;
1598+
1599+
// Don't allow deleting the last page
1600+
if (currentItem.pages.length <= 1) return;
1601+
1602+
const pageIndex = currentItem.pages.findIndex(page => page.id === pageId);
1603+
if (pageIndex === -1) return;
1604+
1605+
const updatedPages = currentItem.pages.filter(page => page.id !== pageId);
1606+
1607+
// If we're deleting the current page, switch to another page
1608+
let updatedCurrentPageId = currentItem.currentPageId;
1609+
if (pageId === currentItem.currentPageId) {
1610+
// Find the nearest page to switch to
1611+
const newPageIndex = Math.min(pageIndex, updatedPages.length - 1);
1612+
updatedCurrentPageId = updatedPages[newPageIndex].id;
1613+
}
1614+
1615+
const updatedItem = {
1616+
...currentItem,
1617+
pages: updatedPages,
1618+
currentPageId: updatedCurrentPageId
1619+
};
1620+
1621+
this.setState({ currentItem: updatedItem }, () => {
1622+
// If we switched pages, refresh the editor
1623+
if (pageId === currentItem.currentPageId && this.contentWrap) {
1624+
this.contentWrap.refreshEditor();
1625+
}
1626+
});
1627+
}
1628+
15061629
render() {
15071630
// remove field imageBase64 from currentItem and save it to a local variable as a copy
15081631
const { imageBase64, ...currentItem } = this.state.currentItem;

0 commit comments

Comments
 (0)