Skip to content

Commit

Permalink
Merge pull request #476 from kbss-cvut/development
Browse files Browse the repository at this point in the history
[3.1.0] Release
  • Loading branch information
ledsoft authored Jul 8, 2024
2 parents 9aa119c + 3d6928b commit f336cde
Show file tree
Hide file tree
Showing 43 changed files with 1,029 additions and 331 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/merge-to-protected.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: "16.x"
- name: Install
Expand Down
8 changes: 8 additions & 0 deletions NEWS.cs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#### Verze 3.1.0

- Přidána možnost zvýraznit v anotátoru výskyty vybraného pojmu.
- Vylepšená opakovaná anotace souborů - zapamatování si již potvrzených výskytů i při (omezené) změně obsahu dokumentu.
- Úprava vizualizace výskytů v anotátoru.
- Filtrování v tabulkách ignoruje diakritiku.
- Opravy chyb, aktualizace knihoven.

#### Verze 3.0.4

- Opravy chyb.
Expand Down
8 changes: 8 additions & 0 deletions NEWS.en.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#### Version 3.1.0

- Added the possibility to highlight occurrences of the selected term in annotator.
- Improvements to repeated document annotation - remember approved occurrences even if the document content changes (to a degree).
- Modified visualization of term occurrences in annotator.
- Ignore accents when filtering in tables.
- Bug fixes, dependency updates.

#### Version 3.0.4

- Minor bug fixes.
Expand Down
199 changes: 75 additions & 124 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "termit-ui",
"version": "3.0.4",
"version": "3.1.0",
"private": true,
"homepage": ".",
"license": "GPL-3.0-only",
Expand Down
19 changes: 18 additions & 1 deletion src/action/AsyncAnnotatorActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { GetStoreState, ThunkDispatch } from "../util/Types";
import {
asyncActionFailure,
asyncActionRequest,
asyncActionSuccess,
asyncActionSuccessWithPayload,
} from "./SyncActions";
import Constants from "../util/Constants";
import Ajax, { params } from "../util/Ajax";
import Ajax, { content, params } from "../util/Ajax";
import JsonLdUtils from "../util/JsonLdUtils";
import Term, { CONTEXT as TERM_CONTEXT, TermData } from "../model/Term";
import { ErrorData } from "../model/ErrorInfo";
import {
isActionRequestPending,
loadTermByIri as loadTermByIriBase,
} from "./AsyncActions";
import TermOccurrence from "../model/TermOccurrence";

export function loadAllTerms(
vocabularyIri: IRI,
Expand Down Expand Up @@ -74,9 +76,24 @@ export function loadTermByIri(termIri: string) {
return promise.then((t) => {
delete pendingTermFetches[termIri];
if (t) {
// No hierarchy for on-demand loaded terms in annotator. We cannot load children anyway
t.subTerms = [];
dispatch(asyncActionSuccessWithPayload(action, t));
}
return t;
});
};
}

export function saveOccurrence(occurrence: TermOccurrence) {
const action = { type: ActionType.CREATE_TERM_OCCURRENCE };
return (dispatch: ThunkDispatch) => {
dispatch(asyncActionRequest(action, true));
return Ajax.put(
`${Constants.API_PREFIX}/occurrence`,
content(occurrence.toJsonLd())
)
.then(() => dispatch(asyncActionSuccess(action)))
.catch((error: ErrorData) => dispatch(asyncActionFailure(action, error)));
};
}
43 changes: 31 additions & 12 deletions src/action/AsyncTermActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,44 +226,63 @@ export function removeTermDefinitionSource(term: Term) {
};
}

