script.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. const API_BASE = 'https://notes.lhamacorp.com/api/notes';
  2. let currentNoteId = null;
  3. let autoSaveTimeout = null;
  4. let lastSavedContent = '';
  5. let currentNoteModifiedAt = null;
  6. let versionCheckInterval = null;
  7. const VERSION_CHECK_INTERVAL_MS = 5000;
  8. function initializeTheme() {
  9. const savedTheme = localStorage.getItem('theme') || 'dark';
  10. const body = document.body;
  11. if (savedTheme === 'light') {
  12. body.setAttribute('data-theme', 'light');
  13. } else {
  14. body.removeAttribute('data-theme');
  15. }
  16. const themeSwitch = document.getElementById('themeSwitch');
  17. if (themeSwitch) {
  18. if (savedTheme === 'light') {
  19. themeSwitch.classList.remove('dark');
  20. } else {
  21. themeSwitch.classList.add('dark');
  22. }
  23. }
  24. updateThemeColor();
  25. }
  26. function updateThemeSwitchState() {
  27. const savedTheme = localStorage.getItem('theme') || 'dark';
  28. const themeSwitch = document.getElementById('themeSwitch');
  29. if (themeSwitch) {
  30. if (savedTheme === 'light') {
  31. themeSwitch.classList.remove('dark');
  32. } else {
  33. themeSwitch.classList.add('dark');
  34. }
  35. }
  36. }
  37. function toggleTheme() {
  38. const body = document.body;
  39. const themeSwitch = document.getElementById('themeSwitch');
  40. if (!themeSwitch) return;
  41. const currentTheme = body.getAttribute('data-theme');
  42. if (currentTheme === 'light') {
  43. body.removeAttribute('data-theme');
  44. themeSwitch.classList.add('dark');
  45. localStorage.setItem('theme', 'dark');
  46. } else {
  47. body.setAttribute('data-theme', 'light');
  48. themeSwitch.classList.remove('dark');
  49. localStorage.setItem('theme', 'light');
  50. }
  51. updateThemeColor();
  52. }
  53. function init() {
  54. initializeTheme();
  55. setupMobileOptimizations();
  56. const pathname = window.location.pathname;
  57. const pathParts = pathname.split('/');
  58. let idFromUrl = null;
  59. for (const part of pathParts) {
  60. if (part && /^[A-Za-z0-9]{26}$/.test(part)) {
  61. idFromUrl = part;
  62. break;
  63. }
  64. }
  65. const noteContent = document.getElementById('noteContent');
  66. if (noteContent) {
  67. // Check for stored note ID from navigation (for desktop app)
  68. const storedNoteId = sessionStorage.getItem('noteToLoad');
  69. if (storedNoteId) {
  70. sessionStorage.removeItem('noteToLoad');
  71. loadNoteById(storedNoteId);
  72. } else if (idFromUrl) {
  73. loadNoteById(idFromUrl);
  74. } else {
  75. newNote();
  76. }
  77. noteContent.addEventListener('input', handleContentChange);
  78. }
  79. const noteIdInput = document.getElementById('noteIdInput');
  80. if (noteIdInput) {
  81. noteIdInput.addEventListener('keypress', function(e) {
  82. if (e.key === 'Enter') {
  83. loadNoteFromInput();
  84. }
  85. });
  86. }
  87. }
  88. function setupMobileOptimizations() {
  89. const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  90. if (isMobile) {
  91. const noteContent = document.getElementById('noteContent');
  92. if (noteContent) {
  93. noteContent.addEventListener('focus', function() {
  94. setTimeout(() => {
  95. this.scrollIntoView({ behavior: 'smooth', block: 'center' });
  96. }, 300);
  97. });
  98. noteContent.addEventListener('touchmove', function(e) {
  99. e.stopPropagation();
  100. }, { passive: true });
  101. }
  102. updateThemeColor();
  103. }
  104. window.addEventListener('orientationchange', function() {
  105. setTimeout(() => {
  106. const vh = window.innerHeight * 0.01;
  107. document.documentElement.style.setProperty('--vh', `${vh}px`);
  108. }, 100);
  109. });
  110. const vh = window.innerHeight * 0.01;
  111. document.documentElement.style.setProperty('--vh', `${vh}px`);
  112. }
  113. function updateThemeColor() {
  114. const themeColorMeta = document.querySelector('meta[name="theme-color"]');
  115. const currentTheme = document.body.getAttribute('data-theme');
  116. if (themeColorMeta) {
  117. if (currentTheme === 'light') {
  118. themeColorMeta.setAttribute('content', '#007bff');
  119. } else {
  120. themeColorMeta.setAttribute('content', '#2d2d2d');
  121. }
  122. }
  123. }
  124. function handleContentChange() {
  125. const noteContent = document.getElementById('noteContent');
  126. if (!noteContent) {
  127. console.error('Element with ID "noteContent" not found');
  128. return;
  129. }
  130. const content = noteContent.value;
  131. if (autoSaveTimeout) {
  132. clearTimeout(autoSaveTimeout);
  133. }
  134. autoSaveTimeout = setTimeout(() => {
  135. autoSave(content);
  136. }, 1000);
  137. }
  138. async function autoSave(content) {
  139. if (content === lastSavedContent) {
  140. return;
  141. }
  142. try {
  143. if (currentNoteId) {
  144. const response = await fetch(`${API_BASE}/${currentNoteId}`, {
  145. method: 'PUT',
  146. headers: {
  147. 'Content-Type': 'application/json',
  148. },
  149. body: JSON.stringify({
  150. content: content
  151. })
  152. });
  153. if (response.ok) {
  154. const updatedNote = await response.json();
  155. setCurrentNoteVersion(updatedNote.modifiedAt);
  156. }
  157. } else {
  158. const response = await fetch(API_BASE, {
  159. method: 'POST',
  160. headers: {
  161. 'Content-Type': 'application/json',
  162. },
  163. body: JSON.stringify({
  164. note: content
  165. })
  166. });
  167. if (response.ok) {
  168. const note = await response.json();
  169. currentNoteId = note.id;
  170. const newUrl = `/${note.id}`;
  171. window.history.replaceState({}, '', newUrl);
  172. showNoteId(note.id);
  173. setCurrentNoteVersion(note.modifiedAt);
  174. }
  175. }
  176. lastSavedContent = content;
  177. } catch (error) {
  178. console.error('Auto-save failed:', error);
  179. }
  180. }
  181. async function loadNoteById(id) {
  182. stopVersionPolling();
  183. try {
  184. const response = await fetch(`${API_BASE}/${id}`);
  185. if (response.ok) {
  186. const note = await response.json();
  187. currentNoteId = note.id;
  188. const noteContent = document.getElementById('noteContent');
  189. if (noteContent) {
  190. noteContent.value = note.content;
  191. lastSavedContent = note.content;
  192. } else {
  193. console.error('Element with ID "noteContent" not found');
  194. }
  195. // Note: Skipping URL history manipulation for desktop app
  196. showNoteId(note.id);
  197. setCurrentNoteVersion(note.modifiedAt);
  198. } else {
  199. console.warn(`Note with ID ${id} not found (${response.status}), creating new note`);
  200. await newNote();
  201. }
  202. } catch (error) {
  203. console.error('Failed to load note:', error, 'creating new note instead');
  204. await newNote();
  205. }
  206. }
  207. function showNoteId(id) {
  208. const noteIdDisplay = document.getElementById('noteIdDisplay');
  209. if (noteIdDisplay) {
  210. noteIdDisplay.textContent = id;
  211. noteIdDisplay.style.display = 'inline-block';
  212. } else {
  213. console.error('Element with ID "noteIdDisplay" not found');
  214. }
  215. }
  216. async function copyNoteLink() {
  217. try {
  218. const noteIdDisplay = document.getElementById('noteIdDisplay');
  219. if (!noteIdDisplay) {
  220. console.error('Element with ID "noteIdDisplay" not found');
  221. return;
  222. }
  223. // Copy just the note ID, not the full URL
  224. const noteId = currentNoteId;
  225. if (!noteId) {
  226. console.error('No note ID available to copy');
  227. return;
  228. }
  229. await navigator.clipboard.writeText(noteId);
  230. const originalText = noteIdDisplay.textContent;
  231. noteIdDisplay.textContent = 'Copied!';
  232. noteIdDisplay.style.color = 'var(--accent-primary)';
  233. setTimeout(() => {
  234. noteIdDisplay.textContent = originalText;
  235. noteIdDisplay.style.color = '';
  236. }, 2000);
  237. } catch (error) {
  238. console.error('Failed to copy note link:', error);
  239. const noteIdDisplay = document.getElementById('noteIdDisplay');
  240. if (noteIdDisplay) {
  241. const originalText = noteIdDisplay.textContent;
  242. noteIdDisplay.textContent = 'Copy failed';
  243. setTimeout(() => {
  244. noteIdDisplay.textContent = originalText;
  245. }, 2000);
  246. }
  247. }
  248. }
  249. async function newNote() {
  250. stopVersionPolling();
  251. try {
  252. const response = await fetch(API_BASE, {
  253. method: 'POST',
  254. headers: {
  255. 'Content-Type': 'application/json',
  256. },
  257. body: JSON.stringify({
  258. note: ''
  259. })
  260. });
  261. if (response.ok) {
  262. const note = await response.json();
  263. currentNoteId = note.id;
  264. lastSavedContent = '';
  265. const noteContent = document.getElementById('noteContent');
  266. if (noteContent) {
  267. noteContent.value = '';
  268. noteContent.focus();
  269. } else {
  270. console.error('Element with ID "noteContent" not found');
  271. }
  272. // Note: Skipping URL history manipulation for desktop app
  273. showNoteId(note.id);
  274. setCurrentNoteVersion(note.modifiedAt);
  275. }
  276. } catch (error) {
  277. console.error('Failed to create new note:', error);
  278. }
  279. }
  280. function showIdInput() {
  281. const idInputOverlay = document.getElementById('idInputOverlay');
  282. const noteIdInput = document.getElementById('noteIdInput');
  283. if (idInputOverlay) {
  284. idInputOverlay.classList.remove('hidden');
  285. }
  286. if (noteIdInput) {
  287. noteIdInput.focus();
  288. }
  289. }
  290. function hideIdInput() {
  291. const idInputOverlay = document.getElementById('idInputOverlay');
  292. const noteIdInput = document.getElementById('noteIdInput');
  293. if (idInputOverlay) {
  294. idInputOverlay.classList.add('hidden');
  295. }
  296. if (noteIdInput) {
  297. noteIdInput.value = '';
  298. }
  299. }
  300. function loadNoteFromInput() {
  301. const noteIdInput = document.getElementById('noteIdInput');
  302. if (!noteIdInput) return;
  303. const id = noteIdInput.value.trim();
  304. if (id) {
  305. hideIdInput();
  306. // Check if we're on the home page, navigate to editor first
  307. if (window.location.pathname.includes('home.html')) {
  308. // Store the note ID to load after navigation
  309. sessionStorage.setItem('noteToLoad', id);
  310. window.location.replace('index.html');
  311. } else {
  312. // We're already on the editor page, just load the note
  313. loadNoteById(id);
  314. }
  315. }
  316. }
  317. async function checkForNoteUpdates() {
  318. if (!currentNoteId || !currentNoteModifiedAt) {
  319. return;
  320. }
  321. try {
  322. const response = await fetch(`${API_BASE}/${currentNoteId}/metadata`);
  323. if (response.ok) {
  324. const metadata = await response.json();
  325. const serverModifiedAt = new Date(metadata.modifiedAt).getTime();
  326. const localModifiedAt = new Date(currentNoteModifiedAt).getTime();
  327. if (serverModifiedAt > localModifiedAt) {
  328. await autoReloadNote();
  329. }
  330. }
  331. } catch (error) {
  332. console.error('Failed to check for note updates:', error);
  333. }
  334. }
  335. function startVersionPolling() {
  336. stopVersionPolling();
  337. if (currentNoteId) {
  338. versionCheckInterval = setInterval(checkForNoteUpdates, VERSION_CHECK_INTERVAL_MS);
  339. }
  340. }
  341. function stopVersionPolling() {
  342. if (versionCheckInterval) {
  343. clearInterval(versionCheckInterval);
  344. versionCheckInterval = null;
  345. }
  346. }
  347. function setCurrentNoteVersion(modifiedAt) {
  348. currentNoteModifiedAt = modifiedAt;
  349. startVersionPolling();
  350. }
  351. function showUpdateToast() {
  352. const toast = document.getElementById('updateToast');
  353. if (toast) {
  354. toast.classList.remove('hidden');
  355. setTimeout(() => {
  356. toast.classList.add('hidden');
  357. }, 2000);
  358. }
  359. }
  360. async function autoReloadNote() {
  361. if (currentNoteId) {
  362. stopVersionPolling();
  363. await loadNoteById(currentNoteId);
  364. showUpdateToast();
  365. }
  366. }
  367. window.addEventListener('load', init);