Home | History | Annotate | Download | only in src
      1 // Copyright 2015 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 import {View} from "./view.js"
      6 import {anyToString, ViewElements, isIterable} from "./util.js"
      7 import {MySelection} from "./selection.js"
      8 
      9 export abstract class TextView extends View {
     10   selectionHandler: NodeSelectionHandler;
     11   blockSelectionHandler: BlockSelectionHandler;
     12   nodeSelectionHandler: NodeSelectionHandler;
     13   selection: MySelection;
     14   blockSelection: MySelection;
     15   textListNode: HTMLUListElement;
     16   nodeIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
     17   blockIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
     18   blockIdtoNodeIds: Map<string, Array<string>>;
     19   nodeIdToBlockId: Array<string>;
     20   patterns: any;
     21 
     22   constructor(id, broker, patterns) {
     23     super(id);
     24     let view = this;
     25     view.textListNode = view.divNode.getElementsByTagName('ul')[0];
     26     view.patterns = patterns;
     27     view.nodeIdToHtmlElementsMap = new Map();
     28     view.blockIdToHtmlElementsMap = new Map();
     29     view.blockIdtoNodeIds = new Map();
     30     view.nodeIdToBlockId = [];
     31     view.selection = new MySelection(anyToString);
     32     view.blockSelection = new MySelection(anyToString);
     33     const selectionHandler = {
     34       clear: function () {
     35         view.selection.clear();
     36         view.updateSelection();
     37         broker.broadcastClear(selectionHandler);
     38       },
     39       select: function (nodeIds, selected) {
     40         view.selection.select(nodeIds, selected);
     41         view.updateSelection();
     42         broker.broadcastNodeSelect(selectionHandler, view.selection.selectedKeys(), selected);
     43       },
     44       brokeredNodeSelect: function (nodeIds, selected) {
     45         const firstSelect = view.blockSelection.isEmpty();
     46         view.selection.select(nodeIds, selected);
     47         view.updateSelection(firstSelect);
     48       },
     49       brokeredClear: function () {
     50         view.selection.clear();
     51         view.updateSelection();
     52       }
     53     };
     54     this.selectionHandler = selectionHandler;
     55     broker.addNodeHandler(selectionHandler);
     56     view.divNode.onmouseup = function (e) {
     57       if (!e.shiftKey) {
     58         view.selectionHandler.clear();
     59       }
     60     }
     61     const blockSelectionHandler = {
     62       clear: function () {
     63         view.blockSelection.clear();
     64         view.updateSelection();
     65         broker.broadcastClear(blockSelectionHandler);
     66       },
     67       select: function (blockIds, selected) {
     68         view.blockSelection.select(blockIds, selected);
     69         view.updateSelection();
     70         broker.broadcastBlockSelect(blockSelectionHandler, blockIds, selected);
     71       },
     72       brokeredBlockSelect: function (blockIds, selected) {
     73         const firstSelect = view.blockSelection.isEmpty();
     74         view.blockSelection.select(blockIds, selected);
     75         view.updateSelection(firstSelect);
     76       },
     77       brokeredClear: function () {
     78         view.blockSelection.clear();
     79         view.updateSelection();
     80       }
     81     };
     82     this.blockSelectionHandler = blockSelectionHandler;
     83     broker.addBlockHandler(blockSelectionHandler);
     84   }
     85 
     86   addHtmlElementForNodeId(anyNodeId: any, htmlElement: HTMLElement) {
     87     const nodeId = anyToString(anyNodeId);
     88     if (!this.nodeIdToHtmlElementsMap.has(nodeId)) {
     89       this.nodeIdToHtmlElementsMap.set(nodeId, []);
     90     }
     91     this.nodeIdToHtmlElementsMap.get(nodeId).push(htmlElement);
     92   }
     93 
     94   addHtmlElementForBlockId(anyBlockId, htmlElement) {
     95     const blockId = anyToString(anyBlockId);
     96     if (!this.blockIdToHtmlElementsMap.has(blockId)) {
     97       this.blockIdToHtmlElementsMap.set(blockId, []);
     98     }
     99     this.blockIdToHtmlElementsMap.get(blockId).push(htmlElement);
    100   }
    101 
    102   addNodeIdToBlockId(anyNodeId, anyBlockId) {
    103     const blockId = anyToString(anyBlockId);
    104     if (!this.blockIdtoNodeIds.has(blockId)) {
    105       this.blockIdtoNodeIds.set(blockId, []);
    106     }
    107     this.blockIdtoNodeIds.get(blockId).push(anyToString(anyNodeId));
    108     this.nodeIdToBlockId[anyNodeId] = blockId;
    109   }
    110 
    111   blockIdsForNodeIds(nodeIds) {
    112     const blockIds = [];
    113     for (const nodeId of nodeIds) {
    114       const blockId = this.nodeIdToBlockId[nodeId];
    115       if (blockId == undefined) continue;
    116       blockIds.push(blockId);
    117     }
    118     return blockIds;
    119   }
    120 
    121   updateSelection(scrollIntoView: boolean = false) {
    122     if (this.divNode.parentNode == null) return;
    123     const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
    124     const view = this;
    125     for (const [blockId, elements] of this.blockIdToHtmlElementsMap.entries()) {
    126       const isSelected = view.blockSelection.isSelected(blockId);
    127       for (const element of elements) {
    128         mkVisible.consider(element, isSelected);
    129         element.classList.toggle("selected", isSelected);
    130       }
    131     }
    132     for (const key of this.nodeIdToHtmlElementsMap.keys()) {
    133       for (const element of this.nodeIdToHtmlElementsMap.get(key)) {
    134         element.classList.toggle("selected", false);
    135       }
    136     }
    137     for (const nodeId of view.selection.selectedKeys()) {
    138       const elements = this.nodeIdToHtmlElementsMap.get(nodeId);
    139       if (!elements) continue;
    140       for (const element of elements) {
    141         mkVisible.consider(element, true);
    142         element.classList.toggle("selected", true);
    143       }
    144     }
    145     mkVisible.apply(scrollIntoView);
    146   }
    147 
    148   setPatterns(patterns) {
    149     let view = this;
    150     view.patterns = patterns;
    151   }
    152 
    153   clearText() {
    154     let view = this;
    155     while (view.textListNode.firstChild) {
    156       view.textListNode.removeChild(view.textListNode.firstChild);
    157     }
    158   }
    159 
    160   createFragment(text, style) {
    161     let view = this;
    162     let fragment = document.createElement("SPAN");
    163 
    164     if (style.blockId != undefined) {
    165       const blockId = style.blockId(text);
    166       if (blockId != undefined) {
    167         fragment.blockId = blockId;
    168         this.addHtmlElementForBlockId(blockId, fragment);
    169       }
    170     }
    171 
    172     if (typeof style.link == 'function') {
    173       fragment.classList.add('linkable-text');
    174       fragment.onmouseup = function (e) {
    175         e.stopPropagation();
    176         style.link(text)
    177       };
    178     }
    179 
    180     if (typeof style.nodeId == 'function') {
    181       const nodeId = style.nodeId(text);
    182       if (nodeId != undefined) {
    183         fragment.nodeId = nodeId;
    184         this.addHtmlElementForNodeId(nodeId, fragment);
    185       }
    186     }
    187 
    188     if (typeof style.assignBlockId === 'function') {
    189       fragment.blockId = style.assignBlockId();
    190       this.addNodeIdToBlockId(fragment.nodeId, fragment.blockId);
    191     }
    192 
    193     if (typeof style.linkHandler == 'function') {
    194       const handler = style.linkHandler(text, fragment)
    195       if (handler !== undefined) {
    196         fragment.classList.add('linkable-text');
    197         fragment.onmouseup = handler;
    198       }
    199     }
    200 
    201     if (style.css != undefined) {
    202       const css = isIterable(style.css) ? style.css : [style.css];
    203       for (const cls of css) {
    204         fragment.classList.add(cls);
    205       }
    206     }
    207     fragment.innerHTML = text;
    208     return fragment;
    209   }
    210 
    211   processLine(line) {
    212     let view = this;
    213     let result = [];
    214     let patternSet = 0;
    215     while (true) {
    216       let beforeLine = line;
    217       for (let pattern of view.patterns[patternSet]) {
    218         let matches = line.match(pattern[0]);
    219         if (matches != null) {
    220           if (matches[0] != '') {
    221             let style = pattern[1] != null ? pattern[1] : {};
    222             let text = matches[0];
    223             if (text != '') {
    224               let fragment = view.createFragment(matches[0], style);
    225               result.push(fragment);
    226             }
    227             line = line.substr(matches[0].length);
    228           }
    229           let nextPatternSet = patternSet;
    230           if (pattern.length > 2) {
    231             nextPatternSet = pattern[2];
    232           }
    233           if (line == "") {
    234             if (nextPatternSet != -1) {
    235               throw ("illegal parsing state in text-view in patternSet" + patternSet);
    236             }
    237             return result;
    238           }
    239           patternSet = nextPatternSet;
    240           break;
    241         }
    242       }
    243       if (beforeLine == line) {
    244         throw ("input not consumed in text-view in patternSet" + patternSet);
    245       }
    246     }
    247   }
    248 
    249   processText(text) {
    250     let view = this;
    251     let textLines = text.split(/[\n]/);
    252     let lineNo = 0;
    253     for (let line of textLines) {
    254       let li = document.createElement("LI");
    255       li.className = "nolinenums";
    256       li.dataset.lineNo = "" + lineNo++;
    257       let fragments = view.processLine(line);
    258       for (let fragment of fragments) {
    259         li.appendChild(fragment);
    260       }
    261       view.textListNode.appendChild(li);
    262     }
    263   }
    264 
    265   initializeContent(data, rememberedSelection) {
    266     let view = this;
    267     view.clearText();
    268     view.processText(data);
    269   }
    270 
    271   deleteContent() {
    272   }
    273 
    274   isScrollable() {
    275     return true;
    276   }
    277 }
    278