Skip to content

Commit c27aad4

Browse files
committed
merge: dev into master
2 parents 0b164d1 + 382412f commit c27aad4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2252
-2481
lines changed

Diff for: CHANGELOG.md

+35
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@
22

33
## [Unreleased]
44

5+
## [9.9.0] - 2025-03-24
6+
7+
### Added
8+
9+
- Choose to show word picture by frequency or LMI [#433](https://github.com/spraakbanken/korp-frontend/issues/433)
10+
- Word picture help texts
11+
12+
### Changed
13+
14+
- Display options in Simple search as a form [#357](https://github.com/spraakbanken/korp-frontend/issues/357)
15+
- Load corpus timespan data in parallel when loading app [#437](https://github.com/spraakbanken/korp-frontend/issues/437)
16+
- Instead of `settings.time_data`, use `import { timeData } from "./timedata"`
17+
- Await `getTimeData()` before using `timeData` or `corpus.time`/`corpus.non_time`. The function is memoized, so repeated calls will not affect performance
18+
- Load Statistics and Word picture result when the tab is selected [#442](https://github.com/spraakbanken/korp-frontend/issues/442)
19+
- The "medial part" search option is logically linked to initial and final part [#443](https://github.com/spraakbanken/korp-frontend/issues/443)
20+
- The display options (hits per page, sort within corpora, compile based on) were moved into the KWIC and Statistics tab correspondingly
21+
- When these are changed, the search is re-triggered automatically
22+
- The "compile based on" input was relabeled as "group by"
23+
- The "word" group-by option no longer gets disabled if it's the only selected option
24+
- Moved "Show context" option to the new display options location and changed to checkbox
25+
- Better readability for hit counts in lemgram autocomplete
26+
- More space in word picture tables [#102](https://github.com/spraakbanken/korp-frontend/issues/102)
27+
28+
### Fixed
29+
30+
- Selecting a search history item used to reset params that were not part of the search
31+
- Trend diagram legend missing
32+
- Internal search links in sidebar does not activate relevant search/result tabs [#450](https://github.com/spraakbanken/korp-frontend/issues/450)
33+
- Comparison result not showing if a search is not done first [#413](https://github.com/spraakbanken/korp-frontend/issues/413)
34+
- Extended search: do not cache operator options across corpora [#409](https://github.com/spraakbanken/korp-frontend/issues/409)
35+
- Error when logging out while protected corpora are selected [#440](https://github.com/spraakbanken/korp-frontend/issues/440)
36+
- Error when loading with restricted corpora selected and then dismissing login dialog [#399](https://github.com/spraakbanken/korp-frontend/issues/399)
37+
- Cached translation files cause broken UI after releases [#435](https://github.com/spraakbanken/korp-frontend/issues/435)
38+
539
## [9.8.5] - 2025-03-17
640

741
### Fixed
@@ -380,6 +414,7 @@
380414
- Lots of bug fixes for the sidebar
381415

382416
[unreleased]: https://github.com/spraakbanken/korp-frontend/compare/master...dev
417+
[9.9.0]: https://github.com/spraakbanken/korp-frontend/releases/tag/v9.9.0
383418
[9.8.5]: https://github.com/spraakbanken/korp-frontend/releases/tag/v9.8.5
384419
[9.8.4]: https://github.com/spraakbanken/korp-frontend/releases/tag/v9.8.4
385420
[9.8.3]: https://github.com/spraakbanken/korp-frontend/releases/tag/v9.8.3

Diff for: app/markup/about.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="modal-header">
2-
<h2>Korp version 9.8.5</h2>
2+
<h2>Korp version 9.9.0</h2>
33
<span ng-click="clickX()" class="close-x">×</span>
44

55
</div>

Diff for: app/scripts/app.ts

+61-46
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
IComponentOptions,
77
ILocaleService,
88
ILocationProvider,
9-
IQService,
109
IScope,
1110
ITimeoutService,
1211
ui,
@@ -18,7 +17,7 @@ import * as authenticationProxy from "@/components/auth/auth"
1817
import { initLocales } from "@/data_init"
1918
import { RootScope } from "@/root-scope.types"
2019
import { CorpusTransformed } from "./settings/config-transformed.types"
21-
import { getService, html } from "@/util"
20+
import { deferOk, getService, html } from "@/util"
2221
import { loc, locObj } from "@/i18n"
2322
import "@/components/app-header"
2423
import "@/components/searchtabs"
@@ -30,6 +29,9 @@ import { LocationService } from "./urlparams"
3029
import { LocLangMap } from "@/i18n/types"
3130
import { getAllCorporaInFolders } from "./components/corpus-chooser/util"
3231

32+
// @ts-ignore
33+
const BUILD_HASH = __webpack_hash__
34+
3335
// Catch unhandled exceptions within Angular, see https://docs.angularjs.org/api/ng/service/$exceptionHandler
3436
korpApp.factory("$exceptionHandler", [
3537
function () {
@@ -89,7 +91,7 @@ authenticationProxy.initAngular(korpApp)
8991
korpApp.config([
9092
"tmhDynamicLocaleProvider",
9193
(tmhDynamicLocaleProvider: tmh.tmh.IDynamicLocaleProvider) =>
92-
tmhDynamicLocaleProvider.localeLocationPattern("translations/angular-locale_{{locale}}.js"),
94+
tmhDynamicLocaleProvider.localeLocationPattern(`translations/angular-locale_{{locale}}.${BUILD_HASH}.js`),
9395
])
9496

9597
korpApp.config([
@@ -121,7 +123,6 @@ korpApp.run([
121123
"$locale",
122124
"tmhDynamicLocale",
123125
"tmhDynamicLocaleCache",
124-
"$q",
125126
"$timeout",
126127
"$uibModal",
127128
async function (
@@ -130,17 +131,24 @@ korpApp.run([
130131
$locale: ILocaleService,
131132
tmhDynamicLocale: tmh.tmh.IDynamicLocale,
132133
tmhDynamicLocaleCache: ICacheObject,
133-
$q: IQService,
134134
$timeout: ITimeoutService,
135135
$uibModal: ui.bootstrap.IModalService
136136
) {
137137
const s = $rootScope
138138

139139
s.extendedCQP = null
140140
s.globalFilterData = {}
141-
142-
/** This deferred is used to signal that the filter feature is ready. */
143-
s.globalFilterDef = $q.defer<never>()
141+
$rootScope.globalFilterDef = deferOk()
142+
$rootScope.langDef = deferOk()
143+
$rootScope.wordpicSortProp = "mi"
144+
145+
/** Get CQP corresponding to the current search, if any. */
146+
$rootScope.getActiveCqp = () => {
147+
if (!$rootScope.activeSearch) return undefined
148+
// Simple search puts CQP in `simpleCQP`. Extended/advanced puts it in `activeSearch.val`.
149+
const isSimple = ["word", "lemgram"].includes($rootScope.activeSearch.type)
150+
return isSimple ? $rootScope.simpleCQP : $rootScope.activeSearch.val
151+
}
144152

145153
// Listen to url changes like #?lang=swe
146154
s.$on("$locationChangeSuccess", () => {
@@ -185,22 +193,22 @@ korpApp.run([
185193
$rootScope.$apply(() => ($rootScope["loc_data"] = data))
186194
)
187195

188-
s.waitForLogin = false
196+
/** Whether initial corpus selection is deferred because it depends on authentication. */
197+
let waitForLogin = false
198+
199+
async function initializeCorpusSelection(ids: string[], skipLogin?: boolean): Promise<void> {
200+
if (!ids || ids.length == 0) ids = settings["preselected_corpora"] || []
189201

190-
async function initializeCorpusSelection(selectedIds: string[]): Promise<void> {
191202
// Resolve any folder ids to the contained corpus ids
192-
selectedIds = selectedIds.flatMap((id) => getAllCorporaInFolders(settings.folders, id))
203+
ids = ids.flatMap((id) => getAllCorporaInFolders(settings.folders, id))
193204

194-
let loginNeededFor: CorpusTransformed[] = []
205+
const hasAccess = (corpus: CorpusTransformed) => authenticationProxy.hasCredential(corpus.id.toUpperCase())
195206

196-
for (const corpusId of selectedIds) {
197-
const corpusObj = settings.corpora[corpusId]
198-
if (corpusObj && corpusObj.limited_access) {
199-
if (!authenticationProxy.hasCredential(corpusId.toUpperCase())) {
200-
loginNeededFor.push(corpusObj)
201-
}
202-
}
203-
}
207+
const deniedCorpora = ids
208+
.map((id) => settings.corpora[id])
209+
.filter((corpus) => corpus?.limited_access && !hasAccess(corpus))
210+
211+
const allowedIds = ids.filter((id) => !deniedCorpora.find((corpus) => corpus.id == id))
204212

205213
const allCorpusIds = settings.corpusListing.corpora.map((corpus) => corpus.id)
206214

@@ -212,14 +220,14 @@ korpApp.run([
212220
content: "<korp-error></korp-error>",
213221
resolvable: false,
214222
})
215-
} else if (loginNeededFor.length != 0) {
223+
} else if (deniedCorpora.length != 0) {
216224
// check if user has access
217225
const loginNeededHTML = () =>
218-
loginNeededFor.map((corpus) => `<span>${locObj(corpus.title)}</span>`).join(", ")
226+
deniedCorpora.map((corpus) => `<span>${locObj(corpus.title)}</span>`).join(", ")
219227

220228
if (authenticationProxy.isLoggedIn()) {
221229
// access partly or fully denied to selected corpora
222-
if (settings.corpusListing.corpora.length == loginNeededFor.length) {
230+
if (settings.corpusListing.corpora.length == deniedCorpora.length) {
223231
openErrorModal({
224232
content: "{{'access_denied' | loc:$root.lang}}",
225233
buttonText: "go_to_start",
@@ -233,38 +241,37 @@ korpApp.run([
233241
<div>${loginNeededHTML()}</div>
234242
<div>{{'access_partly_denied_continue' | loc:$root.lang}}</div>`,
235243
onClose: () => {
236-
const neededIds = loginNeededFor.map((corpus) => corpus.id)
237-
const filtered = selectedIds.filter((corpusId) => !neededIds.includes(corpusId))
238-
const selected = filtered.length ? filtered : settings["preselected_corpora"] || []
239-
initializeCorpusSelection(selected)
244+
initializeCorpusSelection(allowedIds)
240245
},
241246
})
242247
}
243-
} else {
248+
} else if (!skipLogin) {
244249
// login needed before access can be checked
245250
openErrorModal({
246251
content: html`<span class="mr-1">{{'login_needed_for_corpora' | loc:$root.lang}}:</span
247252
>${loginNeededHTML()}`,
248253
onClose: () => {
249-
s.waitForLogin = true
250-
statemachine.send("LOGIN_NEEDED", { loginNeededFor })
254+
waitForLogin = true
255+
statemachine.send("LOGIN_NEEDED", { loginNeededFor: deniedCorpora })
251256
},
252257
})
258+
} else {
259+
// Login dismissed, fall back to allowed corpora
260+
initializeCorpusSelection(allowedIds)
253261
}
254-
} else if (!selectedIds.every((r) => allCorpusIds.includes(r))) {
262+
} else if (!ids.every((r) => allCorpusIds.includes(r))) {
255263
// some corpora missing
256264
openErrorModal({
257265
content: `{{'corpus_not_available' | loc:$root.lang}}`,
258266
onClose: () => {
259-
const validIds = selectedIds.filter((corpusId) => allCorpusIds.includes(corpusId))
260-
const newIds = validIds.length ? validIds : settings["preselected_corpora"] || []
261-
initializeCorpusSelection(newIds)
267+
const validIds = ids.filter((corpusId) => allCorpusIds.includes(corpusId))
268+
initializeCorpusSelection(validIds)
262269
},
263270
})
264271
} else {
265272
// here $timeout must be used so that message is not sent before all controllers/componenters are initialized
266-
settings.corpusListing.select(selectedIds)
267-
$timeout(() => $rootScope.$broadcast("initialcorpuschooserchange", selectedIds), 0)
273+
settings.corpusListing.select(ids)
274+
$timeout(() => $rootScope.$broadcast("initialcorpuschooserchange", ids), 0)
268275
}
269276
}
270277

@@ -318,19 +325,27 @@ korpApp.run([
318325
}
319326
}
320327

321-
function getCorporaFromHash(): string[] {
322-
const corpus = $location.search().corpus
323-
return corpus ? corpus.split(",") : settings["preselected_corpora"] || []
328+
const getCorporaFromHash = (): string[] => {
329+
const value = $location.search().corpus
330+
return value ? value.split(",") : []
324331
}
325332

326-
statemachine.listen("login", function () {
327-
if (s.waitForLogin) {
328-
s.waitForLogin = false
329-
initializeCorpusSelection(getCorporaFromHash())
330-
}
333+
initializeCorpusSelection(getCorporaFromHash())
334+
335+
// Retry initialization after login
336+
statemachine.listen("login", () => {
337+
if (!waitForLogin) return
338+
waitForLogin = false
339+
initializeCorpusSelection(getCorporaFromHash())
340+
})
341+
342+
// Retry intialization after dismissing login
343+
statemachine.listen("logout", () => {
344+
if (!waitForLogin) return
345+
waitForLogin = false
346+
initializeCorpusSelection(getCorporaFromHash(), true)
331347
})
332348

333-
initializeCorpusSelection(getCorporaFromHash())
334349
await initLocalesPromise
335350
},
336351
])
@@ -342,4 +357,4 @@ korpApp.filter("prettyNumber", [
342357
"$rootScope",
343358
($rootScope) => (input: string, lang: string) => Number(input).toLocaleString(lang || $rootScope.lang),
344359
])
345-
korpApp.filter("maxLength", () => (val: string) => val.length > 39 ? val.slice(0, 36) + "…" : val)
360+
korpApp.filter("maxLength", () => (val: unknown) => String(val).length > 39 ? String(val).slice(0, 36) + "…" : val)

Diff for: app/scripts/backend/backend.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { WithinParameters } from "./types"
99
import { QueryResponse } from "./types/query"
1010
import { CountParams } from "./types/count"
1111

12+
// TODO Convert to object
1213
export type CompareResult = [CompareTables, number, SavedSearch, SavedSearch, string[]]
1314

1415
export type CompareTables = { positive: CompareItem[]; negative: CompareItem[] }

Diff for: app/scripts/backend/kwic-proxy.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ export class KwicProxy extends BaseProxy {
2121

2222
async makeRequest(
2323
options: KorpQueryRequestOptions,
24-
page: number | undefined,
24+
page?: number,
2525
progressCallback?: (data: ProgressReport<"query">) => void,
2626
kwicCallback?: (data: QueryResponse) => void
2727
): Promise<QueryResponse> {
2828
this.resetRequest()
2929
const abortSignal = this.abortController.signal
3030

31-
if (!options.ajaxParams.within) {
32-
_.extend(options.ajaxParams, settings.corpusListing.getWithinParameters())
31+
if (!options.within) {
32+
_.extend(options, settings.corpusListing.getWithinParameters())
3333
}
3434

35+
/** Calculate start and end from page and hpp. Only works for main hits. Examples must provide start and end in param. */
3536
function getPageInterval(): { start: number; end: number } {
3637
const hpp = locationSearchGet("hpp")
3738
const itemsPerPage = Number(hpp) || settings.hits_per_page_default
@@ -40,12 +41,12 @@ export class KwicProxy extends BaseProxy {
4041
return { start, end }
4142
}
4243

43-
const command = options.ajaxParams.command || "query"
44+
const command = options.command || "query"
4445

4546
const params: QueryParams = {
4647
default_context: settings.default_overview_context,
4748
...getPageInterval(),
48-
...options.ajaxParams,
49+
...options,
4950
}
5051

5152
const show: string[] = []
@@ -111,11 +112,6 @@ export class KwicProxy extends BaseProxy {
111112
const kwicProxyFactory = new Factory(KwicProxy)
112113
export default kwicProxyFactory
113114

114-
export type KorpQueryRequestOptions = {
115-
// TODO Should start,end really exist here as well as under ajaxParams?
116-
start?: number
117-
end?: number
118-
ajaxParams: QueryParams & {
119-
command?: "query" | "relations_sentences"
120-
}
115+
export type KorpQueryRequestOptions = QueryParams & {
116+
command?: "query" | "relations_sentences"
121117
}

Diff for: app/scripts/backend/lemgram-proxy.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class LemgramProxy extends BaseProxy {
1212
async makeRequest(
1313
word: string,
1414
type: string,
15+
sort: RelationsParams["sort"],
1516
onProgress: ProgressHandler<"relations">
1617
): Promise<RelationsResponse> {
1718
this.resetRequest()
@@ -22,6 +23,7 @@ export class LemgramProxy extends BaseProxy {
2223
corpus: settings.corpusListing.stringifySelected(),
2324
incremental: true,
2425
type,
26+
sort,
2527
max: 1000,
2628
}
2729
this.prevParams = params

0 commit comments

Comments
 (0)