You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
5.0 KiB

  1. import Vue from 'vue';
  2. import annotationMixin from './mixin';
  3. import HTTP from './http';
  4. Vue.use(require('vue-shortkey'), {
  5. prevent: ['input', 'textarea'],
  6. });
  7. Vue.component('annotator', {
  8. template: '<div @click="setSelectedRange">\
  9. <span class="text-sequence"\
  10. v-for="r in chunks"\
  11. v-if="id2label[r.label]"\
  12. v-bind:class="{tag: id2label[r.label].text_color}"\
  13. v-bind:style="{ color: id2label[r.label].text_color, backgroundColor: id2label[r.label].background_color }"\
  14. >{{ text.slice(r.start_offset, r.end_offset) }}<button class="delete is-small"\
  15. v-if="id2label[r.label].text_color"\
  16. @click="removeLabel(r)"></button></span>\
  17. </div>',
  18. props: {
  19. labels: Array, // [{id: Integer, color: String, text: String}]
  20. text: String,
  21. entityPositions: Array, // [{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]
  22. },
  23. data() {
  24. return {
  25. startOffset: 0,
  26. endOffset: 0,
  27. };
  28. },
  29. methods: {
  30. setSelectedRange(e) {
  31. let start;
  32. let end;
  33. if (window.getSelection) {
  34. const range = window.getSelection().getRangeAt(0);
  35. const preSelectionRange = range.cloneRange();
  36. preSelectionRange.selectNodeContents(this.$el);
  37. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  38. start = preSelectionRange.toString().length;
  39. end = start + range.toString().length;
  40. } else if (document.selection && document.selection.type !== 'Control') {
  41. const selectedTextRange = document.selection.createRange();
  42. const preSelectionTextRange = document.body.createTextRange();
  43. preSelectionTextRange.moveToElementText(this.$el);
  44. preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);
  45. start = preSelectionTextRange.text.length;
  46. end = start + selectedTextRange.text.length;
  47. }
  48. this.startOffset = start;
  49. this.endOffset = end;
  50. console.log(start, end);
  51. },
  52. validRange() {
  53. if (this.startOffset === this.endOffset) {
  54. return false;
  55. }
  56. if (this.startOffset > this.text.length || this.endOffset > this.text.length) {
  57. return false;
  58. }
  59. if (this.startOffset < 0 || this.endOffset < 0) {
  60. return false;
  61. }
  62. for (let i = 0; i < this.entityPositions.length; i++) {
  63. const e = this.entityPositions[i];
  64. if ((e.start_offset <= this.startOffset) && (this.startOffset < e.end_offset)) {
  65. return false;
  66. }
  67. if ((e.start_offset < this.endOffset) && (this.endOffset < e.end_offset)) {
  68. return false;
  69. }
  70. if ((this.startOffset < e.start_offset) && (e.start_offset < this.endOffset)) {
  71. return false;
  72. }
  73. if ((this.startOffset < e.end_offset) && (e.end_offset < this.endOffset)) {
  74. return false;
  75. }
  76. }
  77. return true;
  78. },
  79. resetRange() {
  80. this.startOffset = 0;
  81. this.endOffset = 0;
  82. },
  83. addLabel(labelId) {
  84. if (this.validRange()) {
  85. const label = {
  86. start_offset: this.startOffset,
  87. end_offset: this.endOffset,
  88. label: labelId,
  89. };
  90. this.$emit('add-label', label);
  91. }
  92. },
  93. removeLabel(index) {
  94. this.$emit('remove-label', index);
  95. },
  96. makeLabel(startOffset, endOffset) {
  97. const label = {
  98. id: 0,
  99. label: -1,
  100. start_offset: startOffset,
  101. end_offset: endOffset,
  102. };
  103. return label;
  104. },
  105. },
  106. watch: {
  107. entityPositions() {
  108. this.resetRange();
  109. },
  110. },
  111. computed: {
  112. sortedEntityPositions() {
  113. this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);
  114. return this.entityPositions;
  115. },
  116. chunks() {
  117. const res = [];
  118. let left = 0;
  119. for (let i = 0; i < this.sortedEntityPositions.length; i++) {
  120. const e = this.sortedEntityPositions[i];
  121. const l = this.makeLabel(left, e.start_offset);
  122. res.push(l);
  123. res.push(e);
  124. left = e.end_offset;
  125. }
  126. const l = this.makeLabel(left, this.text.length);
  127. res.push(l);
  128. return res;
  129. },
  130. id2label() {
  131. let id2label = {};
  132. // default value;
  133. id2label[-1] = {
  134. text_color: '',
  135. background_color: '',
  136. };
  137. for (let i = 0; i < this.labels.length; i++) {
  138. const label = this.labels[i];
  139. id2label[label.id] = label;
  140. }
  141. return id2label;
  142. },
  143. },
  144. });
  145. const vm = new Vue({
  146. el: '#mail-app',
  147. delimiters: ['[[', ']]'],
  148. mixins: [annotationMixin],
  149. methods: {
  150. annotate(labelId) {
  151. this.$refs.annotator.addLabel(labelId);
  152. },
  153. addLabel(annotation) {
  154. const docId = this.docs[this.pageNumber].id;
  155. HTTP.post(`docs/${docId}/annotations/`, annotation).then((response) => {
  156. this.annotations[this.pageNumber].push(response.data);
  157. });
  158. },
  159. },
  160. });