Skip to content

Commit 4be07ed

Browse files
committed
refactor: migrate reference widget related function to Typescript
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 16070c6 commit 4be07ed

File tree

12 files changed

+243
-163
lines changed

12 files changed

+243
-163
lines changed

src/components/NcRichText/NcReferencePicker/NcCustomPickerElement.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
</template>
99

1010
<script>
11-
import { renderCustomPickerElement, isCustomPickerElementRegistered, destroyCustomPickerElement } from './../../../functions/reference/customPickerElements.js'
11+
import {
12+
renderCustomPickerElement,
13+
isCustomPickerElementRegistered,
14+
destroyCustomPickerElement,
15+
} from '../../../functions/reference/customPickerElements.ts'
1216
1317
export default {
1418
name: 'NcCustomPickerElement',

src/components/NcRichText/NcReferencePicker/NcProviderList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</template>
4343

4444
<script>
45-
import { searchProvider } from './../../../functions/reference/providerHelper.js'
45+
import { searchProvider } from '../../../functions/reference/providerHelper.ts'
4646
import { isUrl } from './utils.js'
4747
import NcEmptyContent from '../../NcEmptyContent/index.ts'
4848
import NcHighlight from '../../NcHighlight/index.ts'

src/components/NcRichText/NcReferencePicker/NcReferencePicker.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import NcCustomPickerElement from './NcCustomPickerElement.vue'
3838
import NcProviderList from './NcProviderList.vue'
3939
import NcRawLinkInput from './NcRawLinkInput.vue'
4040
import NcSearch from './NcSearch.vue'
41-
import { isCustomPickerElementRegistered } from './../../../functions/reference/customPickerElements.js'
42-
import { touchProvider } from './../../../functions/reference/providerHelper.js'
41+
import { isCustomPickerElementRegistered } from '../../../functions/reference/customPickerElements.ts'
42+
import { touchProvider } from '../../../functions/reference/providerHelper.ts'
4343
4444
const MODES = {
4545
providerList: 1,

src/components/NcRichText/NcReferencePicker/NcReferencePickerModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
<script>
4545
import NcReferencePicker from './NcReferencePicker.vue'
46-
import { getCustomPickerElementSize, isCustomPickerElementRegistered } from './../../../functions/reference/customPickerElements.js'
46+
import { getCustomPickerElementSize, isCustomPickerElementRegistered } from '../../../functions/reference/customPickerElements.ts'
4747
import NcButton from '../../NcButton/index.ts'
4848
import NcModal from '../../NcModal/index.js'
4949
import { t } from '../../../l10n.js'

src/components/NcRichText/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ import NcReferenceWidget from './NcReferenceWidget.vue'
1111
import NcReferencePicker from './NcReferencePicker/NcReferencePicker.vue'
1212
import NcReferencePickerModal from './NcReferencePicker/NcReferencePickerModal.vue'
1313
import NcSearch from './NcReferencePicker/NcSearch.vue'
14-
import { getLinkWithPicker } from './../../functions/reference/referencePickerModal.js'
14+
import { getLinkWithPicker } from '../../functions/reference/referencePickerModal.ts'
1515
import {
1616
getProvider,
1717
getProviders,
1818
sortProviders,
1919
searchProvider,
2020
anyLinkProviderId,
21-
} from '../../functions/reference/providerHelper.js'
21+
} from '../../functions/reference/providerHelper.ts'
2222
import {
2323
registerCustomPickerElement,
2424
renderCustomPickerElement,
2525
isCustomPickerElementRegistered,
2626
NcCustomPickerRenderResult,
27-
} from '../../functions/reference/customPickerElements.js'
27+
} from '../../functions/reference/customPickerElements.ts'
2828

2929
export default NcRichText
3030

src/functions/reference/customPickerElements.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,79 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
if (!window._vue_richtext_custom_picker_elements) {
7-
window._vue_richtext_custom_picker_elements = {}
8-
}
6+
import logger from '../../utils/logger'
7+
8+
window._vue_richtext_custom_picker_elements ??= {}
9+
window._registerCustomPickerElement ??= registerCustomPickerElement
910

