diff --git a/edit/index.html b/edit/index.html index 503f5bb..59bb2a2 100644 --- a/edit/index.html +++ b/edit/index.html @@ -84,10 +84,25 @@ cursor: pointer; letter-spacing: 0.5px; } - #save:hover{ + #save_button:hover{ border-color: rgb(13, 71, 161)!important; background: linear-gradient(rgb(5, 79, 163) 0%, rgb(3, 71, 147) 100%) rgb(33, 83, 159)!important; } + #save_button:disabled{ + opacity: 0.5; + cursor: not-allowed; + position: relative; + } + + #save_button:disabled:hover:before{ + content: attr(data-text); + position: absolute; + color: black; + top: 0px; + left: -150px; + line-height: 35px; + } + #cancel_button { background: linear-gradient(rgb(211, 47, 47) 0%, rgb(183, 28, 28) 100%) rgb(185, 36, 43); } @@ -102,9 +117,9 @@ - Documentation for the Dashboard Information widget can be found here. + Documentation for the Dashboard Information widget can be found here.
- +
diff --git a/edit/package.json b/edit/package.json index b40ae86..294c09c 100644 --- a/edit/package.json +++ b/edit/package.json @@ -11,6 +11,7 @@ }, "devDependencies": { "@testing-library/dom": "^9.3.4", + "@types/node": "^20.11.30", "happy-dom": "^13.7.3", "typescript": "^5.2.2", "vite": "^5.1.6", diff --git a/edit/pnpm-lock.yaml b/edit/pnpm-lock.yaml index eb29fc0..ff32d3f 100644 --- a/edit/pnpm-lock.yaml +++ b/edit/pnpm-lock.yaml @@ -16,6 +16,9 @@ devDependencies: '@testing-library/dom': specifier: ^9.3.4 version: 9.3.4 + '@types/node': + specifier: ^20.11.30 + version: 20.11.30 happy-dom: specifier: ^13.7.3 version: 13.7.3 @@ -24,10 +27,10 @@ devDependencies: version: 5.4.2 vite: specifier: ^5.1.6 - version: 5.1.6 + version: 5.1.6(@types/node@20.11.30) vitest: specifier: ^1.3.1 - version: 1.3.1(happy-dom@13.7.3) + version: 1.3.1(@types/node@20.11.30)(happy-dom@13.7.3) packages: @@ -408,6 +411,12 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true + /@types/node@20.11.30: + resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} + dependencies: + undici-types: 5.26.5 + dev: true + /@vitest/expect@1.3.1: resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} dependencies: @@ -1335,7 +1344,11 @@ packages: resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} dev: true - /vite-node@1.3.1: + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /vite-node@1.3.1(@types/node@20.11.30): resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1344,7 +1357,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.1.6 + vite: 5.1.6(@types/node@20.11.30) transitivePeerDependencies: - '@types/node' - less @@ -1356,7 +1369,7 @@ packages: - terser dev: true - /vite@5.1.6: + /vite@5.1.6(@types/node@20.11.30): resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1384,6 +1397,7 @@ packages: terser: optional: true dependencies: + '@types/node': 20.11.30 esbuild: 0.19.12 postcss: 8.4.35 rollup: 4.12.1 @@ -1391,7 +1405,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.3.1(happy-dom@13.7.3): + /vitest@1.3.1(@types/node@20.11.30)(happy-dom@13.7.3): resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1416,6 +1430,7 @@ packages: jsdom: optional: true dependencies: + '@types/node': 20.11.30 '@vitest/expect': 1.3.1 '@vitest/runner': 1.3.1 '@vitest/snapshot': 1.3.1 @@ -1434,8 +1449,8 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.1.6 - vite-node: 1.3.1 + vite: 5.1.6(@types/node@20.11.30) + vite-node: 1.3.1(@types/node@20.11.30) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/edit/src/index.ts b/edit/src/index.ts index 4df9014..ad4116b 100644 --- a/edit/src/index.ts +++ b/edit/src/index.ts @@ -2,11 +2,12 @@ import {fetchContent} from "./services/fetchContent.service.ts"; import {sanitizeContent} from "./services/sanitizeContent.service.ts"; import {renderEditor} from "./services/renderEditor.service.ts"; import {initCancelButton} from "./services/initCancelButton.service.ts"; - +import {setOriginalContent} from "./services/originalContent.var.ts"; (async ()=>{ initCancelButton() const rawContent:string = await fetchContent() || '

New Dashboard Information widget

' const safeContent = sanitizeContent(rawContent) renderEditor(safeContent) + setOriginalContent(safeContent) })() diff --git a/edit/src/services/initSaveButton.service.ts b/edit/src/services/initSaveButton.service.ts new file mode 100644 index 0000000..994ae9d --- /dev/null +++ b/edit/src/services/initSaveButton.service.ts @@ -0,0 +1,14 @@ +import {Jodit} from "jodit/esm/index.js"; +import {getOriginalContent} from "./originalContent.var.ts"; + +let changed = false + +export function initSaveButton(editor: Jodit){ + editor.events.on('change',(content)=>{ + if (changed) return + if (content===getOriginalContent()) return + const saveButton = document.getElementById('save_button')! + saveButton.removeAttribute('disabled') + changed = true + }) +} \ No newline at end of file diff --git a/edit/src/services/originalContent.var.ts b/edit/src/services/originalContent.var.ts new file mode 100644 index 0000000..c763e85 --- /dev/null +++ b/edit/src/services/originalContent.var.ts @@ -0,0 +1,9 @@ +let originalContent:string + +export function setOriginalContent(content:string):void{ + originalContent = content +} + +export function getOriginalContent():string{ + return originalContent +} diff --git a/edit/src/services/renderEditor.service.ts b/edit/src/services/renderEditor.service.ts index 4ca7471..517f012 100644 --- a/edit/src/services/renderEditor.service.ts +++ b/edit/src/services/renderEditor.service.ts @@ -1,4 +1,3 @@ -// import {Jodit} from 'jodit' import {Jodit} from 'jodit/esm/index.js'; import 'jodit/esm/plugins/video/video.js' import 'jodit/esm/plugins/source/source.js' @@ -6,11 +5,11 @@ import 'jodit/esm/plugins/indent/indent.js' import 'jodit/esm/plugins/clean-html/clean-html.js' import 'jodit/esm/plugins/hr/hr.js' import 'jodit/es2021/jodit.css' - import {editorConfig} from "../const/jodit.config.ts"; - +import {initSaveButton} from "./initSaveButton.service.ts"; export async function renderEditor(content:string):Promise{ document.getElementById('editor-container')!.innerHTML = `` const editor = Jodit.make('#editor', editorConfig); editor.value = content + initSaveButton(editor) } \ No newline at end of file diff --git a/edit/src/test/menu/4.menuNavigation.test.ts b/edit/src/test/menu/4.menuNavigation.test.ts deleted file mode 100644 index ccbd602..0000000 --- a/edit/src/test/menu/4.menuNavigation.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {initDom, mockFetch} from "../test.service.ts"; -import {dataStore, explore} from "./menu.shared.ts"; - -test(`4 > Menu navigation`, async ()=>{ - mockFetch(dataStore) - initDom() - await import('../../index.ts') - await explore('Data Analysis') - await explore('Thematic Dashboards') - await explore('COP22/FY23') - await explore('Clinical Cascade ↗') -}) \ No newline at end of file diff --git a/edit/src/test/menu/5.menuStyles.test.ts b/edit/src/test/menu/5.menuStyles.test.ts deleted file mode 100644 index 4c20f7d..0000000 --- a/edit/src/test/menu/5.menuStyles.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {initDom, mockFetch} from "../test.service.ts"; -import {screen} from '@testing-library/dom' -import {dataStore, explore} from "./menu.shared.ts"; -import {expect} from "vitest"; - - -test(`4 > Menu navigation`, async ()=>{ - mockFetch(dataStore) - initDom() - await import('../../index.ts') - await explore('Data Analysis') - await explore('Thematic Dashboards') - expect(screen.getByText('Data Analysis').style.fontWeight).toBe("500") - expect(screen.getByText('Thematic Dashboards').style.fontWeight).toBe("") - expect(document.getElementById('subMenuRoot_0')!.style.height).toBe('200px') -}) \ No newline at end of file diff --git a/edit/src/test/menu/menu.shared.ts b/edit/src/test/menu/menu.shared.ts deleted file mode 100644 index 8ef3987..0000000 --- a/edit/src/test/menu/menu.shared.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {MapOf} from "../test.service.ts"; -import {fireEvent, screen} from "@testing-library/dom"; - -export const dataStore: MapOf = { - '/api/dataStore/dashboard-information/WidgetId': { - "body": "

 

\n
Data Review {font-weight: 500}:\n  MER Result and Target Review Favorites: /dhis-web-dashboard/#/ddwNwfGWWsp\n  World AIDS Day Review: /dhis-web-dashboard/#/SEmvZjJIXrg\n  Dedupe Dashboard: /dhis-web-dashboard/#/i3OhPlqNTcr\n  Approvals Dashboard: /dhis-web-dashboard/#/CWa3HjzJMi5\n  SRE Dashboard: /dhis-web-dashboard/#/aDgKm3fFLO7\nData Analysis {font-weight: 500}:\n  {min-width: 200px}:\n  Thematic Dashboards:\n    COP19/FY20: \n      Clinical Cascade: /dhis-web-dashboard/#/d1saiOSfQBd\n      Testing: /dhis-web-dashboard/#/hCUxXRhOKRs\n      Treatment: /dhis-web-dashboard/#/DG71ebj5nDR\n      PMTCT: /dhis-web-dashboard/#/WUMxLpjpxsC\n      Key Pops and Prevention: /dhis-web-dashboard/#/qT16cnSZgyC\n      Cervical Cancer: /dhis-web-dashboard/#/sD0NuEg14eu\n      TB Cascade: /dhis-web-dashboard/#/MKN9JiZXg6n\n    COP20/FY21:\n      Clinical Cascade: /dhis-web-dashboard/#/lCeICs4hLbi\n      Testing: /dhis-web-dashboard/#/t03bMEn2mBm\n      Treatment: /dhis-web-dashboard/#/JH3rXxLrnlR\n      PMTCT: /dhis-web-dashboard/#/T5jQaKxK3hX\n      Key Pops and Prevention: /dhis-web-dashboard/#/FHOuaQrsU1f\n      Cervical Cancer: /dhis-web-dashboard/#/SRyMbcVjLsG\n      TB Cascade: /dhis-web-dashboard/#/H65uRxO34pA\n    COP21/FY22:\n      Clinical Cascade: /dhis-web-dashboard/#/HviR0NxaVEB\n      Testing: /dhis-web-dashboard/#/tNYZKTpVK9s\n      Treatment: /dhis-web-dashboard/#/ghrQkAsEK11\n      PMTCT: /dhis-web-dashboard/#/EL1xKf4BcZD\n      Key Pops and Prevention: /dhis-web-dashboard/#/mfNqvH2GLki\n      Cervical Cancer: /dhis-web-dashboard/#/wCnSPimAwYp\n      TB Cascade: /dhis-web-dashboard/#/HpRmwxSGmnS\n    COP22/FY23:\n      Clinical Cascade: /dhis-web-dashboard/#/y1Rk1rBpYiL\n      Testing: /dhis-web-dashboard/#/MC0FlH7lVuO\n      Treatment: /dhis-web-dashboard/#/ckV2ZOdeymW\n      PMTCT: /dhis-web-dashboard/#/vg1EJP4mraq\n      Key Pops and Prevention: /dhis-web-dashboard/#/llyXwqQKtxG\n      Cervical Cancer: /dhis-web-dashboard/#/QQfy2j27kJC\n      TB Cascade: /dhis-web-dashboard/#/caMTdhcKA6V\n  COP Analysis Dashboards:\n    COP20: /dhis-web-dashboard/#/lsvZYSi0GEC\n    COP21: /dhis-web-dashboard/#/X5NlX7LbO6g\n    COP22: /dhis-web-dashboard/#/jdjFPxonDQL\n    COP23: \n        Analysis Dashboard: /dhis-web-dashboard/#/EYVNCtpipx8\n        Review Dashboard: /dhis-web-dashboard/#/UwPIkZjFY0U\n\n
" - } -} - -export async function explore(node: string): Promise { - await screen.findByText(node) - fireEvent.click(screen.getByText(node)) -} \ No newline at end of file diff --git a/edit/src/test/test.service.ts b/edit/src/test/test.service.ts index bf4e5be..8696ef1 100644 --- a/edit/src/test/test.service.ts +++ b/edit/src/test/test.service.ts @@ -1,6 +1,7 @@ +import { readFileSync } from 'fs'; + export type MapOf = {[key:string]:T} export function mockFetch(urlList:MapOf):void{ - //@ts-expect-error global not defined global.fetch = vitest.fn().mockImplementation((url:string)=>{ console.log('mocking url', url) if (!urlList[url]) throw new Error(`URL is not mocked ${url}`) @@ -9,6 +10,8 @@ export function mockFetch(urlList:MapOf):void{ } export function initDom():void{ - document.body.innerHTML = `
` + const index:string = readFileSync('./index.html').toString() + const body:string = /.+<\/body>/s.exec(index)![0].replace(//, '') + document.body.innerHTML = body window.location.search = '?dashboardItemId=WidgetId' } \ No newline at end of file diff --git a/edit/src/test/widget/1.render.test.ts b/edit/src/test/widget/1.render.test.ts index a52d281..92e3858 100644 --- a/edit/src/test/widget/1.render.test.ts +++ b/edit/src/test/widget/1.render.test.ts @@ -1,5 +1,6 @@ import {initDom, MapOf, mockFetch} from "../test.service.ts"; import {screen} from '@testing-library/dom' +import {expect} from "vitest"; const dataStore:MapOf = { '/api/dataStore/dashboard-information/WidgetId': { @@ -7,9 +8,12 @@ const dataStore:MapOf = { } } -test(`1 > Render Widget`, async ()=>{ +test(`1 > Render Editor`, async ()=>{ mockFetch(dataStore) initDom() await import('../../index.ts') screen.getByText('Widget content') + const documentationLink = screen.getByText('Documentation for the Dashboard Information widget can be found here.') + expect(documentationLink.getAttribute('href')).toBe('https://github.com/pepfar-datim/dashboard-information-widget') + }) \ No newline at end of file diff --git a/edit/src/test/widget/2.sanitizeContent.test.ts b/edit/src/test/widget/2.sanitizeContent.test.ts deleted file mode 100644 index 38a783a..0000000 --- a/edit/src/test/widget/2.sanitizeContent.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {initDom, MapOf, mockFetch} from "../test.service.ts"; -import {screen} from '@testing-library/dom' - -const dataStore:MapOf = { - '/api/dataStore/dashboard-information/WidgetId': { - "body": "content" - } -} - -test(`2 > Sanitize Content`, async ()=>{ - mockFetch(dataStore) - initDom() - await import('../../index.ts') - screen.getByText('content') - expect(document.body.innerHTML).not.includes('console.log') -}) \ No newline at end of file diff --git a/edit/src/test/widget/3.sanitizer.test.ts b/edit/src/test/widget/3.sanitizer.test.ts deleted file mode 100644 index da10231..0000000 --- a/edit/src/test/widget/3.sanitizer.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {sanitizeContent} from "../../services/sanitizeContent.service.ts"; -import {expect} from "vitest"; - - -const testCases:string[][] = [ - ['',''], - ['content','content'], - ['middle',''], - ['