main.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * Loads a JavaScript file and returns a Promise for when it is loaded
  3. */
  4. const loadScript = async (src, id = undefined) => {
  5. return new Promise((resolve, reject) => {
  6. src = String(src)
  7. id = id || src.substring(src.lastIndexOf('/') + 1)
  8. const oldScript = document.getElementById(id);
  9. const newScript = document.createElement('script')
  10. newScript.id = id
  11. newScript.type = 'text/javascript'
  12. newScript.onload = resolve
  13. newScript.onerror = reject
  14. newScript.src = src
  15. if ((oldScript === undefined) || (oldScript === null)) {
  16. document.body.appendChild(newScript)
  17. } else {
  18. document.body.replaceChild(newScript, oldScript)
  19. }
  20. })
  21. }
  22. let icon = (name, width = 16, height = 16, color = 'currentColor') => {
  23. return `
  24. <svg class="bi" width="${width}" height="${height}" fill="${color}">
  25. <use xlink:href="/icons/icons.svg#${name}"/>
  26. </svg>
  27. `;
  28. }
  29. // Fetch Post content via AJAX
  30. document.querySelectorAll('.item').forEach((el) => {
  31. el.addEventListener('click', (event) => {
  32. document.querySelector('.item-selected').classList.remove('item-selected');
  33. event.currentTarget/*.querySelector('.item')*/.classList.add('item-selected');
  34. document.cookie = `currentId=${event.currentTarget.id}`
  35. fetch(`/posts/${event.currentTarget.id}/view`)
  36. .then(response => response.text())
  37. .then(
  38. data => {
  39. document.getElementById('main').innerHTML = data;
  40. loadScript('/javascripts/view.js', 'view.js');
  41. }
  42. );
  43. });
  44. });
  45. // POST method implementation:
  46. async function postData(url = '', formData) {
  47. let headers = new Headers();
  48. // headers.append('Content-Type', 'application/x-www-form-urlencoded');
  49. headers.append('X-Requested-With', 'XMLHttpRequest');
  50. // Default options are marked with *
  51. const response = await fetch(url, {
  52. method: 'POST', // *GET, POST, PUT, DELETE, etc.
  53. mode: 'same-origin', // no-cors, *cors, same-origin
  54. cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
  55. credentials: 'same-origin', // include, *same-origin, omit
  56. headers: headers,
  57. redirect: 'follow', // manual, *follow, error
  58. referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
  59. body: formData // body data type must match "Content-Type" header
  60. });
  61. return response.json(); // parses JSON response into native JavaScript objects
  62. };
  63. const createObjectStore = async (transaction, objectStoreName) => {
  64. return new Promise((resolve, reject) => {
  65. try {
  66. const objectStore = transaction.objectStore(objectStoreName)
  67. resolve(objectStore)
  68. } catch (error) {
  69. reject(error)
  70. }
  71. })
  72. }
  73. const createTransaction = async (db, objectStoreName, mode = 'read') => {
  74. return new Promise((resolve, reject) => {
  75. try {
  76. const transaction = db.transaction(objectStoreName, mode)
  77. console.log(`${transaction.mode} transaction has been created sucessfuly on database: ${transaction.db.name} and object store(s): ` + Array.from(transaction.objectStoreNames).join(', '))
  78. resolve(transaction)
  79. } catch (error) {
  80. reject(error)
  81. }
  82. })
  83. }
  84. const createDataBase = async (dbName, dbVersion = 1, onupgradeneeded = () => { }) => {
  85. return new Promise((resolve, reject) => {
  86. if (!dbName) {
  87. throw new Error('Param: dbName is mandatory!')
  88. }
  89. if (!window.indexedDB) {
  90. throw new Error('Your browser doesn\'t support a stable version of IndexedDB.')
  91. }
  92. const dbRequest = window.indexedDB.open(dbName, dbVersion)
  93. dbRequest.onsuccess = resolve
  94. dbRequest.onerror = reject
  95. dbRequest.onupgradeneeded = onupgradeneeded
  96. })
  97. }
  98. let dbExec = (documentDB, document, method = 'get') => {
  99. return new Promise((resolve, reject) => {
  100. documentDB.then(event => {
  101. const db = event.target.result
  102. createTransaction(db, 'document', 'readwrite').then(transaction => {
  103. createObjectStore(transaction, 'document').then(objectStore => {
  104. let req
  105. switch (method) {
  106. case 'put':
  107. req = objectStore.put(document)
  108. break;
  109. default:
  110. req = objectStore.get(document._id)
  111. break;
  112. }
  113. req.onsuccess = resolve
  114. req.onerror = reject
  115. })
  116. transaction.oncomplete = event => {
  117. console.log('Transaction completed!')
  118. }
  119. transaction.onerror = event => {
  120. console.log('Transaction error!')
  121. }
  122. transaction.onabort = event => {
  123. console.log('Transaction aborted!')
  124. }
  125. })
  126. }).catch(error => { throw error })
  127. })
  128. }
  129. const doFetch = endPoint => {
  130. fetch(endPoint)
  131. .then(response => response.text())
  132. .then(
  133. async data => {
  134. document.getElementById('main').innerHTML = data;
  135. loadScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js')
  136. .then(
  137. () => {
  138. loadScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/markdown/markdown.min.js')
  139. .then(
  140. () => {
  141. let editor = CodeMirror.fromTextArea(
  142. document.getElementById('editor'),
  143. {
  144. lineNumbers: true,
  145. mode: 'markdown',
  146. lineWrapping: true
  147. }
  148. );
  149. editor.setSize('100%', '100%');
  150. editor.on('change', instance => { instance.save() })
  151. }
  152. )
  153. .then(
  154. async () => {
  155. fetch('/config/AUTOSAVE_FREQ')
  156. .then(response => response.text())
  157. .then(autosaveFreq => {
  158. if (autosaveFreq > 0) {
  159. const documentDB = createDataBase(
  160. 'markdowns',
  161. 1,
  162. // On upgrade needed
  163. event => {
  164. const db = event.target.result
  165. if (!db.objectStoreNames.contains('document')) {
  166. db.createObjectStore('document', { keyPath: '_id' })
  167. }
  168. }
  169. )
  170. let formData = new FormData(document.querySelector('form'))
  171. let currentContent = JSON.stringify(Array.from(formData.entries()))
  172. dbExec(documentDB, {
  173. _id: 1,
  174. content: currentContent
  175. },
  176. 'put'
  177. ).then(event => console.log(event.target.result))
  178. intervalId = setInterval(
  179. () => {
  180. dbExec(
  181. documentDB,
  182. {
  183. _id: 1
  184. },
  185. 'get'
  186. ).then(event => {
  187. let formData = new FormData(document.querySelector('form'))
  188. let data = {}
  189. // Convert Iterator to Javascript Object
  190. for (const key of formData.keys()) {
  191. data[key] = formData.get(key)
  192. }
  193. // Convert Object to JSON
  194. let currentContent = JSON.stringify(data)
  195. savedContent = event.target.result.content
  196. if (savedContent != currentContent) {
  197. const headers = new Headers({
  198. "Content-Type": "application/json"
  199. })
  200. const request = new Request(
  201. '/posts', {
  202. headers: headers,
  203. method: formData.get('id') && 'PUT' || 'POST',
  204. body: currentContent
  205. }
  206. )
  207. fetch(request).then(response => console.log(response))
  208. } else {
  209. console.log('Content are identical no autosaving!!!')
  210. }
  211. })
  212. },
  213. autosaveFreq * 1000
  214. )
  215. }
  216. })
  217. }
  218. )
  219. }
  220. );
  221. }
  222. ).then(() => {
  223. document.getElementById('post').addEventListener('submit', (e) => {
  224. e.preventDefault()
  225. })
  226. })
  227. }
  228. // Edit Post form
  229. document.querySelector('#new').addEventListener('click', event => {
  230. const endPoint = event.currentTarget.id == 'new' ? '/posts/new' : `/posts/${event.currentTarget.dataset.id}/${event.currentTarget.id}`
  231. doFetch(endPoint)
  232. })