Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typescript support #831

Open
wants to merge 4 commits into
base: chore/update-editor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/serious-lamps-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'frontend-gelinkt-notuleren': minor
---

Add typescript support
2 changes: 1 addition & 1 deletion .ember-cli
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
*/
"isTypeScriptProject": false
"isTypeScriptProject": true
}
25 changes: 0 additions & 25 deletions .eslintignore

This file was deleted.

71 changes: 0 additions & 71 deletions .eslintrc.js

This file was deleted.

5 changes: 5 additions & 0 deletions .woodpecker/.test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ steps:
image: danlynn/ember-cli:5.12.0-node_20.18
commands:
- npm ci
lint-types:
image: node:20-slim
group: lint
commands:
- npm run lint:types
lint-js:
image: node:20-slim
group: lint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,27 @@ import { trackedReset } from 'tracked-toolbox';
import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
import { copyStringToClipboard } from '../utils/copy-string-to-clipboard';
import { stripHtmlForPublish } from '@lblod/ember-rdfa-editor/utils/strip-html-for-publish';

class DownloadButton extends Component {
@service intl;
import type IntlService from 'ember-intl/services/intl';
type Section = {
label?: string;
selector?: string;
content?: string;
parts?: Section[];
translatedLabel?: string;
contentSelector?: string;
callback?: (element: Element) => Element | null | undefined;
labelSelector?: string;
labelCallback?: (element: Element) => Element;
};
interface Sig {
Element: HTMLButtonElement;
Args: {
translatedLabel?: string;
section: Section;
};
}
class DownloadButton extends Component<Sig> {
@service declare intl: IntlService;

get isSuccess() {
return this.copyToClipboard.last?.isSuccessful;
Expand All @@ -20,11 +38,19 @@ class DownloadButton extends Component {
return this.isSuccess ? 'circle-check' : undefined;
}
get label() {
return this.args.translatedLabel ?? this.intl.t(this.args.section.label);
if (this.args.translatedLabel) {
return this.args.translatedLabel;
}
if (this.args.section.label) {
return this.intl.t(this.args.section.label);
}
return '';
}

copyToClipboard = task(async () => {
await copyStringToClipboard({ html: this.args.section.content.trim() });
await copyStringToClipboard({
html: this.args.section.content?.trim() ?? '',
});
});

<template>
Expand All @@ -46,7 +72,7 @@ class DownloadButton extends Component {
</template>
}

const SECTIONS = [
const SECTIONS: Section[] = [
{
label: 'copy-options.section.title',
selector:
Expand Down Expand Up @@ -100,51 +126,63 @@ const SECTIONS = [
},
];

function htmlSafer(text) {
return htmlSafe(text);
function htmlSafer(text?: string) {
return htmlSafe(text ?? '');
}

// This method of looking for query selectors is error-prone as it assumes that the document follows
// the current DOM output specs. This is not necessarily true of historic or future documents. It
// would be better to either use an RDFa parser that can also return the elements associated with
// relations or a headless prosemirror instance.
function update(component) {
function update(component: DecisionCopyParts): Section[] | undefined {
const parser = new DOMParser();
const parsed = parser.parseFromString(
stripHtmlForPublish(component.args.decision.content),
stripHtmlForPublish(component.args.decision.content ?? ''),
'text/html',
);
const temporaryRenderingSpace = document.createElement('div');
document.firstElementChild.appendChild(temporaryRenderingSpace);
const firstChild = document.firstElementChild;
if (!firstChild) {
return;
}
firstChild.appendChild(temporaryRenderingSpace);
const mappedSections = SECTIONS.flatMap(
({ label, selector, parts, callback = (a) => a }) => {
const elements = Array.from(parsed.querySelectorAll(selector));
({ label, selector, parts, callback = (a: Element) => a }) => {
const elements = selector
? Array.from(parsed.querySelectorAll(selector))
: [];
return elements.map((element) => {
const contentElement = callback(element);
// Note, it's important to generate the content here as with the use of DOM apis in the
// callbacks, it's easy to accidentally mutate `contentElement`. For example when appending
// parts of the content to a 'container' element.
const contentHtml = contentElement.outerHTML;
let foundParts = [];
const contentHtml = contentElement?.outerHTML;
const foundParts: Section[] = [];
if (parts) {
for (let partType of parts) {
for (const partType of parts) {
const partCb = partType.callback || ((a) => a);
const partElements =
contentElement.querySelectorAll(partType.selector) ?? [];
const partElements = partType.selector
? contentElement?.querySelectorAll(partType.selector) ?? []
: [];
partElements.forEach((part) => {
const partElement = partCb(part);
const partLabel = partType.labelCallback
? partType.labelCallback(part)
: partElement.querySelector(partType.labelSelector);

let partLabel;
if (partType.labelCallback) {
partLabel = partType.labelCallback(part);
} else if (partType.labelSelector) {
partLabel = partElement?.querySelector(partType.labelSelector);
}
const partContent = partType.contentSelector
? partElement.querySelector(partType.contentSelector)?.outerHTML
: partElement.outerHTML;
? partElement?.querySelector(partType.contentSelector)
?.outerHTML
: partElement?.outerHTML;
if (partLabel && partContent) {
// Put the element into the DOM so that `innerText` can know which parts of the
// content are human readable in `innerText`
temporaryRenderingSpace.replaceChildren(partLabel);
foundParts.push({
translatedLabel: partLabel.innerText,
translatedLabel: (partLabel as HTMLElement).innerText,
content: partContent,
});
}
Expand All @@ -164,20 +202,28 @@ function update(component) {
return mappedSections;
}

export default class DecisionCopyParts extends Component {
interface DecisionCopyPartsSig {
Args: {
decision: Section;
};
}
export default class DecisionCopyParts extends Component<DecisionCopyPartsSig> {
@service declare intl: IntlService;
@trackedReset({
memo: 'decision.content',
update,
})
sections = update(this);
sectionLabel = (section: Section) =>
section.label ? this.intl.t(section.label) : '';

<template>
<div class='au-o-flow--small au-u-3-5'>
{{#each this.sections as |section|}}
<div class='gn-meeting-copy--section-container'>
<div class='gn-meeting-copy--structure'>
<div class='gn-meeting-copy--structure-header'>
{{t section.label}}
{{this.sectionLabel section}}
</div>
{{#each section.parts as |part|}}
<div class='gn-meeting-copy--structure-content'>
Expand Down
1 change: 0 additions & 1 deletion app/components/delete-meeting-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export default class DeleteMeetingComponent extends Component {
pollWhileMeetingExists = task(async (id) => {
await timeout(100);
try {
// eslint-disable-next-line no-constant-condition
let response = await fetch(`/zittingen/${id}`);
while (response.ok) {
await timeout(100);
Expand Down
1 change: 0 additions & 1 deletion app/config/mandatee-table-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,6 @@ export const RMW_TAGS = /** @type {const} */ ([
* @typedef {typeof RMW_TAGS[number]} RMW_TAG
* @returns {Record<RMW_TAG, unknown>}
*/
// eslint-disable-next-line no-unused-vars
export const mandateeTableConfigRMW = (meeting) => {
return {
/**
Expand Down
2 changes: 1 addition & 1 deletion app/services/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default class PublishService extends Service {
if (json?.errors) {
errors = json.errors[0]?.title || JSON.stringify(json.errors);
}
} catch (e) {
} catch (_e) {
// throwing body text
errors = await resp.text();
throw new Error(errors);
Expand Down
1 change: 0 additions & 1 deletion app/services/store.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
import Store from 'ember-data/store';
import ArrayProxy from '@ember/array/proxy';

Expand Down
1 change: 1 addition & 0 deletions ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = function (defaults) {
},
'ember-cli-babel': {
includePolyfill: false,
enableTypeScriptTransform: true,
},
minifyCSS: {
enabled: envIsProduction,
Expand Down
Loading