Skip to content

Commit 3bc324a

Browse files
susnuxShGKme
andcommitted
feat(NcAppContent): Allow to set the page title
Sometimes - e.g. when you have multiple view - you want to adjust the page title, that is shown as the browsers tab name. This adds a property to set that value. Co-authored-by: Ferdinand Thiessen <[email protected]> Co-authored-by: Grigorii K. Shartsev <[email protected]> Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent 3779b88 commit 3bc324a

File tree

3 files changed

+176
-5
lines changed

3 files changed

+176
-5
lines changed

l10n/messages.pot

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ msgstr ""
88
msgid "{tag} (restricted)"
99
msgstr ""
1010

11+
msgid "{view} - {app} - {product}"
12+
msgstr ""
13+
1114
msgid "A color with a HEX value {hex}"
1215
msgstr ""
1316

src/components/NcAppContent/NcAppContent.vue

+91-5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,45 @@ The list size must be between the min and the max width value.
5050
:list-max-width="45"
5151
>...</NcAppContent>
5252
```
53+
54+
#### Usage: Custom document title
55+
For accessibility reasons every document should have a `h1` heading,
56+
this is visually hidden, but required for a semantically correct document.
57+
You can use your app name or current view for the heading.
58+
59+
Additionally you can set a custom document title, e.g. to show the current status.
60+
61+
```vue
62+
<template>
63+
<NcAppContent :pageHeading="heading ? 'Heading' : undefined" :pageTitle="title ? 'Title' : undefined" >
64+
<NcCheckboxRadioSwitch type="switch" :checked.sync="title">
65+
Toggle title
66+
</NcCheckboxRadioSwitch>
67+
<NcCheckboxRadioSwitch type="switch" :checked.sync="heading">
68+
Toggle Heading
69+
</NcCheckboxRadioSwitch>
70+
<NcButton @click="reset">Reset</NcButton>
71+
</NcAppContent>
72+
</template>
73+
74+
<script>
75+
export default {
76+
data() {
77+
return {
78+
heading: false,
79+
title: false,
80+
}
81+
},
82+
methods: {
83+
reset() {
84+
this.heading = false
85+
this.title = false
86+
document.title = ''
87+
},
88+
},
89+
}
90+
</script>
91+
```
5392
</docs>
5493

5594
<template>
@@ -103,18 +142,22 @@ The list size must be between the min and the max width value.
103142
</template>
104143

105144
<script>
106-
import NcAppDetailsToggle from './NcAppDetailsToggle.vue'
107-
import { useIsMobile } from '../../composables/useIsMobile/index.js'
108-
109145
import { getBuilder } from '@nextcloud/browser-storage'
146+
import { emit } from '@nextcloud/event-bus'
147+
import { loadState } from '@nextcloud/initial-state'
110148
import { useSwipe } from '@vueuse/core'
111149
import { Splitpanes, Pane } from 'splitpanes'
150+
import { useIsMobile } from '../../composables/useIsMobile/index.js'
151+
import { isRtl } from '../../utils/rtl.ts'
152+
153+
import NcAppDetailsToggle from './NcAppDetailsToggle.vue'
112154

113155
import 'splitpanes/dist/splitpanes.css'
114-
import { emit } from '@nextcloud/event-bus'
115-
import { isRtl } from '../../utils/rtl.ts'
116156

117157
const browserStorage = getBuilder('nextcloud').persist().build()
158+
const { name: productName } = loadState('theming', 'data', { name: 'Nextcloud' })
159+
const activeApp = loadState('core', 'active-app', appName)
160+
const localizedAppName = loadState('core', 'apps', {})[activeApp]?.name ?? appName
118161

119162
/**
120163
* App content container to be used for the main content of your app
@@ -217,6 +260,18 @@ export default {
217260
return ['no-split', 'vertical-split', 'horizontal-split'].includes(value)
218261
},
219262
},
263+
264+
/**
265+
* Allow setting the page's `<title>`
266+
*
267+
* If a page heading is set it defaults to `{pageHeading} - {appName} - {productName}` e.g. `Favorites - Files - Nextcloud`.
268+
* When the page heading and the app name is the same only one is used, e.g. `Files - Files - Nextcloud` is shown as `Files - Nextcloud`.
269+
* When setting the prop then the following format will be used: `{pageTitle} - {pageHeading || appName} - {productName}`
270+
*/
271+
pageTitle: {
272+
type: String,
273+
default: null,
274+
},
220275
},
221276

222277
emits: [
@@ -284,6 +339,37 @@ export default {
284339
},
285340
}
286341
},
342+
343+
realPageTitle() {
344+
const entries = new Set()
345+
if (this.pageTitle) {
346+
entries.add(this.pageTitle)
347+
}
348+
if (this.pageHeading) {
349+
entries.add(this.pageHeading)
350+
}
351+
if (entries.size === 0) {
352+
return null
353+
}
354+
355+
if (entries.size < 2) {
356+
// Only add if not already pageHeading and pageTitle are included
357+
entries.add(localizedAppName)
358+
}
359+
entries.add(productName)
360+
return [...entries.values()].join(' - ')
361+
},
362+
},
363+
364+
watch: {
365+
realPageTitle: {
366+
immediate: true,
367+
handler() {
368+
if (this.realPageTitle !== null) {
369+
document.title = this.realPageTitle
370+
}
371+
},
372+
},
287373
},
288374

289375
updated() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { describe, expect, it } from '@jest/globals'
2+
import { mount } from '@vue/test-utils'
3+
import NcAppContent from '../../../../src/components/NcAppContent/NcAppContent.vue'
4+
5+
describe('NcAppContent', () => {
6+
beforeEach(() => {
7+
document.title = 'Initial title'
8+
})
9+
10+
it('does not set a page heading by default', () => {
11+
const wrapper = mount(NcAppContent)
12+
13+
expect(wrapper.find('h1').exists()).toBe(false)
14+
})
15+
16+
it('can set the page heading', () => {
17+
const wrapper = mount(NcAppContent, {
18+
propsData: {
19+
pageHeading: 'My heading',
20+
},
21+
})
22+
23+
expect(wrapper.find('h1').text()).toBe('My heading')
24+
})
25+
26+
it('does not set the document title without page heading', () => {
27+
mount(NcAppContent)
28+
29+
expect(document.title).toBe('Initial title')
30+
})
31+
32+
it('sets the document title if a heading is provided', () => {
33+
mount(NcAppContent, {
34+
propsData: {
35+
pageHeading: 'My heading',
36+
},
37+
})
38+
39+
expect(document.title).toBe('My heading - nextcloud-vue - Nextcloud')
40+
})
41+
42+
it('does not duplicate the heading in the document title', () => {
43+
mount(NcAppContent, {
44+
propsData: {
45+
pageHeading: 'nextcloud-vue',
46+
},
47+
})
48+
49+
expect(document.title).toBe('nextcloud-vue - Nextcloud')
50+
})
51+
52+
it('sets the document title if pageTitle is provided', () => {
53+
mount(NcAppContent, {
54+
propsData: {
55+
pageTitle: 'My title',
56+
},
57+
})
58+
59+
expect(document.title).toBe('My title - nextcloud-vue - Nextcloud')
60+
})
61+
62+
it('does not duplicate the title in the document title', () => {
63+
mount(NcAppContent, {
64+
propsData: {
65+
pageTitle: 'nextcloud-vue',
66+
},
67+
})
68+
69+
expect(document.title).toBe('nextcloud-vue - Nextcloud')
70+
})
71+
72+
it('sets the document title if pageTitle and pageHeading are provided', () => {
73+
mount(NcAppContent, {
74+
propsData: {
75+
pageHeading: 'My heading',
76+
pageTitle: 'My title',
77+
},
78+
})
79+
80+
expect(document.title).toBe('My title - My heading - Nextcloud')
81+
})
82+
})

0 commit comments

Comments
 (0)