Skip to content

Commit

Permalink
Merge branch 'main' into indigo-v2-to-v3
Browse files Browse the repository at this point in the history
  • Loading branch information
nickmwangemi committed Nov 2, 2023
2 parents 71a8a8f + fb0f0df commit 52590a1
Show file tree
Hide file tree
Showing 24 changed files with 398 additions and 77 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,23 @@ jobs:
git_remote_url: 'ssh://[email protected]:22/obl-microsites'
git_push_flags: '--force'

deploy-rwandalii:
if: ${{ always() }}
needs: deploy-nigerialii
name: Deploy to rwandalii
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: git push to rwandalii.laws.africa
uses: dokku/github-action@master
with:
ssh_private_key: ${{ secrets.SSH_DEPLOYMENT_KEY }}
git_remote_url: 'ssh://[email protected]:22/rwandalii'
git_push_flags: '--force'

deploy-lii:
if: ${{ always() }}
needs: deploy-eswatinilii
Expand Down
19 changes: 18 additions & 1 deletion peachjam/analysis/citations.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,30 @@ def extract_text_matches(self, frbr_uri, text):
)
text = text[: self.max_text_size]

# For text documents, we need to provide the existing citations for context. For html, existing citations
# are already marked up in the HTML.
citations = [
c.to_citator_api()
for c in CitationLink.objects.filter(
document__expression_frbr_uri=frbr_uri.expression_uri()
)
]

resp = self.call_citator(
{
"frbr_uri": frbr_uri.expression_uri(),
"format": "text",
"body": text,
"citations": citations,
}
)

# only keep new citations
existing = {(c["start"], c["end"]) for c in citations}
citations = [
c for c in resp["citations"] if (c["start"], c["end"]) not in existing
]

# store the extracted citations
self.citations = [
ExtractedCitation(
Expand All @@ -127,7 +144,7 @@ def extract_text_matches(self, frbr_uri, text):
c["prefix"],
c["suffix"],
)
for c in resp["citations"]
for c in citations
]