export function removeOccurrence(occurrence: TermOccurrence | AssetData) {
export function removeOccurrence(
occurrence: TermOccurrence | AssetData,
ignoreNotFound = false
) {
const action = {
type: ActionType.REMOVE_TERM_OCCURRENCE,
};
return (dispatch: ThunkDispatch) => {
dispatch(asyncActionRequest(action));
dispatch(asyncActionRequest(action, true));
const OccurrenceIri = VocabularyUtils.create(occurrence.iri!);
return Ajax.delete(
Constants.API_PREFIX + "/occurrence/" + OccurrenceIri.fragment,
param("namespace", OccurrenceIri.namespace)
)
.then(() => dispatch(asyncActionSuccess(action)))
.catch((error: ErrorData) => {
dispatch(asyncActionFailure(action, error));
return dispatch(
SyncActions.publishMessage(new Message(error, MessageType.ERROR))
);
if (error.status !== 404 || !ignoreNotFound) {
dispatch(asyncActionFailure(action, error));
return dispatch(
SyncActions.publishMessage(new Message(error, MessageType.ERROR))
);
} else {
return dispatch(asyncActionFailure(action, error));
}
});
};
}

export function approveOccurrence(occurrence: TermOccurrence | AssetData) {
/**
* Approves the specified term occurrence.
* @param occurrence Occurrence to approve
* @param ignoreNotFound In case the annotations in the document have different identifiers than the stored term occurrences, not found exceptions may be thrown. This allows to not show errors in such cases
*/
export function approveOccurrence(
occurrence: TermOccurrence | AssetData,
ignoreNotFound = false
) {
const action = {
type: ActionType.APPROVE_TERM_OCCURRENCE,
};
return (dispatch: ThunkDispatch) => {
dispatch(asyncActionRequest(action));
dispatch(asyncActionRequest(action, true));
const OccurrenceIri = VocabularyUtils.create(occurrence.iri!);
return Ajax.put(
Constants.API_PREFIX + "/occurrence/" + OccurrenceIri.fragment,
param("namespace", OccurrenceIri.namespace)
)
.then(() => dispatch(asyncActionSuccess(action)))
.catch((error: ErrorData) => {
dispatch(asyncActionFailure(action, error));
return dispatch(
SyncActions.publishMessage(new Message(error, MessageType.ERROR))
);
if (error.status !== 404 || !ignoreNotFound) {
dispatch(asyncActionFailure(action, error));
return dispatch(
SyncActions.publishMessage(new Message(error, MessageType.ERROR))
);
} else {
return dispatch(asyncActionFailure(action, error));
}
});
};
}
Expand Down
32 changes: 29 additions & 3 deletions src/action/__tests__/AsyncAnnotatorActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ActionType from "../ActionType";
import AsyncActionStatus from "../AsyncActionStatus";
import Term from "../../model/Term";
import Generator from "../../__tests__/environment/Generator";
import VocabularyUtils from "../../util/VocabularyUtils";

jest.mock("../../util/Routing");
jest.mock("../../util/Ajax", () => ({
Expand Down Expand Up @@ -67,10 +68,11 @@ describe("AsyncAnnotatorActions", () => {
Ajax.get = jest.fn().mockResolvedValue(term);
return Promise.resolve(
(store.dispatch as ThunkDispatch)(loadTermByIri(term["@id"]))
).then((result: Term) => {
).then((result: Term | null) => {
expect(Ajax.get).toHaveBeenCalled();
expect(result).toBeDefined();
expect(result.iri).toEqual(term["@id"]);
expect(result).not.toBeNull();
expect(result!.iri).toEqual(term["@id"]);
});
});

Expand All @@ -80,9 +82,10 @@ describe("AsyncAnnotatorActions", () => {
Ajax.get = jest.fn().mockResolvedValue({});
return Promise.resolve(
(store.dispatch as ThunkDispatch)(loadTermByIri(term.iri))
).then((result: Term) => {
).then((result: Term | null) => {
expect(Ajax.get).not.toHaveBeenCalled();
expect(result).toBeDefined();
expect(result).not.toBeNull();
expect(result).toEqual(term);
});
});
Expand All @@ -105,5 +108,28 @@ describe("AsyncAnnotatorActions", () => {
expect(successAction).not.toBeDefined();
});
});

// Terms loaded for annotator by IRI are not from the vocabulary associated with the file
// Since there is no additional term fetching available for such terms, it is not possible to load
// subterms. Therefore, we will just keep them as a flat list with no hierarchical structure
it("removes child term references of loaded term", () => {
const term = require("../../rest-mock/terms")[0];
term[VocabularyUtils.NARROWER] = [
{
"@id": Generator.generateUri(),
label: "Child one",
},
];
Ajax.get = jest.fn().mockResolvedValue(term);
return Promise.resolve(
(store.dispatch as ThunkDispatch)(loadTermByIri(term["@id"]))
).then((result: Term | null) => {
expect(Ajax.get).toHaveBeenCalled();
expect(result).toBeDefined();
expect(result).not.toBeNull();
expect(result!.iri).toEqual(term["@id"]);
expect(result!.subTerms).toEqual([]);
});
});
});
});
17 changes: 12 additions & 5 deletions src/component/annotator/Annotation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@
}

