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

Prototype for node selection & copying elis #233

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
107 changes: 101 additions & 6 deletions frontend/src/components/RisLawPreview.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,132 @@
<script setup lang="ts">
withDefaults(
import { nextTick, ref, watch } from "vue"

/**
* Slots
*
* Slots with a name like "eid:part-1_part-2-..." are added into the rendering of the LDML.de preview to the end of the
* element with the given eid. E.g. the content of a slot with the name "eid:hauptteil-1_art-1" is added to the end of
* the rendering of the first article.
*/

const props = withDefaults(
defineProps<{
content: string
highlightMods?: boolean
highlightAffectedDocument?: boolean
selectedEids?: string[]
}>(),
{
highlightMods: false,
highlightAffectedDocument: false,
selectedEids: () => [],
},
)

const emit = defineEmits<{
"content:click": [
{
eid: string
guid: string
originalEvent: MouseEvent
},
]
}>()

function handleClick(e: MouseEvent) {
if (!(e.target instanceof HTMLElement)) {
return
}

if (!e.target.dataset.eid || !e.target.dataset.guid) {
return
}

emit("content:click", {
eid: e.target.dataset.eid,
guid: e.target.dataset.guid,
originalEvent: e,
})
}

function getElementByEid(eid: string) {
return document.querySelector(`[data-eId="${eid}"]`)
}

function difference<a>(xs: a[], ys: a[]): a[] {
return xs.filter((x) => ys.indexOf(x) === -1)
}

watch(
() => [...props.selectedEids],
(newElements: string[], oldElements: string[]) => {
const addedElements = difference(newElements, oldElements)
addedElements.forEach((element) => {
getElementByEid(element)?.classList.add("selected")
})

const removedElements = difference(oldElements, newElements)
removedElements.forEach((element) => {
getElementByEid(element)?.classList.remove("selected")
})
},
{ deep: true },
)

function teleportEidSlotNameToEid(slotName: string) {
return slotName.substring(4)
}

const contentHash = ref("")
watch(
() => props.content,
async () => {
await nextTick()
contentHash.value = props.content
props.selectedEids.forEach((element) => {
getElementByEid(element)?.classList.add("selected")
})
},
)
</script>

<template>
<div class="overflow-hidden">
<!-- eslint-disable vue/no-v-html -->
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events vuejs-accessibility/no-static-element-interactions -- think of something for this when moving this out of the prototype -->
<div
class="flex h-full overflow-auto bg-white p-8"
:class="{
'highlight-mods': highlightMods,
'highlight-affected-document': highlightAffectedDocument,
}"
@click="handleClick"
v-html="content"
></div>
<!-- eslint-enable vuejs-accessibility/click-events-have-key-events vuejs-accessibility/no-static-element-interactions -->
<!-- eslint-enable vue/no-v-html -->

<template
v-for="name in Object.keys($slots).filter((key) =>
key.startsWith('eid:'),
)"
:key="`${contentHash}-${name}`"
>
<Teleport
v-if="getElementByEid(teleportEidSlotNameToEid(name))"
:to="getElementByEid(teleportEidSlotNameToEid(name))"
>
<slot :name="name"></slot>
</Teleport>
</template>
</div>
</template>

<style scoped>
:deep([data-eId]) {
@apply align-top;
}

:deep(:is(table, thead, td)) {
@apply border border-blue-400;
}
Expand Down Expand Up @@ -80,12 +179,8 @@ withDefaults(
@apply text-lg;
}

:deep(.akn-paragraph) {
@apply flex flex-row;
}

:deep(.akn-paragraph .akn-num) {
@apply mr-8;
@apply float-left mr-8;
}