def call_citator(self, body):
Expand Down
157 changes: 93 additions & 64 deletions peachjam/js/components/DocumentContent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,28 @@ class OffCanvas {
class DocumentContent {
protected root: HTMLElement;
private pdfRenderer: PdfRenderer | undefined;
private searchApp: any;
private navOffCanvas: OffCanvas | undefined;
private enchrichmentsManager: EnrichmentsManager | null = null;
private enrichmentsManager: EnrichmentsManager | null = null;
private originalDocument: HTMLElement | undefined;
private tocController: HTMLElement| null = null;
private documentElement: Element | null;
private tocShowActiveItemOnly: boolean = false;
private displayType: string;
private tocItemIndex = new Map<String, any>();
private activeTocItem: any = null;

constructor (root: HTMLElement) {
this.root = root;
this.documentElement = this.root.querySelector('.content');
this.displayType = this.root.getAttribute('data-display-type') || '';

this.setupTabs();
this.setupNav();
this.setupOffCanvasNav();
this.setupPdf();
this.setupSearch();
this.setupEnrichments();
this.setSharedPortion();
if (this.root.getAttribute('data-display-type') !== 'pdf') {
if (this.displayType !== 'pdf') {
// for PDFs, this will be done once the pages are rendered
this.setupPopups();
}
Expand All @@ -54,14 +58,14 @@ class DocumentContent {
const tocTabTriggerEl = this.root.querySelector('#toc-tab');
const searchTabTriggerEl = this.root.querySelector('#navigation-search-tab');
const pdfPreviewsTabTriggerEl = this.root.querySelector('#pdf-previews-tab');
const tocSetupOnTab = this.setupTocForTab();
const tocCreated = this.setupToc();

// If toc setup and mounted successfully, activate toc tab otherwise activate search tab
if (tocSetupOnTab && tocTabTriggerEl) {
if (tocCreated && tocTabTriggerEl) {
tocTabTriggerEl.classList.remove('d-none');
const tocTab = new window.bootstrap.Tab(tocTabTriggerEl);
tocTab.show();
} else if (this.root.getAttribute('data-display-type') === 'pdf' && pdfPreviewsTabTriggerEl) {
} else if (this.displayType === 'pdf' && pdfPreviewsTabTriggerEl) {
const pdfPreviewsTab = new window.bootstrap.Tab(pdfPreviewsTabTriggerEl);
pdfPreviewsTab.show();
} else if (searchTabTriggerEl) {
Expand All @@ -70,7 +74,7 @@ class DocumentContent {
}
}

setupNav () {
setupOffCanvasNav () {
const navColumn: HTMLElement | null = this.root.querySelector('#navigation-column');
const navContent: HTMLElement | null = this.root.querySelector('#navigation-content .navigation__inner');
const navOffCanvasElement: HTMLElement | null = this.root.querySelector('#navigation-offcanvas');
Expand All @@ -81,21 +85,11 @@ class DocumentContent {
this.setupResponsiveContentTransporter(navColumn, this.navOffCanvas.body, navContent);
}
}

// Close navOffCanvas on lac-toc title click
if (this.root.getAttribute('data-display-type') === 'akn') {
const element = this.root.querySelector('la-table-of-contents-controller');
if (element) {
element.addEventListener('itemTitleClicked', () => {
this.navOffCanvas?.hide();
});
}
}
}

setupPdf () {
// if pdf setup pdf renderer instance
if (this.root.getAttribute('data-display-type') === 'pdf') {
if (this.displayType === 'pdf') {
// get dataset attributes
const pdfAttrsElement: HTMLElement | null = this.root.querySelector('[data-pdf]');
if (pdfAttrsElement) {
Expand All @@ -108,8 +102,8 @@ class DocumentContent {
this.navOffCanvas?.hide();
};
this.pdfRenderer.onPdfLoaded = () => {
if (this.enchrichmentsManager) {
this.enchrichmentsManager.setupPdfCitationLinks();
if (this.enrichmentsManager) {
this.enrichmentsManager.setupPdfCitationLinks();
}
this.setupPopups();

Expand All @@ -124,11 +118,11 @@ class DocumentContent {
setupSearch () {
const targetMountElement = this.root.querySelector('[data-doc-search]');
if (targetMountElement) {
this.searchApp = createAndMountApp({
createAndMountApp({
component: DocumentSearch,
props: {
document: this.documentElement,
docType: this.root.getAttribute('data-display-type'),
docType: this.displayType,
mountElement: targetMountElement
},
use: [vueI18n],
Expand Down Expand Up @@ -168,47 +162,23 @@ class DocumentContent {
, 200));
}

/**
* Setup the TOC so that when an item is activated, only the content for that item is shown in the document view.
*/
setupTocShowActiveItemOnly () {
if (this.documentElement) {
this.originalDocument = this.documentElement.cloneNode(true) as HTMLElement;
setupToc () {
this.tocShowActiveItemOnly = this.root.hasAttribute('data-toc-show-active-item-only');

this.tocController?.addEventListener('itemTitleClicked', (e) => {
if (this.originalDocument && this.documentElement) {
const id = (e as CustomEvent).detail.target.getAttribute('href');
if (!id || id === '#') {
this.documentElement.replaceChildren(...Array.from(this.originalDocument.children).map(node => node.cloneNode(true)));
} else {
// @ts-ignore
const sectionOfFocus = this.originalDocument.querySelector(id)?.cloneNode(true) as HTMLElement;
if (sectionOfFocus) {
// Delete content within document element and then append section of focus
this.documentElement.replaceChildren(sectionOfFocus);
}
}
}
});
}
}

setupTocForTab () {
// If there is no toc item don't create and mount la-toc-controller
const tocItems = this.getTocItems();

if (this.root.hasAttribute('data-toc-show-active-item-only')) {
// Add a "Show full text" item to the top of the TOC when the "show active item only" option is enabled
tocItems.unshift({
tag: 'H1',
title: i18next.t('Show full text'),
id: '',
children: []
});
}

if (!tocItems.length) return false;

// index from id to TOC items
const indexItems = (items: any[]) => {
for (const item of items) {
// @ts-ignore
this.tocItemIndex.set(item.id, item);
indexItems(item.children);
}
};
indexItems(tocItems);

this.tocController = createTocController(tocItems);
// @ts-ignore
this.tocController.titleFilterPlaceholder = i18next.t('Search table of contents');
Expand All @@ -217,41 +187,71 @@ class DocumentContent {
if (!tocContainer) return;
tocContainer.appendChild(this.tocController);

if (this.root.hasAttribute('data-toc-show-active-item-only')) {
this.setupTocShowActiveItemOnly();
if (this.tocShowActiveItemOnly && this.documentElement) {
this.originalDocument = this.documentElement.cloneNode(true) as HTMLElement;
}

this.tocController.addEventListener('itemTitleClicked', (e) => {
// @ts-ignore
this.setSharedPortion(e.target?.item?.title);
// hide off-canvas nav, if necessary
this.navOffCanvas?.hide();
if (this.tocShowActiveItemOnly) {
this.showDocumentPortion((e as CustomEvent).detail.target.getAttribute('href'));
}
});

// now that the page has loaded, check if there is a hash in the URL and if so, activate the corresponding TOC item
if (window.location.hash) {
this.showDocumentPortion(window.location.hash);
}

return true;
}

getTocItems = () => {
let items = [];

if (this.root.getAttribute('data-display-type') === 'akn') {
if (this.displayType === 'akn') {
const tocElement: HTMLElement | null = this.root.querySelector('#akn_toc_json');
if (tocElement) {
items = JSON.parse(tocElement.textContent as string) || [];
}
} else if (this.root.getAttribute('data-display-type') === 'html') {
} else if (this.displayType === 'html') {
const content: HTMLElement | null = this.root.querySelector('.content__html');
if (content) {
items = generateHtmlTocItems(content);
wrapTocItems(content, items);
}
}

if (this.tocShowActiveItemOnly) {
// Add a "Show full text" item to the top of the TOC when the "show active item only" option is enabled
items.unshift({
tag: 'H1',
title: i18next.t('Show full text'),
id: '',
children: []
});
}

// recursively add parent and depth information
function addParentAndDepth (items: any[], parent: any = null, depth: number = 1) {
for (const item of items) {
item.parent = parent;
item.depth = depth;
addParentAndDepth(item.children, item, depth + 1);
}
}
addParentAndDepth(items);

return items;
}

setupEnrichments () {
const contentAndEnrichmentsElement = this.root.querySelector('.content-and-enrichments');
if (!contentAndEnrichmentsElement) return;
this.enchrichmentsManager = new EnrichmentsManager(contentAndEnrichmentsElement as HTMLElement);
this.enrichmentsManager = new EnrichmentsManager(contentAndEnrichmentsElement as HTMLElement);
}

setupPopups () {
Expand All @@ -274,6 +274,35 @@ class DocumentContent {
this.documentElement.appendChild(el);
}

/** Show only the portion of the document identified by the given ID, or the entire document if no ID is given. */
showDocumentPortion (id: string) {
// strip leading #
if (id && id.startsWith('#')) id = id.substring(1);

if (this.originalDocument && this.documentElement) {
if (!id) {
// show entire document
this.documentElement.replaceChildren(...Array.from(this.originalDocument.children).map(node => node.cloneNode(true)));
this.activeTocItem = null;
} else {
// find the owning portion for this portion, by looking for the container that has a TOC entry of depth 1
// @ts-ignore
let tocItem = this.tocItemIndex.get(id);
while (tocItem && tocItem.depth > 1) {
tocItem = tocItem.parent;
}
if (!tocItem) return;

// swap in the main owner of this portion, if necessary
if (this.activeTocItem !== tocItem) {
const portion = this.originalDocument.querySelector(`[id="${tocItem.id}"]`)?.cloneNode(true) as HTMLElement;
this.documentElement.replaceChildren(portion);
this.activeTocItem = tocItem;
}
}
}
}

/**
* When the user is focused only on a certain portion of the document, update the social sharing mechanisms
* to reflect that.
Expand Down
6 changes: 4 additions & 2 deletions peachjam/js/utils/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ export function scrollToElement (elem: HTMLElement, callback: (element: HTMLElem
export function generateHtmlTocItems (content: HTMLElement): TOCItemType[] {
let stack: any;
const items: TOCItemType[] = [];
const ids = new Map<string, number>();

content.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5').forEach((heading) => {
if (!heading.id) {
heading.id = heading.tagName + '_' + Math.floor(Math.random() * 10000);
ids.set(heading.tagName, (ids.get(heading.tagName) || 0) + 1);
heading.id = heading.tagName + '_' + ids.get(heading.tagName);
}

const item = {
Expand Down Expand Up @@ -105,7 +107,7 @@ export function wrapTocItems (root: HTMLElement, items: TOCItemType[]) {
const item = items[i];
const nextItem = i + 1 < items.length ? items[i + 1] : null;
const nextId: string | null = nextItem ? nextItem.id : null;
const el: HTMLElement | null = root.querySelector(`#${item.id}`);
const el: HTMLElement | null = document.getElementById(item.id);

if (el) {
const wrapper = document.createElement('div');
Expand Down
15 changes: 15 additions & 0 deletions peachjam/models/citations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ class Meta:
verbose_name = _("citation link")
verbose_name_plural = _("citation links")

def to_citator_api(self):
"""Transform into a format suitable for the Citator API."""
selector = next(
(t for t in self.target_selectors if t["type"] == "TextPositionSelector"),
None,
)
return {
"href": self.url,
"text": self.text,
# strip the page- and just keep the num
"target_id": int(self.target_id.split("-", 1)[1]) - 1,
"start": selector["start"] if selector else -1,
"end": selector["end"] if selector else -1,
}

def __str__(self):
return f"Citation link for {self.document.doc_type} - {self.document.title}"

Expand Down
Loading

0 comments on commit 52590a1

Please sign in to comment.