.assigned-term-occurrence {
background: rgba(188, 239, 184, 0.8);
background: rgba(#bcefb8, 0.8);
border: 0.15rem solid green;
}

.suggested-term-occurrence {
background: rgba(239, 207, 184, 0.8);
background: rgba(#efcfb8, 0.8);
}

.invalid-term-occurrence {
background: rgba(239, 66, 24, 0.7);
border: 0.15em solid rgb(239, 69, 27);
}

.loading-term-occurrence {
background: #e9eaec;
}

.assigned-term-occurrence.proposed-occurrence {
border: 0.15em dotted green;
background: none;
border: 0.15em dashed green;
}

.suggested-term-occurrence.proposed-occurrence {
border: 0.15em dotted rgb(239, 69, 27);
background: none;
border: 0.15em dashed rgb(239, 69, 27);
}

.invalid-term-occurrence.proposed-occurrence {
Expand Down Expand Up @@ -79,6 +83,9 @@

.annotator-highlighted-annotation {
background-color: yellow !important;
border: 1px solid #6f42c1;
border-radius: 0.2rem;
}

.annotator-highlighted-annotation-current {
background-color: orange !important;
}
10 changes: 8 additions & 2 deletions src/component/annotator/Annotation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ThunkDispatch } from "../../util/Types";
import { loadTermByIri } from "../../action/AsyncAnnotatorActions";
import Asset from "../../model/Asset";
import AccessLevel from "../../model/acl/AccessLevel";
import classNames from "classnames";

interface AnnotationProps extends AnnotationSpanProps {
about: string;
Expand All @@ -29,6 +30,7 @@ interface AnnotationProps extends AnnotationSpanProps {
onFetchTerm: (termIri: string) => Promise<Term | null>;
onResetSticky: () => void; // Resets sticky annotation status
accessLevel: AccessLevel;
highlight?: boolean;
}

interface AnnotationState {
Expand Down Expand Up @@ -103,7 +105,8 @@ export class Annotation extends React.Component<
nextProps.sticky !== this.props.sticky ||
nextProps.text !== this.props.text ||
nextProps.resource !== this.props.resource ||
nextProps.score !== this.props.score
nextProps.score !== this.props.score ||
nextProps.highlight !== this.props.highlight
) {
return true;
}
Expand Down Expand Up @@ -159,6 +162,7 @@ export class Annotation extends React.Component<
about: this.props.about,
property: this.props.property,
typeof: this.props.typeof,
score: this.props.score,
};
newAnnotation.resource = t ? t.iri : undefined;
this.props.onUpdate(newAnnotation, t);
Expand Down Expand Up @@ -257,7 +261,9 @@ export class Annotation extends React.Component<
{...contentProps}
typeof={this.props.typeof}
{...scoreProps}
className={termClassName + " " + termCreatorClassName}
className={classNames(termClassName, termCreatorClassName, {
"annotator-highlighted-annotation": this.props.highlight,
})}
>
{this.props.children}
{this.props.typeof === AnnotationType.DEFINITION
Expand Down
31 changes: 26 additions & 5 deletions src/component/annotator/AnnotationDomHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const AnnotationType = {
DEFINITION: VocabularyUtils.DEFINITION,
};

export const SELECTOR_CONTEXT_LENGTH = 32;

function toHtmlString(nodeList: NodeList): string {
let result = "";
for (let i = 0; i < nodeList.length; i++) {
Expand Down Expand Up @@ -71,11 +73,14 @@ const AnnotationDomHelper = {
removeAnnotation(annotation: DomHandlerNode, dom: DomHandlerNode[]): void {
// assuming annotation.type === "tag"
const elem = annotation as DomHandlerElement;
if (
Utils.sanitizeArray(elem.children).length === 1 &&
elem.children![0].type === "text"
) {
const newNode = this.createTextualNode(elem);
if (Utils.sanitizeArray(elem.children).length === 1) {
let newNode;
const child = elem.children![0];
if (child.type === "text") {
newNode = this.createTextualNode(elem);
} else {
newNode = child;
}
DomUtils.replaceElement(elem, newNode);
const elemInd = dom.indexOf(elem);
if (elemInd !== -1) {
Expand Down Expand Up @@ -156,8 +161,24 @@ const AnnotationDomHelper = {
},

generateSelector(node: DomHandlerNode): TextQuoteSelector {
let prefix = undefined;
let suffix = undefined;
if (node.previousSibling) {
prefix = HtmlDomUtils.getTextContent(node.previousSibling);
if (prefix.length > SELECTOR_CONTEXT_LENGTH) {
prefix = prefix.substring(prefix.length - SELECTOR_CONTEXT_LENGTH);
}
}
if (node.nextSibling) {
suffix = HtmlDomUtils.getTextContent(node.nextSibling);
if (suffix.length > SELECTOR_CONTEXT_LENGTH) {
suffix = suffix.substring(0, SELECTOR_CONTEXT_LENGTH);
}
}
return {
exactMatch: HtmlDomUtils.getTextContent(node),
prefix,
suffix,
types: [VocabularyUtils.TEXT_QUOTE_SELECTOR],
};
},
Expand Down
Loading

0 comments on commit f336cde

Please sign in to comment.