Extract common methods from PDFOutlineViewer/PDFAttachmentViewer into a new abstract BaseTreeViewer class
				
					
				
			These two classes are unsurprisingly quite similar, and with upcoming changes[1] the amount of (essentially) duplicated code will increase even further. Notable changes: - Collect shared functionality in the `BaseTreeViewer` class, reducing both current and future code-duplication. - Reduce unnecessary duplication in the CSS rules, which will be particularly useful with upcoming changes. - Tweak the attachmentsView to use links, rather than buttons, to simplify (primarily) the CSS rules. --- [1] Once API support for "Optional Content" lands, I've got more-or-less finished patches to add viewer support as well.
This commit is contained in:
		
							parent
							
								
									a289eb8325
								
							
						
					
					
						commit
						6e9da55a39
					
				
							
								
								
									
										111
									
								
								web/base_tree_viewer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								web/base_tree_viewer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | |||||||
|  | /* Copyright 2020 Mozilla Foundation | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import { removeNullCharacters } from "pdfjs-lib"; | ||||||
|  | 
 | ||||||
|  | class BaseTreeViewer { | ||||||
|  |   constructor(options) { | ||||||
|  |     if (this.constructor === BaseTreeViewer) { | ||||||
|  |       throw new Error("Cannot initialize BaseTreeViewer."); | ||||||
|  |     } | ||||||
|  |     this.container = options.container; | ||||||
|  |     this.eventBus = options.eventBus; | ||||||
|  | 
 | ||||||
|  |     this.reset(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   reset() { | ||||||
|  |     this._lastToggleIsShow = true; | ||||||
|  | 
 | ||||||
|  |     // Remove the tree from the DOM.
 | ||||||
|  |     this.container.textContent = ""; | ||||||
|  |     // Ensure that the left (right in RTL locales) margin is always reset,
 | ||||||
|  |     // to prevent incorrect tree alignment if a new document is opened.
 | ||||||
|  |     this.container.classList.remove("treeWithDeepNesting"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _dispatchEvent(count) { | ||||||
|  |     throw new Error("Not implemented: _dispatchEvent"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _bindLink(element, params) { | ||||||
|  |     throw new Error("Not implemented: _bindLink"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _normalizeTextContent(str) { | ||||||
|  |     return removeNullCharacters(str) || /* en dash = */ "\u2013"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Prepend a button before a tree item which allows the user to collapse or | ||||||
|  |    * expand all tree items at that level; see `_toggleTreeItem`. | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _addToggleButton(div, hidden = false) { | ||||||
|  |     const toggler = document.createElement("div"); | ||||||
|  |     toggler.className = "treeItemToggler"; | ||||||
|  |     if (hidden) { | ||||||
|  |       toggler.classList.add("treeItemsHidden"); | ||||||
|  |     } | ||||||
|  |     toggler.onclick = evt => { | ||||||
|  |       evt.stopPropagation(); | ||||||
|  |       toggler.classList.toggle("treeItemsHidden"); | ||||||
|  | 
 | ||||||
|  |       if (evt.shiftKey) { | ||||||
|  |         const shouldShowAll = !toggler.classList.contains("treeItemsHidden"); | ||||||
|  |         this._toggleTreeItem(div, shouldShowAll); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     div.insertBefore(toggler, div.firstChild); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Collapse or expand the subtree of a tree item. | ||||||
|  |    * | ||||||
|  |    * @param {Element} root - the root of the item (sub)tree. | ||||||
|  |    * @param {boolean} show - whether to show the item (sub)tree. If false, | ||||||
|  |    *   the item subtree rooted at `root` will be collapsed. | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _toggleTreeItem(root, show = false) { | ||||||
|  |     this._lastToggleIsShow = show; | ||||||
|  |     for (const toggler of root.querySelectorAll(".treeItemToggler")) { | ||||||
|  |       toggler.classList.toggle("treeItemsHidden", !show); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Collapse or expand all subtrees of the `container`. | ||||||
|  |    * @private | ||||||
|  |    */ | ||||||
|  |   _toggleAllTreeItems() { | ||||||
|  |     this._toggleTreeItem(this.container, !this._lastToggleIsShow); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render(params) { | ||||||
|  |     throw new Error("Not implemented: render"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { BaseTreeViewer }; | ||||||
| @ -13,11 +13,8 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { | import { createPromiseCapability, getFilenameFromUrl } from "pdfjs-lib"; | ||||||
|   createPromiseCapability, | import { BaseTreeViewer } from "./base_tree_viewer.js"; | ||||||
|   getFilenameFromUrl, |  | ||||||
|   removeNullCharacters, |  | ||||||
| } from "pdfjs-lib"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef {Object} PDFAttachmentViewerOptions |  * @typedef {Object} PDFAttachmentViewerOptions | ||||||
| @ -31,16 +28,13 @@ import { | |||||||
|  * @property {Object|null} attachments - A lookup table of attachment objects. |  * @property {Object|null} attachments - A lookup table of attachment objects. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| class PDFAttachmentViewer { | class PDFAttachmentViewer extends BaseTreeViewer { | ||||||
|   /** |   /** | ||||||
|    * @param {PDFAttachmentViewerOptions} options |    * @param {PDFAttachmentViewerOptions} options | ||||||
|    */ |    */ | ||||||
|   constructor({ container, eventBus, downloadManager }) { |   constructor(options) { | ||||||
|     this.container = container; |     super(options); | ||||||
|     this.eventBus = eventBus; |     this.downloadManager = options.downloadManager; | ||||||
|     this.downloadManager = downloadManager; |  | ||||||
| 
 |  | ||||||
|     this.reset(); |  | ||||||
| 
 | 
 | ||||||
|     this.eventBus._on( |     this.eventBus._on( | ||||||
|       "fileattachmentannotation", |       "fileattachmentannotation", | ||||||
| @ -49,10 +43,8 @@ class PDFAttachmentViewer { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   reset(keepRenderedCapability = false) { |   reset(keepRenderedCapability = false) { | ||||||
|     this.attachments = null; |     super.reset(); | ||||||
| 
 |     this._attachments = null; | ||||||
|     // Remove the attachments from the DOM.
 |  | ||||||
|     this.container.textContent = ""; |  | ||||||
| 
 | 
 | ||||||
|     if (!keepRenderedCapability) { |     if (!keepRenderedCapability) { | ||||||
|       // The only situation in which the `_renderedCapability` should *not* be
 |       // The only situation in which the `_renderedCapability` should *not* be
 | ||||||
| @ -100,9 +92,9 @@ class PDFAttachmentViewer { | |||||||
|    * NOTE: Should only be used when `URL.createObjectURL` is natively supported. |    * NOTE: Should only be used when `URL.createObjectURL` is natively supported. | ||||||
|    * @private |    * @private | ||||||
|    */ |    */ | ||||||
|   _bindPdfLink(button, content, filename) { |   _bindPdfLink(element, { content, filename }) { | ||||||
|     let blobUrl; |     let blobUrl; | ||||||
|     button.onclick = () => { |     element.onclick = () => { | ||||||
|       if (!blobUrl) { |       if (!blobUrl) { | ||||||
|         blobUrl = URL.createObjectURL( |         blobUrl = URL.createObjectURL( | ||||||
|           new Blob([content], { type: "application/pdf" }) |           new Blob([content], { type: "application/pdf" }) | ||||||
| @ -141,8 +133,8 @@ class PDFAttachmentViewer { | |||||||
|   /** |   /** | ||||||
|    * @private |    * @private | ||||||
|    */ |    */ | ||||||
|   _bindLink(button, content, filename) { |   _bindLink(element, { content, filename }) { | ||||||
|     button.onclick = () => { |     element.onclick = () => { | ||||||
|       this.downloadManager.downloadData(content, filename, ""); |       this.downloadManager.downloadData(content, filename, ""); | ||||||
|       return false; |       return false; | ||||||
|     }; |     }; | ||||||
| @ -152,42 +144,45 @@ class PDFAttachmentViewer { | |||||||
|    * @param {PDFAttachmentViewerRenderParameters} params |    * @param {PDFAttachmentViewerRenderParameters} params | ||||||
|    */ |    */ | ||||||
|   render({ attachments, keepRenderedCapability = false }) { |   render({ attachments, keepRenderedCapability = false }) { | ||||||
|     if (this.attachments) { |     if (this._attachments) { | ||||||
|       this.reset(keepRenderedCapability === true); |       this.reset(keepRenderedCapability); | ||||||
|     } |     } | ||||||
|     this.attachments = attachments || null; |     this._attachments = attachments || null; | ||||||
| 
 | 
 | ||||||
|     if (!attachments) { |     if (!attachments) { | ||||||
|       this._dispatchEvent(/* attachmentsCount = */ 0); |       this._dispatchEvent(/* attachmentsCount = */ 0); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     const names = Object.keys(attachments).sort(function (a, b) { |     const names = Object.keys(attachments).sort(function (a, b) { | ||||||
|       return a.toLowerCase().localeCompare(b.toLowerCase()); |       return a.toLowerCase().localeCompare(b.toLowerCase()); | ||||||
|     }); |     }); | ||||||
|     const attachmentsCount = names.length; |  | ||||||
| 
 | 
 | ||||||
|     const fragment = document.createDocumentFragment(); |     const fragment = document.createDocumentFragment(); | ||||||
|     for (let i = 0; i < attachmentsCount; i++) { |     let attachmentsCount = 0; | ||||||
|       const item = attachments[names[i]]; |     for (const name of names) { | ||||||
|       const filename = removeNullCharacters(getFilenameFromUrl(item.filename)); |       const item = attachments[name]; | ||||||
|  |       const filename = getFilenameFromUrl(item.filename); | ||||||
| 
 | 
 | ||||||
|       const div = document.createElement("div"); |       const div = document.createElement("div"); | ||||||
|       div.className = "attachmentsItem"; |       div.className = "treeItem"; | ||||||
|       const button = document.createElement("button"); | 
 | ||||||
|       button.textContent = filename; |       const element = document.createElement("a"); | ||||||
|       if ( |       if ( | ||||||
|         /\.pdf$/i.test(filename) && |         /\.pdf$/i.test(filename) && | ||||||
|         !this.downloadManager.disableCreateObjectURL |         !this.downloadManager.disableCreateObjectURL | ||||||
|       ) { |       ) { | ||||||
|         this._bindPdfLink(button, item.content, filename); |         this._bindPdfLink(element, { content: item.content, filename }); | ||||||
|       } else { |       } else { | ||||||
|         this._bindLink(button, item.content, filename); |         this._bindLink(element, { content: item.content, filename }); | ||||||
|       } |       } | ||||||
|  |       element.textContent = this._normalizeTextContent(filename); | ||||||
|  | 
 | ||||||
|  |       div.appendChild(element); | ||||||
| 
 | 
 | ||||||
|       div.appendChild(button); |  | ||||||
|       fragment.appendChild(div); |       fragment.appendChild(div); | ||||||
|  |       attachmentsCount++; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     this.container.appendChild(fragment); |     this.container.appendChild(fragment); | ||||||
| 
 | 
 | ||||||
|     this._dispatchEvent(attachmentsCount); |     this._dispatchEvent(attachmentsCount); | ||||||
| @ -204,7 +199,7 @@ class PDFAttachmentViewer { | |||||||
|       if (renderedPromise !== this._renderedCapability.promise) { |       if (renderedPromise !== this._renderedCapability.promise) { | ||||||
|         return; // The FileAttachment annotation belongs to a previous document.
 |         return; // The FileAttachment annotation belongs to a previous document.
 | ||||||
|       } |       } | ||||||
|       let attachments = this.attachments; |       let attachments = this._attachments; | ||||||
| 
 | 
 | ||||||
|       if (!attachments) { |       if (!attachments) { | ||||||
|         attachments = Object.create(null); |         attachments = Object.create(null); | ||||||
|  | |||||||
| @ -13,9 +13,8 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { addLinkAttributes, LinkTarget, removeNullCharacters } from "pdfjs-lib"; | import { addLinkAttributes, LinkTarget } from "pdfjs-lib"; | ||||||
| 
 | import { BaseTreeViewer } from "./base_tree_viewer.js"; | ||||||
| const DEFAULT_TITLE = "\u2013"; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef {Object} PDFOutlineViewerOptions |  * @typedef {Object} PDFOutlineViewerOptions | ||||||
| @ -29,30 +28,20 @@ const DEFAULT_TITLE = "\u2013"; | |||||||
|  * @property {Array|null} outline - An array of outline objects. |  * @property {Array|null} outline - An array of outline objects. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| class PDFOutlineViewer { | class PDFOutlineViewer extends BaseTreeViewer { | ||||||
|   /** |   /** | ||||||
|    * @param {PDFOutlineViewerOptions} options |    * @param {PDFOutlineViewerOptions} options | ||||||
|    */ |    */ | ||||||
|   constructor({ container, linkService, eventBus }) { |   constructor(options) { | ||||||
|     this.container = container; |     super(options); | ||||||
|     this.linkService = linkService; |     this.linkService = options.linkService; | ||||||
|     this.eventBus = eventBus; |  | ||||||
| 
 | 
 | ||||||
|     this.reset(); |     this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this)); | ||||||
| 
 |  | ||||||
|     eventBus._on("toggleoutlinetree", this.toggleOutlineTree.bind(this)); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   reset() { |   reset() { | ||||||
|     this.outline = null; |     super.reset(); | ||||||
|     this.lastToggleIsShow = true; |     this._outline = null; | ||||||
| 
 |  | ||||||
|     // Remove the outline from the DOM.
 |  | ||||||
|     this.container.textContent = ""; |  | ||||||
| 
 |  | ||||||
|     // Ensure that the left (right in RTL locales) margin is always reset,
 |  | ||||||
|     // to prevent incorrect outline alignment if a new document is opened.
 |  | ||||||
|     this.container.classList.remove("outlineWithDeepNesting"); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
| @ -103,84 +92,51 @@ class PDFOutlineViewer { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Prepend a button before an outline item which allows the user to toggle |  | ||||||
|    * the visibility of all outline items at that level. |  | ||||||
|    * |  | ||||||
|    * @private |    * @private | ||||||
|    */ |    */ | ||||||
|   _addToggleButton(div, { count, items }) { |   _addToggleButton(div, { count, items }) { | ||||||
|     const toggler = document.createElement("div"); |     const hidden = count < 0 && Math.abs(count) === items.length; | ||||||
|     toggler.className = "outlineItemToggler"; |     super._addToggleButton(div, hidden); | ||||||
|     if (count < 0 && Math.abs(count) === items.length) { |  | ||||||
|       toggler.classList.add("outlineItemsHidden"); |  | ||||||
|     } |  | ||||||
|     toggler.onclick = evt => { |  | ||||||
|       evt.stopPropagation(); |  | ||||||
|       toggler.classList.toggle("outlineItemsHidden"); |  | ||||||
| 
 |  | ||||||
|       if (evt.shiftKey) { |  | ||||||
|         const shouldShowAll = !toggler.classList.contains("outlineItemsHidden"); |  | ||||||
|         this._toggleOutlineItem(div, shouldShowAll); |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|     div.insertBefore(toggler, div.firstChild); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Toggle the visibility of the subtree of an outline item. |  | ||||||
|    * |  | ||||||
|    * @param {Element} root - the root of the outline (sub)tree. |  | ||||||
|    * @param {boolean} show - whether to show the outline (sub)tree. If false, |  | ||||||
|    *   the outline subtree rooted at |root| will be collapsed. |  | ||||||
|    * |  | ||||||
|    * @private |    * @private | ||||||
|    */ |    */ | ||||||
|   _toggleOutlineItem(root, show = false) { |   _toggleAllTreeItems() { | ||||||
|     this.lastToggleIsShow = show; |     if (!this._outline) { | ||||||
|     for (const toggler of root.querySelectorAll(".outlineItemToggler")) { |  | ||||||
|       toggler.classList.toggle("outlineItemsHidden", !show); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Collapse or expand all subtrees of the outline. |  | ||||||
|    */ |  | ||||||
|   toggleOutlineTree() { |  | ||||||
|     if (!this.outline) { |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this._toggleOutlineItem(this.container, !this.lastToggleIsShow); |     super._toggleAllTreeItems(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {PDFOutlineViewerRenderParameters} params |    * @param {PDFOutlineViewerRenderParameters} params | ||||||
|    */ |    */ | ||||||
|   render({ outline }) { |   render({ outline }) { | ||||||
|     let outlineCount = 0; |     if (this._outline) { | ||||||
| 
 |  | ||||||
|     if (this.outline) { |  | ||||||
|       this.reset(); |       this.reset(); | ||||||
|     } |     } | ||||||
|     this.outline = outline || null; |     this._outline = outline || null; | ||||||
| 
 | 
 | ||||||
|     if (!outline) { |     if (!outline) { | ||||||
|       this._dispatchEvent(outlineCount); |       this._dispatchEvent(/* outlineCount = */ 0); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const fragment = document.createDocumentFragment(); |     const fragment = document.createDocumentFragment(); | ||||||
|     const queue = [{ parent: fragment, items: this.outline }]; |     const queue = [{ parent: fragment, items: outline }]; | ||||||
|     let hasAnyNesting = false; |     let outlineCount = 0, | ||||||
|  |       hasAnyNesting = false; | ||||||
|     while (queue.length > 0) { |     while (queue.length > 0) { | ||||||
|       const levelData = queue.shift(); |       const levelData = queue.shift(); | ||||||
|       for (const item of levelData.items) { |       for (const item of levelData.items) { | ||||||
|         const div = document.createElement("div"); |         const div = document.createElement("div"); | ||||||
|         div.className = "outlineItem"; |         div.className = "treeItem"; | ||||||
| 
 | 
 | ||||||
|         const element = document.createElement("a"); |         const element = document.createElement("a"); | ||||||
|         this._bindLink(element, item); |         this._bindLink(element, item); | ||||||
|         this._setStyles(element, item); |         this._setStyles(element, item); | ||||||
|         element.textContent = removeNullCharacters(item.title) || DEFAULT_TITLE; |         element.textContent = this._normalizeTextContent(item.title); | ||||||
| 
 | 
 | ||||||
|         div.appendChild(element); |         div.appendChild(element); | ||||||
| 
 | 
 | ||||||
| @ -189,8 +145,9 @@ class PDFOutlineViewer { | |||||||
|           this._addToggleButton(div, item); |           this._addToggleButton(div, item); | ||||||
| 
 | 
 | ||||||
|           const itemsDiv = document.createElement("div"); |           const itemsDiv = document.createElement("div"); | ||||||
|           itemsDiv.className = "outlineItems"; |           itemsDiv.className = "treeItems"; | ||||||
|           div.appendChild(itemsDiv); |           div.appendChild(itemsDiv); | ||||||
|  | 
 | ||||||
|           queue.push({ parent: itemsDiv, items: item.items }); |           queue.push({ parent: itemsDiv, items: item.items }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -199,10 +156,10 @@ class PDFOutlineViewer { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (hasAnyNesting) { |     if (hasAnyNesting) { | ||||||
|       this.container.classList.add("outlineWithDeepNesting"); |       this.container.classList.add("treeWithDeepNesting"); | ||||||
| 
 | 
 | ||||||
|       this.lastToggleIsShow = |       this._lastToggleIsShow = | ||||||
|         fragment.querySelectorAll(".outlineItemsHidden").length === 0; |         fragment.querySelectorAll(".treeItemsHidden").length === 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.container.appendChild(fragment); |     this.container.appendChild(fragment); | ||||||
|  | |||||||
| @ -1169,30 +1169,23 @@ a:focus > .thumbnail > .thumbnailSelectionRing, | |||||||
|   width: calc(100% - 8px); |   width: calc(100% - 8px); | ||||||
|   top: 0; |   top: 0; | ||||||
|   bottom: 0; |   bottom: 0; | ||||||
|  |   padding: 4px 4px 0; | ||||||
|   overflow: auto; |   overflow: auto; | ||||||
|   -webkit-overflow-scrolling: touch; |   -webkit-overflow-scrolling: touch; | ||||||
|   user-select: none; |   user-select: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #outlineView { | html[dir='ltr'] .treeWithDeepNesting > .treeItem, | ||||||
|   padding: 4px 4px 0; | html[dir='ltr'] .treeItem > .treeItems { | ||||||
| } |  | ||||||
| #attachmentsView { |  | ||||||
|   padding: 3px 4px 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| html[dir='ltr'] .outlineWithDeepNesting > .outlineItem, |  | ||||||
| html[dir='ltr'] .outlineItem > .outlineItems { |  | ||||||
|   margin-left: 20px; |   margin-left: 20px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| html[dir='rtl'] .outlineWithDeepNesting > .outlineItem, | html[dir='rtl'] .treeWithDeepNesting > .treeItem, | ||||||
| html[dir='rtl'] .outlineItem > .outlineItems { | html[dir='rtl'] .treeItem > .treeItems { | ||||||
|   margin-right: 20px; |   margin-right: 20px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .outlineItem > a, | .treeItem > a { | ||||||
| .attachmentsItem > button { |  | ||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   min-width: 95%; |   min-width: 95%; | ||||||
| @ -1206,69 +1199,52 @@ html[dir='rtl'] .outlineItem > .outlineItems { | |||||||
|   line-height: 15px; |   line-height: 15px; | ||||||
|   user-select: none; |   user-select: none; | ||||||
|   white-space: normal; |   white-space: normal; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .attachmentsItem > button { |  | ||||||
|   border: 0 none; |  | ||||||
|   background: none; |  | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   width: 100%; |  | ||||||
| } | } | ||||||
| 
 | html[dir='ltr'] .treeItem > a { | ||||||
| html[dir='ltr'] .outlineItem > a { |  | ||||||
|   padding: 2px 0 5px 4px; |   padding: 2px 0 5px 4px; | ||||||
| } | } | ||||||
| html[dir='ltr'] .attachmentsItem > button { | html[dir='rtl'] .treeItem > a { | ||||||
|   padding: 2px 0 3px 7px; |  | ||||||
|   text-align: left; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| html[dir='rtl'] .outlineItem > a { |  | ||||||
|   padding: 2px 4px 5px 0; |   padding: 2px 4px 5px 0; | ||||||
| } | } | ||||||
| html[dir='rtl'] .attachmentsItem > button { |  | ||||||
|   padding: 2px 7px 3px 0; |  | ||||||
|   text-align: right; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .outlineItemToggler { | .treeItemToggler { | ||||||
|   position: relative; |   position: relative; | ||||||
|   height: 0; |   height: 0; | ||||||
|   width: 0; |   width: 0; | ||||||
|   color: rgba(255, 255, 255, 0.5); |   color: rgba(255, 255, 255, 0.5); | ||||||
| } | } | ||||||
| .outlineItemToggler::before { | .treeItemToggler::before { | ||||||
|   content: url(images/treeitem-expanded.png); |   content: url(images/treeitem-expanded.png); | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   position: absolute; |   position: absolute; | ||||||
| } | } | ||||||
| .outlineItemToggler.outlineItemsHidden::before { | .treeItemToggler.treeItemsHidden::before { | ||||||
|   content: url(images/treeitem-collapsed.png); |   content: url(images/treeitem-collapsed.png); | ||||||
| } | } | ||||||
| html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before { | html[dir='rtl'] .treeItemToggler.treeItemsHidden::before { | ||||||
|   transform: scaleX(-1); |   transform: scaleX(-1); | ||||||
| } | } | ||||||
| .outlineItemToggler.outlineItemsHidden ~ .outlineItems { | .treeItemToggler.treeItemsHidden ~ .treeItems { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
| html[dir='ltr'] .outlineItemToggler { | html[dir='ltr'] .treeItemToggler { | ||||||
|   float: left; |   float: left; | ||||||
| } | } | ||||||
| html[dir='rtl'] .outlineItemToggler { | html[dir='rtl'] .treeItemToggler { | ||||||
|   float: right; |   float: right; | ||||||
| } | } | ||||||
| html[dir='ltr'] .outlineItemToggler::before { | html[dir='ltr'] .treeItemToggler::before { | ||||||
|   right: 4px; |   right: 4px; | ||||||
| } | } | ||||||
| html[dir='rtl'] .outlineItemToggler::before { | html[dir='rtl'] .treeItemToggler::before { | ||||||
|   left: 4px; |   left: 4px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .outlineItemToggler:hover, | .treeItemToggler:hover, | ||||||
| .outlineItemToggler:hover + a, | .treeItemToggler:hover + a, | ||||||
| .outlineItemToggler:hover ~ .outlineItems, | .treeItemToggler:hover ~ .treeItems, | ||||||
| .outlineItem > a:hover, | .treeItem > a:hover { | ||||||
| .attachmentsItem > button:hover { |  | ||||||
|   background-color: rgba(255, 255, 255, 0.02); |   background-color: rgba(255, 255, 255, 0.02); | ||||||
|   background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); |   background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); | ||||||
|   background-clip: padding-box; |   background-clip: padding-box; | ||||||
| @ -1279,7 +1255,7 @@ html[dir='rtl'] .outlineItemToggler::before { | |||||||
|   color: rgba(255, 255, 255, 0.9); |   color: rgba(255, 255, 255, 0.9); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .outlineItem.selected { | .treeItem.selected { | ||||||
|   background-color: rgba(255, 255, 255, 0.08); |   background-color: rgba(255, 255, 255, 0.08); | ||||||
|   background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); |   background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); | ||||||
|   background-clip: padding-box; |   background-clip: padding-box; | ||||||
| @ -1745,21 +1721,21 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * { | |||||||
|     content: url(images/secondaryToolbarButton-documentProperties@2x.png); |     content: url(images/secondaryToolbarButton-documentProperties@2x.png); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .outlineItemToggler::before { |   .treeItemToggler::before { | ||||||
|     transform: scale(0.5); |     transform: scale(0.5); | ||||||
|     top: -1px; |     top: -1px; | ||||||
|     content: url(images/treeitem-expanded@2x.png); |     content: url(images/treeitem-expanded@2x.png); | ||||||
|   } |   } | ||||||
|   .outlineItemToggler.outlineItemsHidden::before { |   .treeItemToggler.treeItemsHidden::before { | ||||||
|     content: url(images/treeitem-collapsed@2x.png); |     content: url(images/treeitem-collapsed@2x.png); | ||||||
|   } |   } | ||||||
|   html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before { |   html[dir='rtl'] .treeItemToggler.treeItemsHidden::before { | ||||||
|     transform: scale(-0.5, 0.5); |     transform: scale(-0.5, 0.5); | ||||||
|   } |   } | ||||||
|   html[dir='ltr'] .outlineItemToggler::before { |   html[dir='ltr'] .treeItemToggler::before { | ||||||
|     right: 0; |     right: 0; | ||||||
|   } |   } | ||||||
|   html[dir='rtl'] .outlineItemToggler::before { |   html[dir='rtl'] .treeItemToggler::before { | ||||||
|     left: 0; |     left: 0; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user