mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #3880 from mikiher/rich-text-book-descriptionss
Support rich text book descriptions
This commit is contained in:
		
						commit
						a4d0f95ecc
					
				| @ -53,3 +53,16 @@ | |||||||
|   text-align: start !important; |   text-align: start !important; | ||||||
|   text-align-last: start !important; |   text-align-last: start !important; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -446,7 +446,7 @@ trix-editor .attachment__metadata .attachment__size { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .trix-content { | .trix-content { | ||||||
|   line-height: 1.5; |   line-height: inherit; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .trix-content * { | .trix-content * { | ||||||
| @ -455,6 +455,13 @@ trix-editor .attachment__metadata .attachment__size { | |||||||
|   padding: 0; |   padding: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .trix-content p { | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   margin-top: 0; | ||||||
|  |   margin-bottom: 0.5em; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .trix-content h1 { | .trix-content h1 { | ||||||
|   font-size: 1.2em; |   font-size: 1.2em; | ||||||
|   line-height: 1.2; |   line-height: 1.2; | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ | |||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="w-full max-h-12 overflow-hidden"> |         <div class="w-full max-h-12 overflow-hidden"> | ||||||
|           <p class="text-gray-500 text-xs">{{ book.description }}</p> |           <p class="text-gray-500 text-xs">{{ book.descriptionPlain }}</p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div v-else class="px-4 flex-grow"> |       <div v-else class="px-4 flex-grow"> | ||||||
|  | |||||||
| @ -94,9 +94,9 @@ | |||||||
|         <div v-if="selectedMatchOrig.description" class="flex items-center py-2"> |         <div v-if="selectedMatchOrig.description" class="flex items-center py-2"> | ||||||
|           <ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" /> |           <ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" /> | ||||||
|           <div class="flex-grow ml-4"> |           <div class="flex-grow ml-4"> | ||||||
|             <ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" /> |             <ui-rich-text-editor v-model="selectedMatch.description" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" /> | ||||||
|             <p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60"> |             <p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60"> | ||||||
|               {{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a> |               {{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.descriptionPlain.substr(0, 100) + (mediaMetadata.descriptionPlain.length > 100 ? '...' : '') }}</a> | ||||||
|             </p> |             </p> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p> |       <p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p> | ||||||
|       <div v-if="description" dir="auto" class="default-style" v-html="description" /> |       <div v-if="description" dir="auto" class="default-style less-spacing" v-html="description" /> | ||||||
|       <p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p> |       <p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p> | ||||||
| 
 | 
 | ||||||
|       <div class="w-full h-px bg-white/5 my-4" /> |       <div class="w-full h-px bg-white/5 my-4" /> | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="default-style"> |   <div class="default-style"> | ||||||
|     <p v-if="label" class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }"> |     <p v-if="label" class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }" style="margin-top: 0; margin-bottom: 0.125em"> | ||||||
|       {{ label }} |       {{ label }} | ||||||
|     </p> |     </p> | ||||||
|     <ui-vue-trix v-model="content" :config="config" :disabled-editor="disabled" @trix-file-accept="trixFileAccept" /> |     <ui-vue-trix ref="input" v-model="content" :disabled-editor="disabled" @trix-file-accept="trixFileAccept" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -12,7 +12,10 @@ export default { | |||||||
|   props: { |   props: { | ||||||
|     value: String, |     value: String, | ||||||
|     label: String, |     label: String, | ||||||
|     disabled: Boolean |     disabled: { | ||||||
|  |       type: Boolean, | ||||||
|  |       default: false | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return {} |     return {} | ||||||
| @ -25,46 +28,16 @@ export default { | |||||||
|       set(val) { |       set(val) { | ||||||
|         this.$emit('input', val) |         this.$emit('input', val) | ||||||
|       } |       } | ||||||
|     }, |  | ||||||
|     config() { |  | ||||||
|       return { |  | ||||||
|         toolbar: { |  | ||||||
|           getDefaultHTML: () => `<div class="trix-button-row"> |  | ||||||
|       <span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools"> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" title="${this.$strings.LabelFontBold}" tabindex="-1">${this.$strings.LabelFontBold}</button> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" title="${this.$strings.LabelFontItalic}" tabindex="-1">${this.$strings.LabelFontItalic}</button> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" title="${this.$strings.LabelFontStrikethrough}" tabindex="-1">${this.$strings.LabelFontStrikethrough}</button> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="${this.$strings.LabelTextEditorLink}" tabindex="-1">${this.$strings.LabelTextEditorLink}</button> |  | ||||||
|       </span> |  | ||||||
|       <span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools"> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" title="${this.$strings.LabelTextEditorBulletedList}" tabindex="-1">${this.$strings.LabelTextEditorBulletedList}</button> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" title="${this.$strings.LabelTextEditorNumberedList}" tabindex="-1">${this.$strings.LabelTextEditorNumberedList}</button> |  | ||||||
|       </span> |  | ||||||
| 
 |  | ||||||
|       <span class="trix-button-group-spacer"></span> |  | ||||||
|       <span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools"> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" title="${this.$strings.LabelUndo}" tabindex="-1">${this.$strings.LabelUndo}</button> |  | ||||||
|         <button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" title="${this.$strings.LabelRedo}" tabindex="-1">${this.$strings.LabelRedo}</button> |  | ||||||
|       </span> |  | ||||||
|     </div> |  | ||||||
|     <div class="trix-dialogs" data-trix-dialogs> |  | ||||||
|       <div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href"> |  | ||||||
|         <div class="trix-dialog__link-fields"> |  | ||||||
|           <input type="url" name="href" class="trix-input trix-input--dialog" placeholder="" aria-label="URL" required data-trix-input> |  | ||||||
|           <div class="trix-button-group"> |  | ||||||
|             <input type="button" class="trix-button trix-button--dialog" value="${this.$strings.LabelTextEditorLink}" data-trix-method="setAttribute"> |  | ||||||
|             <input type="button" class="trix-button trix-button--dialog" value="${this.$strings.LabelTextEditorUnlink}" data-trix-method="removeAttribute"> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div>` |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     trixFileAccept(e) { |     trixFileAccept(e) { | ||||||
|       e.preventDefault() |       e.preventDefault() | ||||||
|  |     }, | ||||||
|  |     blur() { | ||||||
|  |       if (this.$refs.input && this.$refs.input.blur) { | ||||||
|  |         this.$refs.input.blur() | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() {}, |   mounted() {}, | ||||||
|  | |||||||
| @ -1,6 +1,37 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <trix-editor :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" /> |     <trix-toolbar :id="toolbarId"> | ||||||
|  |       <div v-show="!disabledEditor" class="trix-button-row"> | ||||||
|  |         <span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools"> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" :title="$strings.LabelFontBold" tabindex="-1">{{ $strings.LabelFontBold }}</button> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" :title="$strings.LabelFontItalic" tabindex="-1">{{ $strings.LabelFontItalic }}</button> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" :title="$strings.LabelFontStrikethrough" tabindex="-1">{{ $strings.LabelFontStrikethrough }}</button> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" :title="$strings.LabelTextEditorLink" tabindex="-1">{{ $strings.LabelTextEditorLink }}</button> | ||||||
|  |         </span> | ||||||
|  |         <span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools"> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" :title="$strings.LabelTextEditorBulletedList" tabindex="-1">{{ $strings.LabelTextEditorBulletedList }}</button> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" :title="$strings.LabelTextEditorNumberedList" tabindex="-1">{{ $strings.LabelTextEditorNumberedList }}</button> | ||||||
|  |         </span> | ||||||
|  | 
 | ||||||
|  |         <span class="trix-button-group-spacer"></span> | ||||||
|  |         <span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools"> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" :title="$strings.LabelUndo" tabindex="-1">{{ $strings.LabelUndo }}</button> | ||||||
|  |           <button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" :title="$strings.LabelRedo" tabindex="-1">{{ $strings.LabelRedo }}</button> | ||||||
|  |         </span> | ||||||
|  |       </div> | ||||||
|  |       <div class="trix-dialogs" data-trix-dialogs> | ||||||
|  |         <div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href"> | ||||||
|  |           <div class="trix-dialog__link-fields"> | ||||||
|  |             <input type="url" name="href" class="trix-input trix-input--dialog" placeholder="" aria-label="URL" required data-trix-input /> | ||||||
|  |             <div class="trix-button-group"> | ||||||
|  |               <input type="button" class="trix-button trix-button--dialog" :value="$strings.LabelTextEditorLink" data-trix-method="setAttribute" /> | ||||||
|  |               <input type="button" class="trix-button trix-button--dialog" :value="$strings.LabelTextEditorUnlink" data-trix-method="removeAttribute" /> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </trix-toolbar> | ||||||
|  |     <trix-editor :toolbar="toolbarId" :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" /> | ||||||
|     <input type="hidden" :name="inputName" :id="computedId" :value="editorContent" /> |     <input type="hidden" :name="inputName" :id="computedId" :value="editorContent" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @ -14,6 +45,30 @@ | |||||||
| import Trix from 'trix' | import Trix from 'trix' | ||||||
| import '@/assets/trix.css' | 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 { | export default { | ||||||
|   name: 'vue-trix', |   name: 'vue-trix', | ||||||
|   model: { |   model: { | ||||||
| @ -134,6 +189,9 @@ export default { | |||||||
|      * Compute a random id of hidden input |      * Compute a random id of hidden input | ||||||
|      * when it haven't been specified. |      * when it haven't been specified. | ||||||
|      */ |      */ | ||||||
|  |     toolbarId() { | ||||||
|  |       return `trix-toolbar-${this.generateId}` | ||||||
|  |     }, | ||||||
|     generateId() { |     generateId() { | ||||||
|       return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { |       return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | ||||||
|         var r = (Math.random() * 16) | 0 |         var r = (Math.random() * 16) | 0 | ||||||
| @ -223,13 +281,17 @@ export default { | |||||||
|     decorateDisabledEditor(editorState) { |     decorateDisabledEditor(editorState) { | ||||||
|       /** Disable toolbar and editor by pointer events styling */ |       /** Disable toolbar and editor by pointer events styling */ | ||||||
|       if (editorState) { |       if (editorState) { | ||||||
|         this.$refs.trix.toolbarElement.style['pointer-events'] = 'none' |         this.$refs.trix.disabled = true | ||||||
|         this.$refs.trix.contentEditable = false |         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 { |       } 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['pointer-events'] = 'unset' | ||||||
|         this.$refs.trix.style['background'] = 'transparent' |         this.$refs.trix.style['background-color'] = '' | ||||||
|  |         this.$refs.trix.style['color'] = '' | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     overrideConfig(config) { |     overrideConfig(config) { | ||||||
| @ -250,32 +312,15 @@ export default { | |||||||
|       } |       } | ||||||
|       return target |       return target | ||||||
|     }, |     }, | ||||||
|     enableBreakParagraphOnReturn() { |     blur() { | ||||||
|       // Trix works with divs by default, we want paragraphs instead |       if (this.$refs.trix && this.$refs.trix.blur) { | ||||||
|       Trix.config.blockAttributes.default.tagName = 'p' |         this.$refs.trix.blur() | ||||||
|       // 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 |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     /** Override editor configuration */ |     /** Override editor configuration */ | ||||||
|     this.overrideConfig(this.config) |     this.overrideConfig(this.config) | ||||||
|     this.enableBreakParagraphOnReturn() |  | ||||||
|     /** Check if editor read-only mode is required */ |     /** Check if editor read-only mode is required */ | ||||||
|     this.decorateDisabledEditor(this.disabledEditor) |     this.decorateDisabledEditor(this.disabledEditor) | ||||||
|     this.$nextTick(() => { |     this.$nextTick(() => { | ||||||
| @ -305,4 +350,12 @@ export default { | |||||||
| .trix_container .trix-content { | .trix_container .trix-content { | ||||||
|   background-color: white; |   background-color: white; | ||||||
| } | } | ||||||
|  | trix-editor { | ||||||
|  |   max-height: calc(4 * 1lh); | ||||||
|  |   overflow-y: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | trix-editor * { | ||||||
|  |   pointer-events: inherit; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" /> |       <ui-rich-text-editor ref="descriptionInput" v-model="details.description" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" /> | ||||||
| 
 | 
 | ||||||
|       <div class="flex flex-wrap mt-2 -mx-1"> |       <div class="flex flex-wrap mt-2 -mx-1"> | ||||||
|         <div class="w-full md:w-1/2 px-1"> |         <div class="w-full md:w-1/2 px-1"> | ||||||
|  | |||||||
| @ -123,7 +123,7 @@ | |||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <div class="my-4 w-full"> |           <div class="my-4 w-full"> | ||||||
|             <p ref="description" id="item-description" dir="auto" class="text-base text-gray-100 whitespace-pre-line mb-1" :class="{ 'show-full': showFullDescription }">{{ description }}</p> |             <p ref="description" id="item-description" dir="auto" class="default-style less-spacing text-base text-gray-100 whitespace-pre-line mb-1" :class="{ 'show-full': showFullDescription }" v-html="description" /> | ||||||
|             <button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : ''" /></button> |             <button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : ''" /></button> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
| @ -804,8 +804,7 @@ export default { | |||||||
|   display: -webkit-box; |   display: -webkit-box; | ||||||
|   -webkit-box-orient: vertical; |   -webkit-box-orient: vertical; | ||||||
|   -webkit-line-clamp: 4; |   -webkit-line-clamp: 4; | ||||||
|   max-height: 6.25rem; |   max-height: calc(6 * 1lh); | ||||||
|   transition: all 0.3s ease-in-out; |  | ||||||
| } | } | ||||||
| #item-description.show-full { | #item-description.show-full { | ||||||
|   -webkit-line-clamp: unset; |   -webkit-line-clamp: unset; | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ const AudiobookCovers = require('../providers/AudiobookCovers') | |||||||
| const CustomProviderAdapter = require('../providers/CustomProviderAdapter') | const CustomProviderAdapter = require('../providers/CustomProviderAdapter') | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const { levenshteinDistance, escapeRegExp } = require('../utils/index') | const { levenshteinDistance, escapeRegExp } = require('../utils/index') | ||||||
|  | const htmlSanitizer = require('../utils/htmlSanitizer') | ||||||
| 
 | 
 | ||||||
| class BookFinder { | class BookFinder { | ||||||
|   #providerResponseTimeout = 30000 |   #providerResponseTimeout = 30000 | ||||||
| @ -463,6 +464,12 @@ class BookFinder { | |||||||
|     } else { |     } else { | ||||||
|       books = await this.getGoogleBooksResults(title, author) |       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 |     return books | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ const { DataTypes, Model } = require('sequelize') | |||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils') | const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils') | ||||||
| const parseNameString = require('../utils/parsers/parseNameString') | const parseNameString = require('../utils/parsers/parseNameString') | ||||||
|  | const htmlSanitizer = require('../utils/htmlSanitizer') | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef EBookFileObject |  * @typedef EBookFileObject | ||||||
| @ -579,6 +580,7 @@ class Book extends Model { | |||||||
|     oldMetadataJSON.authorNameLF = this.authorNameLF |     oldMetadataJSON.authorNameLF = this.authorNameLF | ||||||
|     oldMetadataJSON.narratorName = (this.narrators || []).join(', ') |     oldMetadataJSON.narratorName = (this.narrators || []).join(', ') | ||||||
|     oldMetadataJSON.seriesName = this.seriesName |     oldMetadataJSON.seriesName = this.seriesName | ||||||
|  |     oldMetadataJSON.descriptionPlain = this.description ? htmlSanitizer.stripAllTags(this.description) : null | ||||||
|     return oldMetadataJSON |     return oldMetadataJSON | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| const axios = require('axios').default | const axios = require('axios').default | ||||||
| const htmlSanitizer = require('../utils/htmlSanitizer') |  | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const { isValidASIN } = require('../utils/index') | const { isValidASIN } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| @ -68,7 +67,7 @@ class Audible { | |||||||
|       narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null, |       narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null, | ||||||
|       publisher: publisherName, |       publisher: publisherName, | ||||||
|       publishedYear: releaseDate ? releaseDate.split('-')[0] : null, |       publishedYear: releaseDate ? releaseDate.split('-')[0] : null, | ||||||
|       description: summary ? htmlSanitizer.stripAllTags(summary) : null, |       description: summary || null, | ||||||
|       cover: image, |       cover: image, | ||||||
|       asin, |       asin, | ||||||
|       genres: genresFiltered.length ? genresFiltered : null, |       genres: genresFiltered.length ? genresFiltered : null, | ||||||
|  | |||||||
| @ -112,7 +112,7 @@ class iTunes { | |||||||
|       artistId: data.artistId, |       artistId: data.artistId, | ||||||
|       title: data.collectionName, |       title: data.collectionName, | ||||||
|       author, |       author, | ||||||
|       description: htmlSanitizer.stripAllTags(data.description || ''), |       description: data.description || null, | ||||||
|       publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null, |       publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null, | ||||||
|       genres: data.primaryGenreName ? [data.primaryGenreName] : null, |       genres: data.primaryGenreName ? [data.primaryGenreName] : null, | ||||||
|       cover: this.getCoverArtwork(data) |       cover: this.getCoverArtwork(data) | ||||||
|  | |||||||
| @ -1,11 +1,9 @@ | |||||||
| const sanitizeHtml = require('../libs/sanitizeHtml') | const sanitizeHtml = require('../libs/sanitizeHtml') | ||||||
| const { entities } = require("./htmlEntities"); | const { entities } = require('./htmlEntities') | ||||||
| 
 | 
 | ||||||
| function sanitize(html) { | function sanitize(html) { | ||||||
|   const sanitizerOptions = { |   const sanitizerOptions = { | ||||||
|     allowedTags: [ |     allowedTags: ['p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br', 'b', 'i'], | ||||||
|       'p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br' |  | ||||||
|     ], |  | ||||||
|     disallowedTagsMode: 'discard', |     disallowedTagsMode: 'discard', | ||||||
|     allowedAttributes: { |     allowedAttributes: { | ||||||
|       a: ['href', 'name', 'target'] |       a: ['href', 'name', 'target'] | ||||||
| @ -34,6 +32,6 @@ function decodeHTMLEntities(strToDecode) { | |||||||
|     if (entity in entities) { |     if (entity in entities) { | ||||||
|       return entities[entity] |       return entities[entity] | ||||||
|     } |     } | ||||||
|     return entity; |     return entity | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user