- 1 :
/**
- 2 :
* The Reader tool provides a way of reading documents in the corpus, text is fetched as needed.
- 3 :
*
- 4 :
* @example
- 5 :
*
- 6 :
* let config = {
- 7 :
* "limit": null,
- 8 :
* "query": null,
- 9 :
* "skipTodocId": null,
- 10 :
* "start": null
- 11 :
* };
- 12 :
*
- 13 :
* loadCorpus("austen").tool("Reader", config);
- 14 :
*
- 15 :
* @class Reader
- 16 :
* @tutorial reader
- 17 :
* @memberof Tools
- 18 :
*/
- 19 :
Ext.define('Voyant.panel.Reader', {
- 20 :
extend: 'Ext.panel.Panel',
- 21 :
requires: ['Voyant.data.store.Tokens'],
- 22 :
mixins: ['Voyant.panel.Panel'],
- 23 :
alias: 'widget.reader',
- 24 :
isConsumptive: true,
- 25 :
statics: {
- 26 :
i18n: {
- 27 :
highlightEntities: 'Highlight Entities',
- 28 :
entityType: 'entity type',
- 29 :
nerVoyant: 'Entity Identification with Voyant',
- 30 :
nerNssi: 'Entity Identification with NSSI',
- 31 :
nerSpacy: 'Entity Identification with SpaCy'
- 32 :
},
- 33 :
api: {
- 34 :
/**
- 35 :
* @memberof Tools.Reader
- 36 :
* @instance
- 37 :
* @property {start}
- 38 :
* @default
- 39 :
*/
- 40 :
start: 0,
- 41 :
- 42 :
/**
- 43 :
* @memberof Tools.Reader
- 44 :
* @instance
- 45 :
* @property {limit}
- 46 :
* @default
- 47 :
*/
- 48 :
limit: 1000,
- 49 :
- 50 :
/**
- 51 :
* @memberof Tools.Reader
- 52 :
* @instance
- 53 :
* @property {String} skipToDocId The document ID to start reading from, defaults to the first document in the corpus.
- 54 :
*/
- 55 :
skipToDocId: undefined,
- 56 :
- 57 :
/**
- 58 :
* @memberof Tools.Reader
- 59 :
* @instance
- 60 :
* @property {query}
- 61 :
*/
- 62 :
query: undefined
- 63 :
},
- 64 :
glyph: 'xf0f6@FontAwesome'
- 65 :
},
- 66 :
config: {
- 67 :
innerContainer: undefined,
- 68 :
tokensStore: undefined, // for loading the tokens to display in the reader
- 69 :
documentsStore: undefined, // for storing a copy of the corpus document models
- 70 :
documentTermsStore: undefined, // for getting document term positions for highlighting
- 71 :
documentEntitiesStore: undefined, // for storing the results of an entities call
- 72 :
enableEntitiesList: true, // set to false when using reader as part of entitiesset
- 73 :
exportVisualization: false,
- 74 :
lastScrollTop: 0,
- 75 :
scrollIntoView: false,
- 76 :
insertWhere: 'beforeEnd',
- 77 :
lastLocationUpdate: new Date(),
- 78 :
options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'}]
- 79 :
},
- 80 :
- 81 :
SCROLL_UP: -1,
- 82 :
SCROLL_EQ: 0,
- 83 :
SCROLL_DOWN: 1,
- 84 :
- 85 :
LOCATION_UPDATE_FREQ: 100,
- 86 :
- 87 :
INITIAL_LIMIT: 1000, // need to keep track since limit can be changed when scrolling,
- 88 :
- 89 :
MAX_TOKENS_FOR_NER: 100000, // upper limit on document size for ner submission
- 90 :
- 91 :
constructor: function(config) {
- 92 :
this.mixins['Voyant.util.Api'].constructor.apply(this, arguments);
- 93 :
this.callParent(arguments);
- 94 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 95 :
},
- 96 :
- 97 :
initComponent: function(config) {
- 98 :
var tokensStore = Ext.create("Voyant.data.store.Tokens", {
- 99 :
parentTool: this,
- 100 :
proxy: {
- 101 :
extraParams: {
- 102 :
forTool: 'reader'
- 103 :
}
- 104 :
}
- 105 :
})
- 106 :
var me = this;
- 107 :
tokensStore.on("beforeload", function(store) {
- 108 :
return me.hasCorpusAccess(store.getCorpus());
- 109 :
})
- 110 :
tokensStore.on("load", function(s, records, success) {
- 111 :
if (success) {
- 112 :
var contents = "";
- 113 :
var documentFrequency = this.localize("documentFrequency");
- 114 :
var isPlainText = false;
- 115 :
var docIndex = -1;
- 116 :
var isLastNewLine = false;
- 117 :
records.forEach(function(record) {
- 118 :
if (record.getPosition()==0) {
- 119 :
contents+="<h3>"+this.getDocumentsStore().getById(record.getDocId()).getFullLabel()+"</h3>";
- 120 :
}
- 121 :
if (record.getDocIndex()!=docIndex) {
- 122 :
isPlainText = this.getDocumentsStore().getById(record.getDocId()).isPlainText();
- 123 :
docIndex = record.getDocIndex();
- 124 :
}
- 125 :
if (record.isWord()) {
- 126 :
isLastNewLine = false;
- 127 :
contents += "<span class='word' id='"+ record.getId() + "' data-qtip='<div class=\"freq\">"+documentFrequency+" "+record.getDocumentRawFreq()+"</div>'>"+ record.getTerm() + "</span>";
- 128 :
}
- 129 :
else {
- 130 :
var newContents = record.getTermWithLineSpacing(isPlainText);
- 131 :
var isNewLine = newContents.indexOf("<br />")==0;
- 132 :
if (isLastNewLine && (isNewLine || newContents.trim().length==0)) {}
- 133 :
else {
- 134 :
contents += newContents;
- 135 :
isLastNewLine = isNewLine;
- 136 :
}
- 137 :
}
- 138 :
}, this);
- 139 :
this.updateText(contents);
- 140 :
- 141 :
this.highlightKeywords();
- 142 :
- 143 :
if (this.getDocumentEntitiesStore() !== undefined) {
- 144 :
this.highlightEntities();
- 145 :
}
- 146 :
}
- 147 :
}, this);
- 148 :
this.setTokensStore(tokensStore);
- 149 :
- 150 :
this.on("query", function(src, queries) {
- 151 :
this.loadQueryTerms(queries);
- 152 :
}, this);
- 153 :
- 154 :
this.setDocumentTermsStore(Ext.create("Ext.data.Store", {
- 155 :
model: "Voyant.data.model.DocumentTerm",
- 156 :
autoLoad: false,
- 157 :
remoteSort: false,
- 158 :
proxy: {
- 159 :
type: 'ajax',
- 160 :
url: Voyant.application.getTromboneUrl(),
- 161 :
extraParams: {
- 162 :
tool: 'corpus.DocumentTerms',
- 163 :
withPositions: true,
- 164 :
bins: 25,
- 165 :
forTool: 'reader'
- 166 :
},
- 167 :
reader: {
- 168 :
type: 'json',
- 169 :
rootProperty: 'documentTerms.terms',
- 170 :
totalProperty: 'documentTerms.total'
- 171 :
},
- 172 :
simpleSortMode: true
- 173 :
},
- 174 :
listeners: {
- 175 :
load: function(store, records, successful, opts) {
- 176 :
this.highlightKeywords(records);
- 177 :
},
- 178 :
scope: this
- 179 :
}
- 180 :
}));
- 181 :
- 182 :
this.on("afterrender", function() {
- 183 :
var centerPanel = this.down('panel[region="center"]');
- 184 :
this.setInnerContainer(centerPanel.getLayout().getRenderTarget());
- 185 :
- 186 :
// scroll listener
- 187 :
centerPanel.body.on("scroll", function(event, target) {
- 188 :
var scrollDir = this.getLastScrollTop() < target.scrollTop ? this.SCROLL_DOWN
- 189 :
: this.getLastScrollTop() > target.scrollTop ? this.SCROLL_UP
- 190 :
: this.SCROLL_EQ;
- 191 :
- 192 :
// scroll up
- 193 :
if (scrollDir == this.SCROLL_UP && target.scrollTop < 1) {
- 194 :
this.fetchPrevious(true);
- 195 :
// scroll down
- 196 :
} else if (scrollDir == this.SCROLL_DOWN && target.scrollHeight - target.scrollTop < target.offsetHeight*1.5) {//target.scrollTop+target.offsetHeight>target.scrollHeight/2) { // more than half-way down
- 197 :
this.fetchNext(false);
- 198 :
} else {
- 199 :
var amount;
- 200 :
if (target.scrollTop == 0) {
- 201 :
amount = 0;
- 202 :
} else if (target.scrollHeight - target.scrollTop == target.clientHeight) {
- 203 :
amount = 1;
- 204 :
} else {
- 205 :
amount = (target.scrollTop + target.clientHeight * 0.5) / target.scrollHeight;
- 206 :
}
- 207 :
- 208 :
var now = new Date();
- 209 :
if (now - this.getLastLocationUpdate() > this.LOCATION_UPDATE_FREQ || amount == 0 || amount == 1) {
- 210 :
this.updateLocationMarker(amount, scrollDir);
- 211 :
}
- 212 :
}
- 213 :
this.setLastScrollTop(target.scrollTop);
- 214 :
}, this);
- 215 :
- 216 :
// click listener
- 217 :
centerPanel.body.on("click", function(event, target) {
- 218 :
target = Ext.get(target);
- 219 :
// if (target.hasCls('entity')) {} TODO
- 220 :
if (target.hasCls('word')) {
- 221 :
var info = Voyant.data.model.Token.getInfoFromElement(target);
- 222 :
var term = target.getHtml();
- 223 :
var data = [{
- 224 :
term: term,
- 225 :
docIndex: info.docIndex
- 226 :
}];
- 227 :
this.loadQueryTerms([term]);
- 228 :
this.getApplication().dispatchEvent('termsClicked', this, data);
- 229 :
}
- 230 :
}, this);
- 231 :
- 232 :
if (this.getCorpus()) {
- 233 :
if (this.getApiParam('skipToDocId') === undefined) {
- 234 :
this.setApiParam('skipToDocId', this.getCorpus().getDocument(0).getId());
- 235 :
}
- 236 :
this.load();
- 237 :
var query = this.getApiParam('query');
- 238 :
if (query) {
- 239 :
this.loadQueryTerms(Ext.isString(query) ? [query] : query);
- 240 :
}
- 241 :
}
- 242 :
this.on("loadedCorpus", function() {
- 243 :
if (this.getApiParam('skipToDocId') === undefined) {
- 244 :
this.setApiParam('skipToDocId', this.getCorpus().getDocument(0).getId());
- 245 :
}
- 246 :
this.load(true); // make sure to clear in case we're replacing the corpus
- 247 :
var query = this.getApiParam('query');
- 248 :
if (query) {
- 249 :
this.loadQueryTerms(Ext.isString(query) ? [query] : query);
- 250 :
}
- 251 :
}, this);
- 252 :
}, this);
- 253 :
- 254 :
Ext.apply(this, {
- 255 :
title: this.localize('title'),
- 256 :
cls: 'voyant-reader',
- 257 :
layout: 'fit',
- 258 :
items: {
- 259 :
layout: 'border',
- 260 :
items: [{
- 261 :
bodyPadding: 10,
- 262 :
region: 'center',
- 263 :
border: false,
- 264 :
autoScroll: true,
- 265 :
html: '<div class="readerContainer"></div>'
- 266 :
},{
- 267 :
xtype: 'readergraph',
- 268 :
region: 'south',
- 269 :
weight: 0,
- 270 :
height: 30,
- 271 :
split: {
- 272 :
size: 2
- 273 :
},
- 274 :
splitterResize: true,
- 275 :
border: false,
- 276 :
listeners: {
- 277 :
documentRelativePositionSelected: function(src, data) {
- 278 :
var doc = this.getDocumentsStore().getAt(data.docIndex);
- 279 :
var totalTokens = doc.get('tokensCount-lexical');
- 280 :
var position = Math.floor(totalTokens * data.fraction);
- 281 :
var bufferPosition = position - (this.getApiParam('limit')/2);
- 282 :
this.setApiParams({'skipToDocId': doc.getId(), start: bufferPosition < 0 ? 0 : bufferPosition});
- 283 :
this.load(true);
- 284 :
},
- 285 :
scope: this
- 286 :
}
- 287 :
},{
- 288 :
xtype: 'entitieslist',
- 289 :
region: 'east',
- 290 :
weight: 10,
- 291 :
width: '40%',
- 292 :
split: {
- 293 :
size: 2
- 294 :
},
- 295 :
splitterResize: true,
- 296 :
border: false,
- 297 :
hidden: true,
- 298 :
collapsible: true,
- 299 :
animCollapse: false
- 300 :
}]
- 301 :
},
- 302 :
- 303 :
dockedItems: [{
- 304 :
dock: 'bottom',
- 305 :
xtype: 'toolbar',
- 306 :
overflowHandler: 'scroller',
- 307 :
items: [{
- 308 :
glyph: 'xf060@FontAwesome',
- 309 :
handler: function() {
- 310 :
this.fetchPrevious(true);
- 311 :
},
- 312 :
scope: this
- 313 :
},{
- 314 :
glyph: 'xf061@FontAwesome',
- 315 :
handler: function() {
- 316 :
this.fetchNext(true);
- 317 :
},
- 318 :
scope: this
- 319 :
},{xtype: 'tbseparator'},{
- 320 :
xtype: 'querysearchfield'
- 321 :
},'->',{
- 322 :
glyph: 'xf0eb@FontAwesome',
- 323 :
tooltip: this.localize('highlightEntities'),
- 324 :
itemId: 'nerServiceParent',
- 325 :
hidden: true,
- 326 :
menu: {
- 327 :
items: [{
- 328 :
xtype: 'menucheckitem',
- 329 :
group: 'nerService',
- 330 :
text: this.localize('nerSpacy'),
- 331 :
itemId: 'spacy',
- 332 :
checked: true,
- 333 :
handler: this.nerServiceHandler,
- 334 :
scope: this
- 335 :
},{
- 336 :
xtype: 'menucheckitem',
- 337 :
group: 'nerService',
- 338 :
text: this.localize('nerNssi'),
- 339 :
itemId: 'nssi',
- 340 :
checked: false,
- 341 :
handler: this.nerServiceHandler,
- 342 :
scope: this
- 343 :
},{
- 344 :
xtype: 'menucheckitem',
- 345 :
group: 'nerService',
- 346 :
text: this.localize('nerVoyant'),
- 347 :
itemId: 'stanford',
- 348 :
checked: false,
- 349 :
handler: this.nerServiceHandler,
- 350 :
scope: this
- 351 :
}
- 352 :
// ,{
- 353 :
// xtype: 'menucheckitem',
- 354 :
// group: 'nerService',
- 355 :
// text: 'NER with Voyant (OpenNLP)',
- 356 :
// itemId: 'opennlp',
- 357 :
// checked: false,
- 358 :
// handler: this.nerServiceHandler,
- 359 :
// scope: this
- 360 :
// }
- 361 :
]
- 362 :
}
- 363 :
}]
- 364 :
}],
- 365 :
listeners: {
- 366 :
loadedCorpus: function(src, corpus) {
- 367 :
this.getTokensStore().setCorpus(corpus);
- 368 :
this.getDocumentTermsStore().getProxy().setExtraParam('corpus', corpus.getId());
- 369 :
- 370 :
var docs = corpus.getDocuments();
- 371 :
this.setDocumentsStore(docs);
- 372 :
- 373 :
if (this.rendered) {
- 374 :
this.load();
- 375 :
if (this.hasCorpusAccess(corpus)==false) {
- 376 :
this.mask(this.localize("limitedAccess"), 'mask-no-spinner')
- 377 :
}
- 378 :
var query = this.getApiParam('query');
- 379 :
if (query) {
- 380 :
this.loadQueryTerms(Ext.isString(query) ? [query] : query);
- 381 :
}
- 382 :
}
- 383 :
- 384 :
},
- 385 :
termsClicked: function(src, terms) {
- 386 :
var queryTerms = [];
- 387 :
terms.forEach(function(term) {
- 388 :
if (Ext.isString(term)) {queryTerms.push(term);}
- 389 :
else if (term.term) {queryTerms.push(term.term);}
- 390 :
else if (term.getTerm) {queryTerms.push(term.getTerm());}
- 391 :
});
- 392 :
if (queryTerms.length > 0) {
- 393 :
this.loadQueryTerms(queryTerms);
- 394 :
}
- 395 :
},
- 396 :
corpusTermsClicked: function(src, terms) {
- 397 :
var queryTerms = [];
- 398 :
terms.forEach(function(term) {
- 399 :
if (term.getTerm()) {queryTerms.push(term.getTerm());}
- 400 :
});
- 401 :
this.loadQueryTerms(queryTerms);
- 402 :
},
- 403 :
documentTermsClicked: function(src, terms) {
- 404 :
var queryTerms = [];
- 405 :
terms.forEach(function(term) {
- 406 :
if (term.getTerm()) {queryTerms.push(term.getTerm());}
- 407 :
});
- 408 :
this.loadQueryTerms(queryTerms);
- 409 :
},
- 410 :
documentSelected: function(src, document) {
- 411 :
var corpus = this.getTokensStore().getCorpus();
- 412 :
var doc = corpus.getDocument(document);
- 413 :
this.setApiParams({'skipToDocId': doc.getId(), start: 0});
- 414 :
this.load(true);
- 415 :
},
- 416 :
documentsClicked: function(src, documents, corpus) {
- 417 :
if (documents.length > 0) {
- 418 :
var doc = documents[0];
- 419 :
this.setApiParams({'skipToDocId': doc.getId(), start: 0});
- 420 :
this.load(true);
- 421 :
}
- 422 :
},
- 423 :
termLocationClicked: function(src, terms) {
- 424 :
if (terms[0] !== undefined) {
- 425 :
var term = terms[0];
- 426 :
var docIndex = term.get('docIndex');
- 427 :
var position = term.get('position');
- 428 :
this.showTermLocation(docIndex, position, term);
- 429 :
};
- 430 :
},
- 431 :
documentIndexTermsClicked: function(src, terms) {
- 432 :
if (terms[0] !== undefined) {
- 433 :
var term = terms[0];
- 434 :
var termRec = Ext.create('Voyant.data.model.Token', term);
- 435 :
this.fireEvent('termLocationClicked', this, [termRec]);
- 436 :
}
- 437 :
},
- 438 :
entityResults: function(src, entities) {
- 439 :
if (entities !== null) {
- 440 :
this.clearEntityHighlights(); // clear again in case failed documents were rerun
- 441 :
this.setDocumentEntitiesStore(entities);
- 442 :
this.highlightEntities();
- 443 :
if (this.getEnableEntitiesList()) {
- 444 :
this.down('entitieslist').expand().show();
- 445 :
}
- 446 :
}
- 447 :
},
- 448 :
entitiesClicked: function(src, entities) {
- 449 :
if (entities[0] !== undefined) {
- 450 :
var entity = entities[0];
- 451 :
var docIndex = entity.get('docIndex');
- 452 :
var position = entity.get('positions')[0];
- 453 :
if (Array.isArray(position)) position = position[0];
- 454 :
this.showTermLocation(docIndex, position, entity);
- 455 :
}
- 456 :
},
- 457 :
entityLocationClicked: function(src, entity, positionIndex) {
- 458 :
var docIndex = entity.get('docIndex');
- 459 :
var position = entity.get('positions')[positionIndex];
- 460 :
if (Array.isArray(position)) position = position[0];
- 461 :
this.showTermLocation(docIndex, position, entity);
- 462 :
},
- 463 :
scope: this
- 464 :
}
- 465 :
});
- 466 :
- 467 :
this.callParent(arguments);
- 468 :
},
- 469 :
- 470 :
loadQueryTerms: function(queryTerms) {
- 471 :
if (queryTerms && queryTerms.length > 0) {
- 472 :
var docId = this.getApiParam('skipToDocId');
- 473 :
if (docId === undefined) {
- 474 :
var docIndex = 0;
- 475 :
var locationInfo = this.getLocationInfo();
- 476 :
if (locationInfo) {
- 477 :
docIndex = locationInfo[0].docIndex;
- 478 :
}
- 479 :
docId = this.getCorpus().getDocument(docIndex).getId();
- 480 :
}
- 481 :
this.getDocumentTermsStore().load({
- 482 :
params: {
- 483 :
query: queryTerms,
- 484 :
docId: docId,
- 485 :
categories: this.getApiParam('categories'),
- 486 :
limit: -1
- 487 :
}
- 488 :
});
- 489 :
this.down('readergraph').loadQueryTerms(queryTerms);
- 490 :
}
- 491 :
},
- 492 :
- 493 :
showTermLocation: function(docIndex, position, term) {
- 494 :
var bufferPosition = position - (this.getApiParam('limit')/2);
- 495 :
var doc = this.getCorpus().getDocument(docIndex);
- 496 :
this.setApiParams({'skipToDocId': doc.getId(), start: bufferPosition < 0 ? 0 : bufferPosition});
- 497 :
this.load(true, {
- 498 :
callback: function() {
- 499 :
var el = this.body.dom.querySelector("#_" + docIndex + "_" + position);
- 500 :
if (el) {
- 501 :
el.scrollIntoView({
- 502 :
block: 'center'
- 503 :
});
- 504 :
Ext.fly(el).frame('#f80');
- 505 :
}
- 506 :
if (term.get('type')) {
- 507 :
this.highlightEntities();
- 508 :
} else {
- 509 :
this.highlightKeywords(term, false);
- 510 :
}
- 511 :
},
- 512 :
scope: this
- 513 :
});
- 514 :
},
- 515 :
- 516 :
highlightKeywords: function(termRecords, doScroll) {
- 517 :
var container = this.getInnerContainer().first();
- 518 :
container.select('span[class*=keyword]').removeCls('keyword').applyStyles({backgroundColor: 'transparent', color: 'black'});
- 519 :
- 520 :
if (termRecords === undefined && this.getDocumentTermsStore().getCount() > 0) {
- 521 :
termRecords = this.getDocumentTermsStore().getData().items;
- 522 :
}
- 523 :
if (termRecords === undefined) {
- 524 :
return;
- 525 :
}
- 526 :
- 527 :
if (!Ext.isArray(termRecords)) termRecords = [termRecords];
- 528 :
- 529 :
termRecords.forEach(function(r) {
- 530 :
var term = r.get('term');
- 531 :
var bgColor = this.getApplication().getColorForTerm(term);
- 532 :
var textColor = this.getApplication().getTextColorForBackground(bgColor);
- 533 :
bgColor = 'rgb('+bgColor.join(',')+') !important';
- 534 :
textColor = 'rgb('+textColor.join(',')+') !important';
- 535 :
var styles = 'background-color:'+bgColor+';color:'+textColor+';';
- 536 :
- 537 :
// might be slightly faster to use positions so do that if they're available
- 538 :
if (r.get('positions')) {
- 539 :
var positions = r.get('positions');
- 540 :
var docIndex = r.get('docIndex');
- 541 :
- 542 :
positions.forEach(function(pos) {
- 543 :
var match = container.dom.querySelector('#_'+docIndex+'_'+pos);
- 544 :
if (match) {
- 545 :
Ext.fly(match).addCls('keyword').dom.setAttribute('style', styles);
- 546 :
}
- 547 :
})
- 548 :
} else {
- 549 :
var caseInsensitiveQuery = new RegExp('^'+term+'$', 'i');
- 550 :
var nodes = container.select('span.word');
- 551 :
nodes.each(function(el, compEl, index) {
- 552 :
if (el.dom.firstChild && el.dom.firstChild.nodeValue.match(caseInsensitiveQuery)) {
- 553 :
el.addCls('keyword').dom.setAttribute('style', styles);
- 554 :
}
- 555 :
});
- 556 :
}
- 557 :
}, this);
- 558 :
},
- 559 :
- 560 :
nerServiceHandler: function(menuitem) {
- 561 :
var annotator = menuitem.itemId;
- 562 :
- 563 :
var docIndex = [];
- 564 :
var locationInfo = this.getLocationInfo();
- 565 :
if (locationInfo) {
- 566 :
for (var i = locationInfo[0].docIndex; i <= locationInfo[1].docIndex; i++) {
- 567 :
docIndex.push(i);
- 568 :
}
- 569 :
} else {
- 570 :
docIndex.push(0);
- 571 :
}
- 572 :
- 573 :
this.clearEntityHighlights();
- 574 :
- 575 :
var entitiesList = this.down('entitieslist');
- 576 :
entitiesList.clearEntities();
- 577 :
entitiesList.getEntities(annotator, docIndex);
- 578 :
},
- 579 :
- 580 :
clearEntityHighlights: function() {
- 581 :
var container = this.getInnerContainer().first();
- 582 :
container.select('.entity').each(function(el) {
- 583 :
el.removeCls('entity start middle end location person organization misc money time percent date duration set unknown');
- 584 :
el.dom.setAttribute('data-qtip', el.dom.getAttribute('data-qtip').replace(/<div class="entity">.*?<\/div>/g, ''));
- 585 :
});
- 586 :
},
- 587 :
- 588 :
highlightEntities: function() {
- 589 :
var container = this.getInnerContainer().first();
- 590 :
var entities = this.getDocumentEntitiesStore();
- 591 :
var entityTypeStr = this.localize('entityType');
- 592 :
entities.forEach(function(entity) {
- 593 :
var positionInstances = entity.positions;
- 594 :
if (positionInstances) {
- 595 :
positionInstances.forEach(function(positions) {
- 596 :
var multiTermEntity = positions.length > 1;
- 597 :
if (multiTermEntity) {
- 598 :
// find the difference between start and end positions
- 599 :
if (positions.length === 2 && positions[1]-positions[0] > 1) {
- 600 :
// more than two terms, so fill in the middle positions
- 601 :
var endPos = positions[1];
- 602 :
var curPos = positions[0]+1;
- 603 :
var curIndex = 1;
- 604 :
while (curPos < endPos) {
- 605 :
positions.splice(curIndex, 0, curPos);
- 606 :
curPos++;
- 607 :
curIndex++;
- 608 :
}
- 609 :
}
- 610 :
}
- 611 :
- 612 :
for (var i = 0, len = positions.length; i < len; i++) {
- 613 :
var position = positions[i];
- 614 :
if (position === -1) {
- 615 :
console.warn('missing position for: '+entity.term);
- 616 :
} else {
- 617 :
var match = container.selectNode('#_'+entity.docIndex+'_'+position, false);
- 618 :
if (match) {
- 619 :
var termEntityPosition = '';
- 620 :
if (multiTermEntity) {
- 621 :
if (i === 0) {
- 622 :
termEntityPosition = 'start ';
- 623 :
} else if (i === len-1) {
- 624 :
termEntityPosition = 'end ';
- 625 :
} else {
- 626 :
termEntityPosition = 'middle ';
- 627 :
}
- 628 :
}
- 629 :
- 630 :
match.addCls('entity '+termEntityPosition+entity.type);
- 631 :
var prevQTip = match.dom.getAttribute('data-qtip');
- 632 :
if (prevQTip.indexOf('class="entity"') === -1) {
- 633 :
match.dom.setAttribute('data-qtip', prevQTip+'<div class="entity">'+entityTypeStr+': '+entity.type+'</div>');
- 634 :
}
- 635 :
}
- 636 :
}
- 637 :
}
- 638 :
});
- 639 :
} else {
- 640 :
console.warn('no positions for: '+entity.term);
- 641 :
}
- 642 :
});
- 643 :
},
- 644 :
- 645 :
fetchPrevious: function(scroll) {
- 646 :
var readerContainer = this.getInnerContainer().first();
- 647 :
var first = readerContainer.first('.word');
- 648 :
if (first != null && first.hasCls("loading")===false) {
- 649 :
while(first) {
- 650 :
if (first.hasCls("word")) {
- 651 :
var info = Voyant.data.model.Token.getInfoFromElement(first);
- 652 :
var docIndex = info.docIndex;
- 653 :
var start = info.position;
- 654 :
var doc = this.getDocumentsStore().getAt(docIndex);
- 655 :
var limit = this.getApiParam('limit');
- 656 :
var getPrevDoc = false;
- 657 :
if (docIndex === 0 && start === 0) {
- 658 :
var scrollContainer = this.down('panel[region="center"]').body;
- 659 :
var scrollNeeded = first.getScrollIntoViewXY(scrollContainer, scrollContainer.dom.scrollTop, scrollContainer.dom.scrollLeft);
- 660 :
if (scrollNeeded.y != 0) {
- 661 :
first.dom.scrollIntoView();
- 662 :
}
- 663 :
first.frame("red");
- 664 :
break;
- 665 :
}
- 666 :
if (docIndex > 0 && start === 0) {
- 667 :
getPrevDoc = true;
- 668 :
docIndex--;
- 669 :
doc = this.getDocumentsStore().getAt(docIndex);
- 670 :
var totalTokens = doc.get('tokensCount-lexical');
- 671 :
start = totalTokens-limit;
- 672 :
if (start < 0) {
- 673 :
start = 0;
- 674 :
this.setApiParam('limit', totalTokens);
- 675 :
}
- 676 :
} else {
- 677 :
limit--; // subtract one to limit for the word we're removing. need to do this to account for non-lexical tokens before/after first word.
- 678 :
start -= limit;
- 679 :
}
- 680 :
if (start < 0) start = 0;
- 681 :
- 682 :
var mask = first.insertSibling("<div class='loading'>"+this.localize('loading')+"</div>", 'before', false).mask();
- 683 :
if (!getPrevDoc) {
- 684 :
first.destroy();
- 685 :
}
- 686 :
- 687 :
var id = doc.getId();
- 688 :
this.setApiParams({'skipToDocId': id, start: start});
- 689 :
this.setInsertWhere('afterBegin')
- 690 :
this.setScrollIntoView(scroll);
- 691 :
this.load();
- 692 :
this.setApiParam('limit', this.INITIAL_LIMIT);
- 693 :
break;
- 694 :
}
- 695 :
first.destroy(); // remove non word
- 696 :
first = readerContainer.first();
- 697 :
}
- 698 :
}
- 699 :
},
- 700 :
- 701 :
fetchNext: function(scroll) {
- 702 :
var readerContainer = this.getInnerContainer().first();
- 703 :
var last = readerContainer.last();
- 704 :
if (last.hasCls("loading")===false) {
- 705 :
while(last) {
- 706 :
if (last.hasCls("word")) {
- 707 :
var info = Voyant.data.model.Token.getInfoFromElement(last);
- 708 :
var docIndex = info.docIndex;
- 709 :
var start = info.position;
- 710 :
var doc = this.getDocumentsStore().getAt(info.docIndex);
- 711 :
var id = doc.getId();
- 712 :
- 713 :
var totalTokens = doc.get('tokensCount-lexical');
- 714 :
if (start + this.getApiParam('limit') >= totalTokens && docIndex == this.getCorpus().getDocumentsCount()-1) {
- 715 :
var limit = totalTokens - start;
- 716 :
if (limit <= 1) {
- 717 :
last.dom.scrollIntoView();
- 718 :
last.frame("red")
- 719 :
break;
- 720 :
} else {
- 721 :
this.setApiParam('limit', limit);
- 722 :
}
- 723 :
}
- 724 :
- 725 :
// remove any text after the last word
- 726 :
var nextSib = last.dom.nextSibling;
- 727 :
while(nextSib) {
- 728 :
var oldNext = nextSib;
- 729 :
nextSib = nextSib.nextSibling;
- 730 :
oldNext.parentNode.removeChild(oldNext);
- 731 :
}
- 732 :
- 733 :
var mask = last.insertSibling("<div class='loading'>"+this.localize('loading')+"</div>", 'after', false).mask();
- 734 :
last.destroy();
- 735 :
this.setApiParams({'skipToDocId': id, start: info.position});
- 736 :
this.setInsertWhere('beforeEnd');
- 737 :
this.setScrollIntoView(scroll);
- 738 :
this.load(); // callback not working on buffered store
- 739 :
this.setApiParam('limit', this.INITIAL_LIMIT);
- 740 :
break;
- 741 :
}
- 742 :
last.destroy(); // remove non word
- 743 :
last = readerContainer.last();
- 744 :
}
- 745 :
}
- 746 :
},
- 747 :
- 748 :
load: function(doClear, config) {
- 749 :
if (doClear) {
- 750 :
this.getInnerContainer().first().destroy(); // clear everything
- 751 :
this.getInnerContainer().setHtml('<div class="readerContainer"><div class="loading">'+this.localize('loading')+'</div></div>');
- 752 :
this.getInnerContainer().first().first().mask();
- 753 :
}
- 754 :
- 755 :
// check if we're loading a different doc and update terms store if so
- 756 :
var tokensStore = this.getTokensStore();
- 757 :
if (tokensStore.lastOptions && tokensStore.lastOptions.params.skipToDocId && tokensStore.lastOptions.params.skipToDocId !== this.getApiParam('skipToDocId')) {
- 758 :
var dts = this.getDocumentTermsStore();
- 759 :
if (dts.lastOptions) {
- 760 :
var query = dts.lastOptions.params.query;
- 761 :
this.loadQueryTerms(query);
- 762 :
}
- 763 :
}
- 764 :
- 765 :
this.getTokensStore().load(Ext.apply(config || {}, {
- 766 :
params: Ext.apply(this.getApiParams(), {
- 767 :
stripTags: 'blocksOnly',
- 768 :
stopList: '' // token requests shouldn't have stopList
- 769 :
})
- 770 :
}));
- 771 :
},
- 772 :
- 773 :
updateText: function(contents) {
- 774 :
var loadingMask = this.getInnerContainer().down('.loading');
- 775 :
if (loadingMask) loadingMask.destroy();
- 776 :
// FIXME: something is weird here in tool/Reader mode, this.getInnerContainer() seems empty but this.getInnerContainer().first() gets the canvas?!?
- 777 :
var inserted = this.getInnerContainer().first().insertHtml(this.getInsertWhere()/* where is this defined? */, contents, true); // return Element, not dom
- 778 :
if (inserted && this.getScrollIntoView()) {
- 779 :
inserted.dom.scrollIntoView(); // use dom
- 780 :
// we can't rely on the returned element because it can be a transient fly element, but the id is right in a deferred call
- 781 :
Ext.Function.defer(function() {
- 782 :
var el = Ext.get(inserted.id); // re-get el
- 783 :
if (el) {el.frame("red")}
- 784 :
}, 100);
- 785 :
}
- 786 :
var target = this.down('panel[region="center"]').body.dom;
- 787 :
var amount;
- 788 :
if (target.scrollTop == 0) {
- 789 :
amount = 0;
- 790 :
} else if (target.scrollHeight - target.scrollTop == target.clientHeight) {
- 791 :
amount = 1;
- 792 :
} else {
- 793 :
amount = (target.scrollTop + target.clientHeight * 0.5) / target.scrollHeight;
- 794 :
}
- 795 :
this.updateLocationMarker(amount);
- 796 :
},
- 797 :
- 798 :
updateLocationMarker: function(amount, scrollDir) {
- 799 :
var locationInfo = this.getLocationInfo();
- 800 :
if (locationInfo) {
- 801 :
var info1 = locationInfo[0];
- 802 :
var info2 = locationInfo[1];
- 803 :
- 804 :
var corpus = this.getCorpus();
- 805 :
var partialFirstDoc = false;
- 806 :
- 807 :
if (info1.position !== 0) {
- 808 :
partialFirstDoc = true;
- 809 :
}
- 810 :
- 811 :
var docTokens = {};
- 812 :
var totalTokens = 0;
- 813 :
var showNerButton = this.getEnableEntitiesList() && this.getApplication().getEntitiesEnabled ? this.getApplication().getEntitiesEnabled() : false;
- 814 :
var currIndex = info1.docIndex;
- 815 :
while (currIndex <= info2.docIndex) {
- 816 :
var tokens = corpus.getDocument(currIndex).get('tokensCount-lexical');
- 817 :
if (tokens > this.MAX_TOKENS_FOR_NER) {
- 818 :
showNerButton = false;
- 819 :
}
- 820 :
if (currIndex === info2.docIndex) {
- 821 :
tokens = info2.position; // only count tokens up until last displayed word
- 822 :
}
- 823 :
if (currIndex === info1.docIndex) {
- 824 :
tokens -= info1.position; // subtract missing tokens, if any
- 825 :
}
- 826 :
totalTokens += tokens;
- 827 :
docTokens[currIndex] = tokens;
- 828 :
currIndex++;
- 829 :
}
- 830 :
- 831 :
var nerParent = this.down('#nerServiceParent');
- 832 :
if (showNerButton) {
- 833 :
nerParent.show();
- 834 :
} else {
- 835 :
nerParent.hide();
- 836 :
}
- 837 :
- 838 :
var tokenPos = Math.round(totalTokens * amount);
- 839 :
var docIndex = 0;
- 840 :
var currToken = 0;
- 841 :
for (var i = info1.docIndex; i <= info2.docIndex; i++) {
- 842 :
docIndex = i;
- 843 :
currToken += docTokens[i];
- 844 :
if (currToken >= tokenPos) {
- 845 :
break;
- 846 :
}
- 847 :
}
- 848 :
var remains = (currToken - tokenPos);
- 849 :
var tokenPosInDoc = docTokens[docIndex] - remains;
- 850 :
- 851 :
if (partialFirstDoc && docIndex === info1.docIndex) {
- 852 :
tokenPosInDoc += info1.position;
- 853 :
}
- 854 :
- 855 :
var fraction = tokenPosInDoc / corpus.getDocument(docIndex).get('tokensCount-lexical');
- 856 :
- 857 :
this.down('readergraph').moveLocationMarker(docIndex, fraction, scrollDir);
- 858 :
}
- 859 :
},
- 860 :
- 861 :
getLocationInfo: function() {
- 862 :
var readerWords = Ext.DomQuery.select('.word', this.getInnerContainer().down('.readerContainer', true));
- 863 :
var firstWord = readerWords[0];
- 864 :
var lastWord = readerWords[readerWords.length-1];
- 865 :
if (firstWord !== undefined && lastWord !== undefined) {
- 866 :
var info1 = Voyant.data.model.Token.getInfoFromElement(firstWord);
- 867 :
var info2 = Voyant.data.model.Token.getInfoFromElement(lastWord);
- 868 :
return [info1, info2];
- 869 :
} else {
- 870 :
return null;
- 871 :
}
- 872 :
}
- 873 :
});