op.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. var equal = require('deep-equal');
  2. var extend = require('extend');
  3. var lib = {
  4. attributes: {
  5. compose: function (a, b, keepNull) {
  6. if (typeof a !== 'object') a = {};
  7. if (typeof b !== 'object') b = {};
  8. var attributes = extend(true, {}, b);
  9. if (!keepNull) {
  10. attributes = Object.keys(attributes).reduce(function (copy, key) {
  11. if (attributes[key] != null) {
  12. copy[key] = attributes[key];
  13. }
  14. return copy;
  15. }, {});
  16. }
  17. for (var key in a) {
  18. if (a[key] !== undefined && b[key] === undefined) {
  19. attributes[key] = a[key];
  20. }
  21. }
  22. return Object.keys(attributes).length > 0 ? attributes : undefined;
  23. },
  24. diff: function(a, b) {
  25. if (typeof a !== 'object') a = {};
  26. if (typeof b !== 'object') b = {};
  27. var attributes = Object.keys(a).concat(Object.keys(b)).reduce(function (attributes, key) {
  28. if (!equal(a[key], b[key])) {
  29. attributes[key] = b[key] === undefined ? null : b[key];
  30. }
  31. return attributes;
  32. }, {});
  33. return Object.keys(attributes).length > 0 ? attributes : undefined;
  34. },
  35. transform: function (a, b, priority) {
  36. if (typeof a !== 'object') return b;
  37. if (typeof b !== 'object') return undefined;
  38. if (!priority) return b; // b simply overwrites us without priority
  39. var attributes = Object.keys(b).reduce(function (attributes, key) {
  40. if (a[key] === undefined) attributes[key] = b[key]; // null is a valid value
  41. return attributes;
  42. }, {});
  43. return Object.keys(attributes).length > 0 ? attributes : undefined;
  44. }
  45. },
  46. iterator: function (ops) {
  47. return new Iterator(ops);
  48. },
  49. length: function (op) {
  50. if (typeof op['delete'] === 'number') {
  51. return op['delete'];
  52. } else if (typeof op.retain === 'number') {
  53. return op.retain;
  54. } else {
  55. return typeof op.insert === 'string' ? op.insert.length : 1;
  56. }
  57. }
  58. };
  59. function Iterator(ops) {
  60. this.ops = ops;
  61. this.index = 0;
  62. this.offset = 0;
  63. };
  64. Iterator.prototype.hasNext = function () {
  65. return this.peekLength() < Infinity;
  66. };
  67. Iterator.prototype.next = function (length) {
  68. if (!length) length = Infinity;
  69. var nextOp = this.ops[this.index];
  70. if (nextOp) {
  71. var offset = this.offset;
  72. var opLength = lib.length(nextOp)
  73. if (length >= opLength - offset) {
  74. length = opLength - offset;
  75. this.index += 1;
  76. this.offset = 0;
  77. } else {
  78. this.offset += length;
  79. }
  80. if (typeof nextOp['delete'] === 'number') {
  81. return { 'delete': length };
  82. } else {
  83. var retOp = {};
  84. if (nextOp.attributes) {
  85. retOp.attributes = nextOp.attributes;
  86. }
  87. if (typeof nextOp.retain === 'number') {
  88. retOp.retain = length;
  89. } else if (typeof nextOp.insert === 'string') {
  90. retOp.insert = nextOp.insert.substr(offset, length);
  91. } else {
  92. // offset should === 0, length should === 1
  93. retOp.insert = nextOp.insert;
  94. }
  95. return retOp;
  96. }
  97. } else {
  98. return { retain: Infinity };
  99. }
  100. };
  101. Iterator.prototype.peek = function () {
  102. return this.ops[this.index];
  103. };
  104. Iterator.prototype.peekLength = function () {
  105. if (this.ops[this.index]) {
  106. // Should never return 0 if our index is being managed correctly
  107. return lib.length(this.ops[this.index]) - this.offset;
  108. } else {
  109. return Infinity;
  110. }
  111. };
  112. Iterator.prototype.peekType = function () {
  113. if (this.ops[this.index]) {
  114. if (typeof this.ops[this.index]['delete'] === 'number') {
  115. return 'delete';
  116. } else if (typeof this.ops[this.index].retain === 'number') {
  117. return 'retain';
  118. } else {
  119. return 'insert';
  120. }
  121. }
  122. return 'retain';
  123. };
  124. Iterator.prototype.rest = function () {
  125. if (!this.hasNext()) {
  126. return [];
  127. } else if (this.offset === 0) {
  128. return this.ops.slice(this.index);
  129. } else {
  130. var offset = this.offset;
  131. var index = this.index;
  132. var next = this.next();
  133. var rest = this.ops.slice(this.index);
  134. this.offset = offset;
  135. this.index = index;
  136. return [next].concat(rest);
  137. }
  138. };
  139. module.exports = lib;