1011
/**
1112
* Representation of the render callback result
1213
* It contains a dom element and an object (Vue instance or other fancy things)
1314
*/
14-
class NcCustomPickerRenderResult {
15+
export class NcCustomPickerRenderResult {
16+
17+
public element: HTMLElement
18+
public object: object
1519

1620
/**
17-
* @param {HTMLElement} element The HTML element
18-
* @param {object} object The object
21+
* @param element - The HTML element
22+
* @param object - The object
1923
*/
20-
constructor(element, object) {
24+
constructor(element: HTMLElement, object: object) {
2125
this.element = element
2226
this.object = object
2327
}
2428

2529
}
2630

27-
const isCustomPickerElementRegistered = (id) => {
31+
interface CustomPickerElementProps {
32+
providerId: number
33+
accessible: boolean
34+
}
35+
36+
type CustomPickerElementRegistrationCallback = (el: HTMLElement, options: CustomPickerElementProps) => void
37+
type CustomPickerElementDestroyCallback = (el: HTMLElement, result: NcCustomPickerRenderResult) => void
38+
39+
export interface CustomPickerElement {
40+
id: string
41+
size: 'small' | 'normal' | 'large' | 'full'
42+
43+
callback: CustomPickerElementRegistrationCallback
44+
onDestroy: CustomPickerElementDestroyCallback
45+
}
46+
47+
/**
48+
* @param id - Id of the element to check
49+
*/
50+
export function isCustomPickerElementRegistered(id: string): boolean {
2851
return !!window._vue_richtext_custom_picker_elements[id]
2952
}
3053

31-
const getCustomPickerElementSize = (id) => {
54+
/**
55+
* @param id - Id of the element to get
56+
*/
57+
export function getCustomPickerElementSize(id: string): 'small' | 'normal' | 'large' | 'full' | null {
3258
const size = window._vue_richtext_custom_picker_elements[id]?.size
3359
if (['small', 'normal', 'large', 'full'].includes(size)) {
3460
return size
3561
}
3662
return null
3763
}
3864

39-
const registerCustomPickerElement = (id, callback, onDestroy = (el) => {}, size = 'large') => {
65+
/**
66+
* @param id - Id of element to register
67+
* @param callback - Render callback
68+
* @param onDestroy - Cleanup callback
69+
* @param size - Size of the element
70+
*/
71+
export function registerCustomPickerElement(
72+
id: string,
73+
callback: CustomPickerElementRegistrationCallback,
74+
onDestroy: CustomPickerElementDestroyCallback = () => {},
75+
size: CustomPickerElement['size'] = 'large',
76+
) {
4077
if (window._vue_richtext_custom_picker_elements[id]) {
41-
console.error('Custom reference picker element for id ' + id + ' already registered')
78+
logger.error(`Custom reference picker element for id ${id} already registered`)
4279
return
4380
}
4481

@@ -50,30 +87,31 @@ const registerCustomPickerElement = (id, callback, onDestroy = (el) => {}, size
5087
}
5188
}
5289

53-
const renderCustomPickerElement = (el, { providerId, accessible }) => {
90+
/**
91+
* @param el - element to render to
92+
* @param options - Element options
93+
*/
94+
export function renderCustomPickerElement(el: HTMLElement, options: CustomPickerElementProps) {
95+
const { providerId, accessible } = options
5496
if (!window._vue_richtext_custom_picker_elements[providerId]) {
55-
console.error('Custom reference picker element for reference provider ID ' + providerId + ' not registered')
97+
logger.error(`Custom reference picker element for reference provider ID ${providerId} not registered`)
5698
return
5799
}
58100

59-
return window._vue_richtext_custom_picker_elements[providerId].callback(el, { providerId, accessible })
101+
return window._vue_richtext_custom_picker_elements[providerId]
102+
.callback(el, { providerId, accessible })
60103
}
61104

62-
const destroyCustomPickerElement = (providerId, el, renderResult) => {
105+
/**
106+
* @param providerId - Provider id of element to destroy
107+
* @param el - The element to destroy element from
108+
* @param renderResult - The render result
109+
*/
110+
export function destroyCustomPickerElement(providerId: string, el: HTMLElement, renderResult: NcCustomPickerRenderResult) {
63111
if (!window._vue_richtext_custom_picker_elements[providerId]) {
64112
return
65113
}
66114

67-
window._vue_richtext_custom_picker_elements[providerId].onDestroy(el, renderResult)
68-
}
69-
70-
window._registerCustomPickerElement = registerCustomPickerElement
71-
72-
export {
73-
NcCustomPickerRenderResult,
74-
registerCustomPickerElement,
75-
renderCustomPickerElement,
76-
destroyCustomPickerElement,
77-
isCustomPickerElementRegistered,
78-
getCustomPickerElementSize,
115+
window._vue_richtext_custom_picker_elements[providerId]
116+
.onDestroy(el, renderResult)
79117
}

src/functions/reference/index.ts

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,32 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { registerWidget, renderWidget, isWidgetRegistered, hasInteractiveView } from './widgets.ts'
7-
import { getLinkWithPicker } from './referencePickerModal.js'
8-
import {
9-
getProvider,
10-
getProviders,
11-
sortProviders,
12-
searchProvider,
13-
anyLinkProviderId,
14-
} from './providerHelper.js'
15-
import {
16-
registerCustomPickerElement,
17-
renderCustomPickerElement,
18-
isCustomPickerElementRegistered,
19-
NcCustomPickerRenderResult,
20-
} from './customPickerElements.js'
21-
226
export {
7+
type ReferenceWidgetProps,
8+
type ReferenceWidgetRenderProperties,
9+
2310
registerWidget,
2411
renderWidget,
2512
isWidgetRegistered,
2613
hasInteractiveView,
14+
} from './widgets.ts'
15+
16+
export { getLinkWithPicker } from './referencePickerModal.ts'
17+
18+
export {
19+
type ReferenceProvider,
2720

28-
NcCustomPickerRenderResult,
29-
registerCustomPickerElement,
30-
renderCustomPickerElement,
31-
isCustomPickerElementRegistered,
32-
getLinkWithPicker,
33-
anyLinkProviderId,
3421
getProvider,
3522
getProviders,
3623
sortProviders,
3724
searchProvider,
38-
}
25+
anyLinkProviderId,
26+
} from './providerHelper.ts'
27+
28+
export {
29+
type CustomPickerElement,
30+
registerCustomPickerElement,
31+
renderCustomPickerElement,
32+
isCustomPickerElementRegistered,
33+
NcCustomPickerRenderResult,
34+
} from './customPickerElements.ts'

src/functions/reference/providerHelper.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,51 @@ import { t } from '../../l10n.js'
1010
import axios from '@nextcloud/axios'
1111
import { loadState } from '@nextcloud/initial-state'
1212
import { generateOcsUrl, imagePath } from '@nextcloud/router'
13+
import logger from '../../utils/logger.js'
14+
15+
export interface ReferenceProvider {
16+
id: string
17+
title: string
18+
icon_url: string
19+
order: number
20+
search_providers_ids?: string[]
21+
}
1322

1423
export const anyLinkProviderId = 'any-link'
1524

16-
const anyLinkProvider = {
25+
const anyLinkProvider: ReferenceProvider = {
1726
id: anyLinkProviderId,
1827
title: t('Any link'),
28+
order: 0,
1929
icon_url: imagePath('core', 'filetypes/link.svg'),
2030
}
2131

2232
// only get the provider list once, even if functions of this file are imported multiple times by different apps/components
23-
if (!window._vue_richtext_reference_providers) {
24-
window._vue_richtext_reference_providers = loadState('core', 'reference-provider-list', [])
25-
}
26-
33+
window._vue_richtext_reference_providers ??= loadState('core', 'reference-provider-list', [])
2734
// single timestamps object used by every entity in the page
28-
if (!window._vue_richtext_reference_provider_timestamps) {
29-
window._vue_richtext_reference_provider_timestamps = loadState('core', 'reference-provider-timestamps', {})
30-
}
35+
window._vue_richtext_reference_provider_timestamps ??= loadState('core', 'reference-provider-timestamps', {})
3136

3237
/**
33-
* @param {string} providerId The provider ID
34-
* @return {object} The provider object
38+
* @param providerId - The provider ID
39+
* @return The provider object
3540
*/
36-
export function getProvider(providerId) {
41+
export function getProvider(providerId: string): ReferenceProvider|undefined {
3742
if (providerId === anyLinkProviderId) {
3843
return anyLinkProvider
3944
}
45+
4046
return getProviders().find(p => p.id === providerId)
4147
}
4248

4349
/**
44-
* @return {Array} Raw provider list as it was provided by the server
50+
* Get raw provider list as it was provided by the server
4551
*/
46-
export function getProviders() {
47-
return window._vue_richtext_reference_providers.filter(p => {
52+
export function getProviders(): ReferenceProvider[] {
53+
return window._vue_richtext_reference_providers.filter((p) => {
4854
// avoid providers with no associated search provider and no custom component registered
4955
const keep = (!!p.search_providers_ids && p.search_providers_ids.length > 0) || isCustomPickerElementRegistered(p.id)
5056
if (!keep) {
51-
console.debug('[smart picker]', p.id, 'reference provider is discoverable but does not have any related search provider or custom picker component registered')
57+
logger.debug(`[smart picker] ${p.id} reference provider is discoverable but does not have any related search provider or custom picker component registered`)
5258
}
5359
return keep
5460
})
@@ -59,10 +65,10 @@ export function getProviders() {
5965
* - their "last used timestamp"
6066
* - their "order" property (coming from the provider declaration in the server implementation)
6167
*
62-
* @param {Array} providerList list of provider objects
63-
* @return {Array} the sorted provider list
68+
* @param providerList - List of provider objects
69+
* @return The sorted provider list
6470
*/
65-
export function sortProviders(providerList) {
71+
export function sortProviders(providerList: ReferenceProvider[]): ReferenceProvider[] {
6672
const timestamps = window._vue_richtext_reference_provider_timestamps
6773

6874
return providerList.sort((a, b) => {
@@ -90,21 +96,23 @@ export function sortProviders(providerList) {
9096
* Helper function to search a provider from a search query
9197
* Result is a sorted list of providers
9298
*
93-
* @param {string} query The search query
94-
* @param {number} limit (optional) max number of results
95-
* @return {Array} the sorted/filtered provider list
99+
* @param query - The search query
100+
* @param limit - (optional) max number of results
101+
* @return The sorted/filtered provider list
96102
*/
97-
export function searchProvider(query, limit = null) {
103+
export function searchProvider(query: string, limit?: number): ReferenceProvider[] {
98104
const providers = getProviders()
99105
const escapedQuery = query.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
100106
const regexp = new RegExp(escapedQuery, 'i')
101107
const sortedProviders = sortProviders(providers)
102-
const filteredSortedProviders = sortedProviders.filter(p => {
108+
const filteredSortedProviders = sortedProviders.filter((p) => {
103109
return p.title.match(regexp)
104110
})
111+
105112
const searchResult = limit
106113
? filteredSortedProviders.slice(0, limit)
107114
: filteredSortedProviders
115+
108116
// append the 'any link' provider in the full list or when there is no result
109117
if (query === '' || searchResult.length === 0) {
110118
searchResult.push(anyLinkProvider)
@@ -115,16 +123,12 @@ export function searchProvider(query, limit = null) {
115123
/**
116124
* Update the "last used timestamp" on the server side and then locally in the frontend
117125
*
118-
* @param {number} providerId The id of the search provider
126+
* @param providerId - The id of the search provider
119127
*/
120-
export function touchProvider(providerId) {
128+
export async function touchProvider(providerId: number) {
121129
const timestamp = Math.floor(Date.now() / 1000)
122-
const params = {
123-
timestamp,
124-
}
125130
const url = generateOcsUrl('references/provider/{providerId}', { providerId })
126-
axios.put(url, params)
127-
.then((response) => {
128-
window._vue_richtext_reference_provider_timestamps[providerId] = timestamp
129-
})
131+
132+
await axios.put(url, { timestamp })
133+
window._vue_richtext_reference_provider_timestamps[providerId] = timestamp
130134
}

0 commit comments

Comments
 (0)