diff --git a/client/assets/defaultStyles.css b/client/assets/defaultStyles.css index 027ccdf2..e0ca79e2 100644 --- a/client/assets/defaultStyles.css +++ b/client/assets/defaultStyles.css @@ -52,4 +52,17 @@ text-indent: 0px !important; text-align: start !important; text-align-last: start !important; -} \ No newline at end of file +} + +.default-style.less-spacing p { + margin-block-start: 0; +} + +.default-style.less-spacing ul { + margin-block-start: 0; +} + +.default-style.less-spacing ol { + margin-block-start: 0; +} + diff --git a/client/assets/trix.css b/client/assets/trix.css index 8f88c61f..7432b25f 100644 --- a/client/assets/trix.css +++ b/client/assets/trix.css @@ -446,7 +446,7 @@ trix-editor .attachment__metadata .attachment__size { } .trix-content { - line-height: 1.5; + line-height: inherit; } .trix-content * { @@ -455,6 +455,13 @@ trix-editor .attachment__metadata .attachment__size { padding: 0; } +.trix-content p { + box-sizing: border-box; + margin-top: 0; + margin-bottom: 0.5em; + padding: 0; +} + .trix-content h1 { font-size: 1.2em; line-height: 1.2; @@ -560,4 +567,4 @@ trix-editor .attachment__metadata .attachment__size { .trix-content .attachment-gallery.attachment-gallery--4 .attachment { flex-basis: 50%; max-width: 50%; -} \ No newline at end of file +} diff --git a/client/components/cards/BookMatchCard.vue b/client/components/cards/BookMatchCard.vue index d5355e91..4fa24c1f 100644 --- a/client/components/cards/BookMatchCard.vue +++ b/client/components/cards/BookMatchCard.vue @@ -24,7 +24,7 @@
-

{{ book.description }}

+

{{ book.descriptionPlain }}

diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index c7247d51..623ef2a1 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -94,9 +94,9 @@
diff --git a/client/components/modals/podcast/ViewEpisode.vue b/client/components/modals/podcast/ViewEpisode.vue index af67242a..6c1a678c 100644 --- a/client/components/modals/podcast/ViewEpisode.vue +++ b/client/components/modals/podcast/ViewEpisode.vue @@ -16,7 +16,7 @@

{{ title }}

-
+

{{ $strings.MessageNoDescription }}

diff --git a/client/components/ui/RichTextEditor.vue b/client/components/ui/RichTextEditor.vue index c5ae6d83..51bdc5ae 100644 --- a/client/components/ui/RichTextEditor.vue +++ b/client/components/ui/RichTextEditor.vue @@ -1,9 +1,9 @@ @@ -12,7 +12,10 @@ export default { props: { value: String, label: String, - disabled: Boolean + disabled: { + type: Boolean, + default: false + } }, data() { return {} @@ -25,49 +28,19 @@ export default { set(val) { this.$emit('input', val) } - }, - config() { - return { - toolbar: { - getDefaultHTML: () => `
- - - - - - - - - - - - - - - - -
-
- -
` - } - } } }, methods: { trixFileAccept(e) { e.preventDefault() + }, + blur() { + if (this.$refs.input && this.$refs.input.blur) { + this.$refs.input.blur() + } } }, mounted() {}, beforeDestroy() {} } - \ No newline at end of file + diff --git a/client/components/ui/VueTrix.vue b/client/components/ui/VueTrix.vue index 5d351c72..8bbb42df 100644 --- a/client/components/ui/VueTrix.vue +++ b/client/components/ui/VueTrix.vue @@ -1,6 +1,37 @@ @@ -14,6 +45,30 @@ import Trix from 'trix' import '@/assets/trix.css' +function enableBreakParagraphOnReturn() { + // Trix works with divs by default, we want paragraphs instead + Trix.config.blockAttributes.default.tagName = 'p' + // Enable break paragraph on Enter (Shift + Enter will still create a line break) + Trix.config.blockAttributes.default.breakOnReturn = true + + // Hack to fix buggy paragraph breaks + // Copied from https://github.com/basecamp/trix/issues/680#issuecomment-735742942 + Trix.Block.prototype.breaksOnReturn = function () { + const attr = this.getLastAttribute() + const config = Trix.getBlockConfig(attr ? attr : 'default') + return config ? config.breakOnReturn : false + } + Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () { + if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) { + return this.startLocation.offset > 0 + } else { + return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false + } + } +} + +enableBreakParagraphOnReturn() + export default { name: 'vue-trix', model: { @@ -134,6 +189,9 @@ export default { * Compute a random id of hidden input * when it haven't been specified. */ + toolbarId() { + return `trix-toolbar-${this.generateId}` + }, generateId() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { var r = (Math.random() * 16) | 0 @@ -223,13 +281,17 @@ export default { decorateDisabledEditor(editorState) { /** Disable toolbar and editor by pointer events styling */ if (editorState) { - this.$refs.trix.toolbarElement.style['pointer-events'] = 'none' + this.$refs.trix.disabled = true this.$refs.trix.contentEditable = false - this.$refs.trix.style['background'] = '#e9ecef' + this.$refs.trix.style['pointer-events'] = 'none' + this.$refs.trix.style['background-color'] = '#444' + this.$refs.trix.style['color'] = '#bbb' } else { - this.$refs.trix.toolbarElement.style['pointer-events'] = 'unset' + this.$refs.trix.disabled = false + this.$refs.trix.contentEditable = true this.$refs.trix.style['pointer-events'] = 'unset' - this.$refs.trix.style['background'] = 'transparent' + this.$refs.trix.style['background-color'] = '' + this.$refs.trix.style['color'] = '' } }, overrideConfig(config) { @@ -250,32 +312,15 @@ export default { } return target }, - enableBreakParagraphOnReturn() { - // Trix works with divs by default, we want paragraphs instead - Trix.config.blockAttributes.default.tagName = 'p' - // Enable break paragraph on Enter (Shift + Enter will still create a line break) - Trix.config.blockAttributes.default.breakOnReturn = true - - // Hack to fix buggy paragraph breaks - // Copied from https://github.com/basecamp/trix/issues/680#issuecomment-735742942 - Trix.Block.prototype.breaksOnReturn = function () { - const attr = this.getLastAttribute() - const config = Trix.getBlockConfig(attr ? attr : 'default') - return config ? config.breakOnReturn : false - } - Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () { - if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) { - return this.startLocation.offset > 0 - } else { - return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false - } + blur() { + if (this.$refs.trix && this.$refs.trix.blur) { + this.$refs.trix.blur() } } }, mounted() { /** Override editor configuration */ this.overrideConfig(this.config) - this.enableBreakParagraphOnReturn() /** Check if editor read-only mode is required */ this.decorateDisabledEditor(this.disabledEditor) this.$nextTick(() => { @@ -305,4 +350,12 @@ export default { .trix_container .trix-content { background-color: white; } +trix-editor { + max-height: calc(4 * 1lh); + overflow-y: auto; +} + +trix-editor * { + pointer-events: inherit; +} diff --git a/client/components/widgets/BookDetailsEdit.vue b/client/components/widgets/BookDetailsEdit.vue index 5fbcaa20..fa26bcf5 100644 --- a/client/components/widgets/BookDetailsEdit.vue +++ b/client/components/widgets/BookDetailsEdit.vue @@ -26,7 +26,7 @@
- +
diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue index a0cadc1d..714e326c 100644 --- a/client/pages/item/_id/index.vue +++ b/client/pages/item/_id/index.vue @@ -123,7 +123,7 @@
-

{{ description }}

+

@@ -804,8 +804,7 @@ export default { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; - max-height: 6.25rem; - transition: all 0.3s ease-in-out; + max-height: calc(6 * 1lh); } #item-description.show-full { -webkit-line-clamp: unset; diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index f4323094..8fde7bc4 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -8,6 +8,7 @@ const AudiobookCovers = require('../providers/AudiobookCovers') const CustomProviderAdapter = require('../providers/CustomProviderAdapter') const Logger = require('../Logger') const { levenshteinDistance, escapeRegExp } = require('../utils/index') +const htmlSanitizer = require('../utils/htmlSanitizer') class BookFinder { #providerResponseTimeout = 30000 @@ -463,6 +464,12 @@ class BookFinder { } else { books = await this.getGoogleBooksResults(title, author) } + books.forEach((book) => { + if (book.description) { + book.description = htmlSanitizer.sanitize(book.description) + book.descriptionPlain = htmlSanitizer.stripAllTags(book.description) + } + }) return books } diff --git a/server/models/Book.js b/server/models/Book.js index 5a4eee54..527960ea 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -2,6 +2,7 @@ const { DataTypes, Model } = require('sequelize') const Logger = require('../Logger') const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils') const parseNameString = require('../utils/parsers/parseNameString') +const htmlSanitizer = require('../utils/htmlSanitizer') /** * @typedef EBookFileObject @@ -579,6 +580,7 @@ class Book extends Model { oldMetadataJSON.authorNameLF = this.authorNameLF oldMetadataJSON.narratorName = (this.narrators || []).join(', ') oldMetadataJSON.seriesName = this.seriesName + oldMetadataJSON.descriptionPlain = this.description ? htmlSanitizer.stripAllTags(this.description) : null return oldMetadataJSON } diff --git a/server/providers/Audible.js b/server/providers/Audible.js index 505b8f0e..e6816082 100644 --- a/server/providers/Audible.js +++ b/server/providers/Audible.js @@ -1,5 +1,4 @@ const axios = require('axios').default -const htmlSanitizer = require('../utils/htmlSanitizer') const Logger = require('../Logger') const { isValidASIN } = require('../utils/index') @@ -68,7 +67,7 @@ class Audible { narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null, publisher: publisherName, publishedYear: releaseDate ? releaseDate.split('-')[0] : null, - description: summary ? htmlSanitizer.stripAllTags(summary) : null, + description: summary || null, cover: image, asin, genres: genresFiltered.length ? genresFiltered : null, diff --git a/server/providers/iTunes.js b/server/providers/iTunes.js index 1ec051d1..57a47d0d 100644 --- a/server/providers/iTunes.js +++ b/server/providers/iTunes.js @@ -112,7 +112,7 @@ class iTunes { artistId: data.artistId, title: data.collectionName, author, - description: htmlSanitizer.stripAllTags(data.description || ''), + description: data.description || null, publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null, genres: data.primaryGenreName ? [data.primaryGenreName] : null, cover: this.getCoverArtwork(data) diff --git a/server/utils/htmlSanitizer.js b/server/utils/htmlSanitizer.js index 68d92c85..cab92392 100644 --- a/server/utils/htmlSanitizer.js +++ b/server/utils/htmlSanitizer.js @@ -1,11 +1,9 @@ const sanitizeHtml = require('../libs/sanitizeHtml') -const { entities } = require("./htmlEntities"); +const { entities } = require('./htmlEntities') function sanitize(html) { const sanitizerOptions = { - allowedTags: [ - 'p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br' - ], + allowedTags: ['p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br', 'b', 'i'], disallowedTagsMode: 'discard', allowedAttributes: { a: ['href', 'name', 'target'] @@ -34,6 +32,6 @@ function decodeHTMLEntities(strToDecode) { if (entity in entities) { return entities[entity] } - return entity; + return entity }) }