:deep(.akn-point) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/composables/useAmendingLawHtml.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("useAmendingLawHtml", () => {
const eli = ref(
"eli/bund/bgbl-1/1990/s2954/2022-12-19/1/deu/regelungstext-1",
)
const html = useAmendingLawHtml(eli)
const { html } = useAmendingLawHtml(eli)
await vi.waitUntil(() => html.value)

expect(html.value).toBe("<div></div>")
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/composables/useAmendingLawHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { getAmendingLawHtmlByEli } from "@/services/amendingLawsService"
*
* @returns A reference to the amending law rendered HTML or undefined if it is not available (or still loading).
*/
export function useAmendingLawHtml(
eli: MaybeRefOrGetter<string | undefined>,
): Readonly<Ref<string | undefined>> {
export function useAmendingLawHtml(eli: MaybeRefOrGetter<string | undefined>): {
html: Readonly<Ref<string | undefined>>
update: () => unknown
} {
const amendingLawHtml = ref<string>()

watch(
Expand All @@ -24,5 +25,13 @@ export function useAmendingLawHtml(
{ immediate: true },
)

return readonly(amendingLawHtml)
return {
html: readonly(amendingLawHtml),
update: async () => {
const eliValue = toValue(eli)
if (eliValue) {
amendingLawHtml.value = await getAmendingLawHtmlByEli(eliValue)
}
},
}
}
28 changes: 28 additions & 0 deletions frontend/src/composables/useTargetLawHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MaybeRefOrGetter, Ref, readonly, ref, toValue, watch } from "vue"
import { getTargetLawHtmlByEli } from "@/services/targetLawsService"

/**
* Get the rendered HTML of an amending law.
*
* @param eli a reference to the eli for which the law xml will be returned.
* Changing the value of the reference will load the data for the new eli.
*
* @returns A reference to the amending law rendered HTML or undefined if it is not available (or still loading).
*/
export function useTargetLawHtml(
eli: MaybeRefOrGetter<string | undefined>,
): Readonly<Ref<string | undefined>> {
const amendingLawHtml = ref<string>()

watch(
() => toValue(eli),
async (eli) => {
if (eli) {
amendingLawHtml.value = await getTargetLawHtmlByEli(eli)
}
},
{ immediate: true },
)

return readonly(amendingLawHtml)
}
22 changes: 22 additions & 0 deletions frontend/src/services/eidService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type EidPart = "ändbefehl" | "bezugsdoc" | "text" | "intro"

function takeUntil<T>(xs: T[], predicate: (x: T) => boolean): T[] {
const res = []
for (const x of xs) {
res.push(x)
if (predicate(x)) {
break
}
}
return res
}

export function reduceEidToPart(eid: string, part: EidPart): string {
return takeUntil(eid.split("_"), (eidPart) => eidPart.startsWith(part)).join(
"_",
)
}

export function eidHasPart(eid: string, part: EidPart): boolean {
return eid.includes(part)
}
60 changes: 60 additions & 0 deletions frontend/src/services/ldmlDeService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export function evaluateXPath(xpath: string, xml: Document) {
return xml
.createExpression(xpath, (prefix) => {
switch (prefix) {
case "akn":
return "http://Inhaltsdaten.LegalDocML.de/1.6/"
}
return null
})
.evaluate(xml)
.iterateNext()
}

function getChangeTypeNode(xml: Document, eid: string) {
return evaluateXPath(`//*[@eId="${eid}"]/@refersTo`, xml)
}

export function getChangeType(xml: Document, eid: string) {
return getChangeTypeNode(xml, eid)?.nodeValue
}

export function setChangeType(
xml: Document,
eid: string,
changeType: string,
): Document {
const node = getChangeTypeNode(xml, eid)
if (node) {
node.nodeValue = changeType
}
return xml
}

export function getChangeRefHref(xml: Document, eid: string) {
return evaluateXPath(`//*[@eId="${eid}"]/akn:ref/@href`, xml)?.nodeValue
}

function getChangeNewTextNode(xml: Document, eid: string) {
return evaluateXPath(`(//*[@eId="${eid}"]/akn:quotedText)[2]`, xml)
}

export function getChangeNewText(xml: Document, eid: string) {
return getChangeNewTextNode(xml, eid)?.textContent
}

export function getTargetLawHref(xml: Document, eid: string) {
return evaluateXPath(`//*[@eId="${eid}"]/@href`, xml)?.nodeValue
}

export function setChangeNewText(
xml: Document,
eid: string,
newText: string,
): Document {
const node = getChangeNewTextNode(xml, eid)
if (node) {
node.textContent = newText
}
return xml
}
7 changes: 7 additions & 0 deletions frontend/src/services/xmlService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function xmlStringToDocument(xmlString: string): Document {
return new DOMParser().parseFromString(xmlString, "application/xml")
}

export function xmlDocumentToString(xmlDoc: Document): string {
return new XMLSerializer().serializeToString(xmlDoc)
}