diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a5f693..d695df2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.25.3](https://github.com/Fantom-foundation/Fantom-Vue3-Components/compare/v0.25.2...v0.25.3) (2022-09-13)
+
+
+### Features
+
+* **DocumentMeta:** create plugin class ([d52557a](https://github.com/Fantom-foundation/Fantom-Vue3-Components/commit/d52557a8fe5a4496bd9d94375e55794f63094436))
+* **useDocumentMeta:** create composable ([2d6fa4e](https://github.com/Fantom-foundation/Fantom-Vue3-Components/commit/2d6fa4e673717daccc2aa63b156dbf2a440a3394))
+
+
+### Bug Fixes
+
+* **FUploadAreaFormInput:** fix style when input is invalid ([390713a](https://github.com/Fantom-foundation/Fantom-Vue3-Components/commit/390713a2e1444fc4bdca0f35bdfd76ff2643b6c2))
+
### [0.25.2](https://github.com/Fantom-foundation/Fantom-Vue3-Components/compare/v0.25.1...v0.25.2) (2022-09-12)
### [0.25.1](https://github.com/Fantom-foundation/Fantom-Vue3-Components/compare/v0.25.0...v0.25.1) (2022-09-12)
diff --git a/package-lock.json b/package-lock.json
index 3749b9d..a96a65d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "fantom-vue3-components",
- "version": "0.25.2",
+ "version": "0.25.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fantom-vue3-components",
- "version": "0.25.2",
+ "version": "0.25.3",
"license": "MIT",
"dependencies": {
"@vueuse/core": "^8.5.0",
diff --git a/package.json b/package.json
index b6a5e01..aa1050a 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fantom-vue3-components",
- "version": "0.25.2",
+ "version": "0.25.3",
"description": "Library of various Vue 3 components with focus on accessibility",
"author": "Fantom Foundation",
"license": "MIT",
diff --git a/src/components/FUploadAreaFormInput/FUploadAreaFormInput.vue b/src/components/FUploadAreaFormInput/FUploadAreaFormInput.vue
index 1cae9e2..a75a2c5 100644
--- a/src/components/FUploadAreaFormInput/FUploadAreaFormInput.vue
+++ b/src/components/FUploadAreaFormInput/FUploadAreaFormInput.vue
@@ -1,6 +1,7 @@
@@ -48,6 +54,7 @@ defineExpose({ validate });
{
+ it('should return instance of DocumentMeta', () => {
+ const { composableResult, app } = withSetup({ composable: () => useDocumentMeta({ pageInfo: {} }) });
+
+ expect(composableResult.documentMeta instanceof DocumentMeta).toBe(true);
+
+ app.unmount();
+ });
+});
diff --git a/src/plugins/DocumentMeta/DocumentMeta.js b/src/plugins/DocumentMeta/DocumentMeta.js
new file mode 100644
index 0000000..61842c0
--- /dev/null
+++ b/src/plugins/DocumentMeta/DocumentMeta.js
@@ -0,0 +1,66 @@
+export class DocumentMeta {
+ constructor({ mainTitle = '' } = {}) {
+ this.mainTitle = mainTitle;
+ }
+
+ /**
+ * @param {string|array} title
+ * @param {boolean} [addMainTitle] Add main title at the end of the title
+ */
+ setTitle(title, addMainTitle = false) {
+ let t = '';
+
+ if (title !== undefined) {
+ if (Array.isArray(title)) {
+ t = title.map((str) => str.trim()).join(' | ');
+ } else {
+ t = title;
+ }
+
+ if (addMainTitle && this.mainTitle) {
+ t += t !== '' ? ` | ${this.mainTitle}` : this.mainTitle;
+ }
+
+ document.title = t;
+ DocumentMeta.#setMetaContentAttribute('og:title', t);
+ DocumentMeta.#setMetaContentAttribute('twitter:title', t);
+ }
+ }
+
+ /**
+ * @param {string} description
+ */
+ setDescription(description) {
+ if (description !== undefined) {
+ DocumentMeta.#setMetaContentAttribute('og:description', description);
+ DocumentMeta.#setMetaContentAttribute('twitter:description', description);
+ }
+ }
+
+ /**
+ * @param {string} mainTitle
+ */
+ setMainTitle(mainTitle = '') {
+ this.mainTitle = mainTitle;
+ }
+
+ /**
+ * @return {string[]}
+ */
+ getSplittedTitle() {
+ return document.title.split('|').map((title) => title.trim());
+ }
+
+ /**
+ * @param {string} property
+ * @param {string} value
+ */
+ static #setMetaContentAttribute(property, value = '') {
+ if (property) {
+ const elem = document.querySelector(`meta[property="${property}"]`);
+ if (elem) {
+ elem.setAttribute('content', value);
+ }
+ }
+ }
+}
diff --git a/src/plugins/DocumentMeta/DocumentMeta.spec.js b/src/plugins/DocumentMeta/DocumentMeta.spec.js
new file mode 100644
index 0000000..896458b
--- /dev/null
+++ b/src/plugins/DocumentMeta/DocumentMeta.spec.js
@@ -0,0 +1,101 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { DocumentMeta } from './DocumentMeta.js';
+
+let dm = null;
+const TEST_TITLE = 'Test title';
+const TEST_DESCRIPTION = 'Test description';
+
+function createMetaElement(property = '', content = '') {
+ const elem = document.createElement('meta');
+
+ if (property) {
+ elem.setAttribute('property', property);
+ }
+
+ if (content) {
+ elem.setAttribute('content', content);
+ }
+
+ return elem;
+}
+
+beforeEach(() => {
+ dm = new DocumentMeta();
+});
+
+afterEach(() => {
+ dm = null;
+});
+
+describe('DocumentMeta', () => {
+ it('should set html document title', () => {
+ dm.setTitle(TEST_TITLE);
+
+ expect(document.title).toEqual(TEST_TITLE);
+ });
+
+ it('should set og and twitter meta title', () => {
+ const ogMetaTitle = createMetaElement('og:title');
+ const twitterMetaTitle = createMetaElement('twitter:title');
+
+ document.head.appendChild(ogMetaTitle);
+ document.head.appendChild(twitterMetaTitle);
+
+ dm.setTitle(TEST_TITLE);
+
+ expect(ogMetaTitle.getAttribute('content')).toEqual(TEST_TITLE);
+ expect(twitterMetaTitle.getAttribute('content')).toEqual(TEST_TITLE);
+
+ ogMetaTitle.remove();
+ twitterMetaTitle.remove();
+ });
+
+ it('should set og and twitter meta description', () => {
+ const ogMetaDescription = createMetaElement('og:description');
+ const twitterMetaDescription = createMetaElement('twitter:description');
+
+ document.head.appendChild(ogMetaDescription);
+ document.head.appendChild(twitterMetaDescription);
+
+ dm.setDescription(TEST_DESCRIPTION);
+
+ expect(ogMetaDescription.getAttribute('content')).toEqual(TEST_DESCRIPTION);
+ expect(twitterMetaDescription.getAttribute('content')).toEqual(TEST_DESCRIPTION);
+
+ ogMetaDescription.remove();
+ twitterMetaDescription.remove();
+ });
+
+ it('should set title by given array and display it separated with `|`', () => {
+ dm.setTitle([' part1 ', 'part2']);
+
+ expect(document.title).toEqual('part1 | part2');
+ });
+
+ it('should add main title at the end of the title, separated by `|`', () => {
+ const mainTitle = 'MyApp';
+
+ dm = new DocumentMeta({ mainTitle });
+
+ dm.setTitle(TEST_TITLE, true);
+ expect(document.title).toEqual(`${TEST_TITLE} | ${mainTitle}`);
+
+ dm.setTitle('', true);
+ expect(document.title).toEqual(`${mainTitle}`);
+ });
+
+ it('should be able to set main title', () => {
+ const mainTitle = 'MyApp';
+
+ dm.setMainTitle(mainTitle);
+ dm.setTitle(TEST_TITLE, true);
+
+ expect(document.title).toEqual(`${TEST_TITLE} | ${mainTitle}`);
+ });
+
+ it('should get current title splitted by `|`', () => {
+ dm.setTitle(`${TEST_TITLE} | ${TEST_TITLE}`);
+
+ expect(dm.getSplittedTitle()).toEqual([TEST_TITLE, TEST_TITLE]);
+ });
+});
diff --git a/src/plugins/index.js b/src/plugins/index.js
index 8ff8428..9a4f8a1 100644
--- a/src/plugins/index.js
+++ b/src/plugins/index.js
@@ -3,3 +3,4 @@ export * from './jazzicon/jazzicon.js';
export * from './Formatters/Formatters.js';
export * from './Locale/Locale.js';
export * from './api/index.js';
+export * from './DocumentMeta/DocumentMeta.js';