Skip to content

Commit 45a4752

Browse files
committed
merge: dev into master
2 parents 23bcabe + e07ee9e commit 45a4752

36 files changed

+317
-417
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
## [Unreleased]
44

5+
## [9.12.0] – 2025-10-29
6+
7+
### Added
8+
9+
- Add loading indicators to global filter selectors [#429](https://github.com/spraakbanken/korp-frontend/issues/429)
10+
- Corpus selector: expand partially selected folders by default [#431](https://github.com/spraakbanken/korp-frontend/issues/431)
11+
- Show downtime announcements if backend is down [#436](https://github.com/spraakbanken/korp-frontend/issues/436)
12+
- Statistics: link cells to corpus-filtered search [#455](https://github.com/spraakbanken/korp-frontend/issues/455)
13+
- More accurate headers in KWIC CSV download
14+
15+
### Changed
16+
17+
- Setting `preselected_corpora` to empty list used to select all, now it selects none. To select all, leave it unset.
18+
19+
### Fixed
20+
21+
- Error when switching corpus after searching by a corpus-specific attribute [#490](https://github.com/spraakbanken/korp-frontend/issues/490)
22+
- Error when downloading example KWIC [#491](https://github.com/spraakbanken/korp-frontend/issues/491)
23+
524
## [9.11.4] – 2025-09-29
625

726
### Fixed

app/markup/about.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="modal-header">
2-
<h2>Korp version 9.11.4</h2>
2+
<h2>Korp version 9.12.0</h2>
33
<span ng-click="clickX()" class="close-x">×</span>
44
</div>
55

app/scripts/app.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,23 @@ korpApp.run([
172172
let waitForLogin = false
173173

174174
async function initializeCorpusSelection(ids: string[], skipLogin?: boolean): Promise<void> {
175-
if (!ids || ids.length == 0) ids = settings["preselected_corpora"] || []
176-
177-
// Resolve any folder ids to the contained corpus ids
178-
ids = ids.flatMap((id) => getAllCorporaInFolders(settings.folders, id))
179-
180-
const hasAccess = (corpus: CorpusTransformed) => auth.hasCredential(corpus.id.toUpperCase())
181-
182-
const deniedCorpora = ids
183-
.map((id) => settings.corpora[id])
184-
.filter((corpus) => corpus?.limited_access && !hasAccess(corpus))
175+
const isDenied = (corpus?: CorpusTransformed) =>
176+
corpus?.limited_access && !auth.hasCredential(corpus.id.toUpperCase())
177+
178+
// If no id is given, use default
179+
if (!ids || ids.length == 0) {
180+
if (settings["preselected_corpora"]) ids = settings["preselected_corpora"]
181+
else {
182+
// If the default setting is not given, fallback to selecting all non-protected corpora. If all are protected, select all.
183+
const nonhidden = corpusListing.corpora.filter((corpus) => !corpus.hide)
184+
const allowed = nonhidden.filter((corpus) => !isDenied(corpus))
185+
ids = (allowed.length ? allowed : nonhidden).map((corpus) => corpus.id)
186+
}
187+
}
185188

189+
const deniedCorpora = ids.map((id) => settings.corpora[id]).filter((corpus) => isDenied(corpus))
186190
const allowedIds = ids.filter((id) => !deniedCorpora.find((corpus) => corpus.id == id))
187191

188-
const allCorpusIds = corpusListing.map((corpus) => corpus.id)
189-
190192
if (settings.initialization_checks && (await settings.initialization_checks())) {
191193
// custom initialization code called
192194
} else if (isEmpty(settings.corpora)) {
@@ -234,12 +236,12 @@ korpApp.run([
234236
// Login dismissed, fall back to allowed corpora
235237
initializeCorpusSelection(allowedIds)
236238
}
237-
} else if (!ids.every((r) => allCorpusIds.includes(r))) {
239+
} else if (!ids.every((id) => id in settings.corpora)) {
238240
// some corpora missing
239241
openErrorModal({
240242
content: `{{'corpus_not_available' | loc:$root.lang}}`,
241243
onClose: () => {
242-
const validIds = ids.filter((corpusId) => allCorpusIds.includes(corpusId))
244+
const validIds = ids.filter((corpusId) => corpusId in settings.corpora)
243245
initializeCorpusSelection(validIds)
244246
},
245247
})

app/scripts/backend/proxy/relations-proxy.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,11 @@
11
import ProxyBase from "./proxy-base"
22
import { unregescape } from "@/util"
3-
import { ApiRelation, RelationsParams, RelationsSort } from "../types/relations"
3+
import { RelationsParams, RelationsSort } from "../types/relations"
44
import { parse } from "@/cqp_parser/cqp"
55
import { CqpQuery } from "@/cqp_parser/cqp.types"
66
import { corpusSelection } from "@/corpora/corpus_listing"
77
import { WordPicture, WordType } from "@/word-picture"
88

9-
/** A relation item modified for showing. */
10-
export type ShowableApiRelation = ApiRelation & {
11-
/** Direction of relation */
12-
show_rel: "head" | "dep"
13-
}
14-
15-
export type TableData = {
16-
table: ApiRelation[] | { word: string }
17-
rel?: string
18-
show_rel?: string
19-
}
20-
21-
export type TableDrawData = {
22-
token: string
23-
wordClass: string
24-
wordClassShort: string
25-
data: TableData[][]
26-
}
27-
289
export type RelationsQuery = {
2910
type: WordType
3011
word: string

app/scripts/backend/types/relations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ export type RelationsParams = {
1212
}
1313

1414
export type RelationsResponse = {
15-
relations?: ApiRelation[]
15+
relations?: Relation[]
1616
/** Execution time in seconds */
1717
time: number
1818
}
1919

2020
export type RelationsSort = "freq" | "mi"
2121

22-
export type ApiRelation = {
22+
export type Relation = {
2323
dep: string
2424
depextra: string
2525
deppos: string

app/scripts/components/kwic/kwic.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import { KwicWordScope } from "./kwic-word"
1010
import { SelectWordEvent } from "@/statemachine/types"
1111
import { ApiKwic, Token } from "@/backend/types"
1212
import { StoreService } from "@/services/store"
13-
import { QueryParamSort, QueryResponse } from "@/backend/types/query"
13+
import { QueryParams, QueryParamSort, QueryResponse } from "@/backend/types/query"
1414
import { CorpusTransformed } from "@/settings/config-transformed.types"
1515
import { JQueryExtended, JQueryStaticExtended } from "@/jquery.types"
1616
import { loc } from "@/i18n"
1717
import { calculateHitsPicture, HitsPictureItem, isKwic, isLinkedKwic, massageData, Row } from "@/kwic/kwic"
1818
import { corpusSelection } from "@/corpora/corpus_listing"
19+
import { RelationsSentencesParams } from "@/backend/types/relations-sentences"
1920

2021
type KwicController = IController & {
2122
// Bindings
@@ -29,7 +30,7 @@ type KwicController = IController & {
2930
page: number
3031
pageEvent: (page: number) => void
3132
hitsPerPage: number
32-
params: any
33+
params: QueryParams | RelationsSentencesParams
3334
response?: QueryResponse
3435
corpusOrder: string[]
3536
/** Current page of results. */
@@ -622,7 +623,7 @@ angular.module("korpApp").component("kwic", {
622623
const next = getNextToken()
623624
if (next) {
624625
next.trigger("click")
625-
scrollToShowWord(next)
626+
next.get(0)?.scrollIntoView({ block: "nearest", inline: "nearest" })
626627
// Return false to prevent default behavior
627628
return false
628629
}
@@ -694,28 +695,6 @@ angular.module("korpApp").component("kwic", {
694695

695696
return output
696697
}
697-
698-
function scrollToShowWord(word: JQLite) {
699-
if (!word.length) return
700-
const offset = 200
701-
702-
if (word.offset()!.top + word.height()! > window.scrollY + $(window).height()!) {
703-
$("html, body")
704-
.stop(true, true)
705-
.animate({ scrollTop: window.scrollY + offset })
706-
} else if (word.offset()!.top < window.scrollY) {
707-
$("html, body")
708-
.stop(true, true)
709-
.animate({ scrollTop: window.scrollY - offset })
710-
}
711-
712-
const area = $element.find(".table_scrollarea")
713-
if (word.offset()!.left + word.width()! > area.offset()!.left + area.width()!) {
714-
area.stop(true, true).animate({ scrollLeft: area.scrollLeft()! + offset })
715-
} else if (word.offset()!.left < area.offset()!.left) {
716-
area.stop(true, true).animate({ scrollLeft: area.scrollLeft()! - offset })
717-
}
718-
}
719698
},
720699
],
721700
})
@@ -757,7 +736,7 @@ class SelectionManager {
757736
// Add download links for other formats, defined in
758737
// settings["download_formats"] (Jyrki Niemi <[email protected]>
759738
// 2014-02-26/04-30)
760-
export function setDownloadLinks(params: string, result_data: { kwic: Row[]; corpus_order: string[] }): void {
739+
export function setDownloadLinks(params: any, result_data: { kwic: Row[]; corpus_order: string[] }): void {
761740
// If some of the required parameters are null, return without
762741
// adding the download links.
763742
if (!(params != null && result_data != null && result_data.corpus_order != null && result_data.kwic != null)) {

app/scripts/components/kwic/results-examples.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ angular.module("korpApp").component("resultsExamples", {
4949
page="page"
5050
page-event="pageChange"
5151
hits-per-page="hitsPerPage"
52-
params="task.proxy.params"
52+
params="$ctrl.task.proxy.params"
5353
corpus-order="corpusOrder"
5454
on-update-search="onUpdateSearch()"
5555
></kwic>

app/scripts/components/search/extended/cqp-value.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ angular.module("korpApp").component("extendedCqpValue", {
5858
const write = (val: string) => (shouldUseRegexp() ? val : regescape(val))
5959
const read = (val: string) => (shouldUseRegexp() ? val : unregescape(val))
6060
// Set initial input value
61-
childScope.input = read(childScope.model as string)
61+
childScope.input = read((childScope.model as string) || "")
6262
// Sync from input to model, escaping special characters if needed
63-
childScope.$watch("input", () => (childScope.model = write(childScope.input)))
64-
childScope.$watch("orObj.op", () => (childScope.model = write(childScope.input)))
63+
childScope.$watch("input", () => (childScope.model = write(childScope.input || "")))
64+
childScope.$watch("orObj.op", () => (childScope.model = write(childScope.input || "")))
6565

6666
const locals = { $scope: childScope }
6767
const { template, controller } = getWidget()
6868

69+
// TODO Are we always creating a new component instance here? What happens to the old ones?
6970
// @ts-ignore
7071
$controller(controller, locals)
7172
const tmplElem = $compile(template)(childScope)

app/scripts/components/search/extended/tokens.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { html } from "@/util"
66
import "./token"
77
import "./struct-token"
88
import "./add-box"
9-
import "@/directives/scroll-to-start"
109
import { CqpQuery, CqpToken } from "@/cqp_parser/cqp.types"
1110

1211
type ExtendedTokensController = IController & {
@@ -28,7 +27,7 @@ type ExtendedTokensController = IController & {
2827
angular.module("korpApp").component("extendedTokens", {
2928
template: html`
3029
<div id="query_table">
31-
<div ui-sortable="{ items: '> .token', delay : 100 }" ng-model="$ctrl.data" scroll-to-start="scrollToStart">
30+
<div ui-sortable="{ items: '> .token', delay : 100 }" ng-model="$ctrl.data">
3231
<div class="token inline-block" ng-repeat="token in $ctrl.data track by $index">
3332
<extended-token
3433
ng-if="token.and_block"
@@ -105,14 +104,14 @@ angular.module("korpApp").component("extendedTokens", {
105104
ctrl.toggleStart = (idx) => () => ctrl.addStructToken(true, idx)
106105
ctrl.toggleEnd = (idx) => () => ctrl.addStructToken(false, idx + 1)
107106

108-
ctrl.scrollToStart = false
109107
ctrl.addStructToken = function (start = true, idx = -1) {
110108
const token: CqpToken = { struct: undefined, start }
111109
if (idx != -1) {
112110
ctrl.data.splice(idx, 0, token)
113111
} else if (start) {
114-
ctrl.scrollToStart = true
115112
ctrl.data.unshift(token)
113+
// Scroll to start
114+
document.getElementById("query_table")?.scrollTo(0, 0)
116115
} else {
117116
ctrl.data.push(token)
118117
}

app/scripts/components/search/extended/widgets/common.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Condition } from "@/cqp_parser/cqp.types"
55
import { StoreService } from "@/services/store"
66
import { AttributeOption } from "@/corpora/corpus-set"
77
import { loadOptions } from "@/search/extended-search"
8+
import { isEqual } from "lodash"
89

910
export type Widget = {
1011
template: string
@@ -38,9 +39,9 @@ export const selectController = (autocomplete: boolean): IController => [
3839
"$scope",
3940
"store",
4041
function ($scope: SelectWidgetScope, store: StoreService) {
41-
store.watch("corpus", (selected) => {
42+
store.watch("corpus", (selected, old) => {
4243
// TODO Destroy if new corpus selection doesn't support the attribute?
43-
if (selected.length > 0) {
44+
if (selected.length > 0 && !isEqual(selected, old)) {
4445
reloadValues()
4546
}
4647
})
@@ -53,7 +54,7 @@ export const selectController = (autocomplete: boolean): IController => [
5354
$scope.loading = false
5455
$scope.options = options
5556
// Reset old selection if that option has been removed.
56-
if (!autocomplete && !currentInputExists) {
57+
if (!autocomplete && !currentInputExists && $scope.options.length) {
5758
$scope.input = $scope.options[0][0]
5859
}
5960
})

0 commit comments

Comments
 (0)