insert.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. import 'package:tuple/tuple.dart';
  2. import '../quill_delta.dart';
  3. import '../document/style.dart';
  4. import '../document/attribute.dart';
  5. import 'rule.dart';
  6. abstract class InsertRule extends Rule {
  7. const InsertRule();
  8. @override
  9. RuleType get type => RuleType.INSERT;
  10. @override
  11. void validateArgs(int? length, Object? data, Attribute? attribute) {
  12. assert(data != null);
  13. assert(attribute == null);
  14. }
  15. }
  16. /* -------------------------------- Rule Impl ------------------------------- */
  17. class PreserveLineStyleOnSplitRule extends InsertRule {
  18. const PreserveLineStyleOnSplitRule();
  19. @override
  20. Delta? applyRule(Delta document, int index,
  21. {int? length, Object? data, Attribute? attribute}) {
  22. if (data is! String || data != '\n') {
  23. return null;
  24. }
  25. final it = DeltaIterator(document);
  26. final before = it.skip(index);
  27. if (before == null ||
  28. before.data is! String ||
  29. (before.data as String).endsWith('\n')) {
  30. return null;
  31. }
  32. final after = it.next();
  33. if (after.data is! String || (after.data as String).startsWith('\n')) {
  34. return null;
  35. }
  36. final text = after.data as String;
  37. final delta = Delta()..retain(index + (length ?? 0));
  38. if (text.contains('\n')) {
  39. assert(after.isPlain);
  40. delta.insert('\n');
  41. return delta;
  42. }
  43. final nextNewLine = _getNextNewLine(it);
  44. final attributes = nextNewLine.item1?.attributes;
  45. return delta..insert('\n', attributes);
  46. }
  47. }
  48. class PreserveBlockStyleOnInsertRule extends InsertRule {
  49. const PreserveBlockStyleOnInsertRule();
  50. @override
  51. Delta? applyRule(Delta document, int index,
  52. {int? length, Object? data, Attribute? attribute}) {
  53. if (data is! String || !data.contains('\n')) {
  54. return null;
  55. }
  56. final it = DeltaIterator(document)..skip(index);
  57. final nextNewLine = _getNextNewLine(it);
  58. final lineStyle = Style.fromJson(
  59. nextNewLine.item1?.attributes ?? <String, dynamic>{},
  60. );
  61. final attribute = lineStyle.getBlockExceptHeader();
  62. if (attribute == null) {
  63. return null;
  64. }
  65. final blockStyle = <String, dynamic>{attribute.key: attribute.value};
  66. Map<String, dynamic>? resetStyle;
  67. if (lineStyle.containsKey(Attribute.header.key)) {
  68. resetStyle = Attribute.header.toJson();
  69. }
  70. final lines = data.split('\n');
  71. final delta = Delta()..retain(index + (length ?? 0));
  72. for (var i = 0; i < lines.length; i++) {
  73. final line = lines[i];
  74. if (line.isNotEmpty) {
  75. delta.insert(line);
  76. }
  77. if (i == 0) {
  78. delta.insert('\n', lineStyle.toJson());
  79. } else if (i < lines.length - 1) {
  80. delta.insert('\n', blockStyle);
  81. }
  82. }
  83. if (resetStyle != null) {
  84. delta
  85. ..retain(nextNewLine.item2!)
  86. ..retain((nextNewLine.item1!.data as String).indexOf('\n'))
  87. ..retain(1, resetStyle);
  88. }
  89. return delta;
  90. }
  91. }
  92. class AutoExitBlockRule extends InsertRule {
  93. const AutoExitBlockRule();
  94. bool _isEmptyLine(Operation? before, Operation? after) {
  95. if (before == null) {
  96. return true;
  97. }
  98. return before.data is String &&
  99. (before.data as String).endsWith('\n') &&
  100. after!.data is String &&
  101. (after.data as String).startsWith('\n');
  102. }
  103. @override
  104. Delta? applyRule(Delta document, int index,
  105. {int? length, Object? data, Attribute? attribute}) {
  106. if (data is! String || data != '\n') {
  107. return null;
  108. }
  109. final it = DeltaIterator(document);
  110. final prev = it.skip(index), cur = it.next();
  111. final blockStyle = Style.fromJson(cur.attributes).getBlockExceptHeader();
  112. if (cur.isPlain || blockStyle == null) {
  113. return null;
  114. }
  115. if (!_isEmptyLine(prev, cur)) {
  116. return null;
  117. }
  118. if ((cur.value as String).length > 1) {
  119. return null;
  120. }
  121. final nextNewLine = _getNextNewLine(it);
  122. if (nextNewLine.item1 != null &&
  123. nextNewLine.item1!.attributes != null &&
  124. Style.fromJson(nextNewLine.item1!.attributes).getBlockExceptHeader() ==
  125. blockStyle) {
  126. return null;
  127. }
  128. final attributes = cur.attributes ?? <String, dynamic>{};
  129. final k = attributes.keys
  130. .firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
  131. attributes[k] = null;
  132. // retain(1) should be '\n', set it with no attribute
  133. return Delta()..retain(index + (length ?? 0))..retain(1, attributes);
  134. }
  135. }
  136. class ResetLineFormatOnNewLineRule extends InsertRule {
  137. const ResetLineFormatOnNewLineRule();
  138. @override
  139. Delta? applyRule(Delta document, int index,
  140. {int? length, Object? data, Attribute? attribute}) {
  141. if (data is! String || data != '\n') {
  142. return null;
  143. }
  144. final itr = DeltaIterator(document)..skip(index);
  145. final cur = itr.next();
  146. if (cur.data is! String || !(cur.data as String).startsWith('\n')) {
  147. return null;
  148. }
  149. Map<String, dynamic>? resetStyle;
  150. if (cur.attributes != null &&
  151. cur.attributes!.containsKey(Attribute.header.key)) {
  152. resetStyle = Attribute.header.toJson();
  153. }
  154. return Delta()
  155. ..retain(index + (length ?? 0))
  156. ..insert('\n', cur.attributes)
  157. ..retain(1, resetStyle)
  158. ..trim();
  159. }
  160. }
  161. class InsertEmbedsRule extends InsertRule {
  162. const InsertEmbedsRule();
  163. @override
  164. Delta? applyRule(Delta document, int index,
  165. {int? length, Object? data, Attribute? attribute}) {
  166. if (data is String) {
  167. return null;
  168. }
  169. final delta = Delta()..retain(index + (length ?? 0));
  170. final it = DeltaIterator(document);
  171. final prev = it.skip(index), cur = it.next();
  172. final textBefore = prev?.data is String ? prev!.data as String? : '';
  173. final textAfter = cur.data is String ? (cur.data as String?)! : '';
  174. final isNewlineBefore = prev == null || textBefore!.endsWith('\n');
  175. final isNewlineAfter = textAfter.startsWith('\n');
  176. if (isNewlineBefore && isNewlineAfter) {
  177. return delta..insert(data);
  178. }
  179. Map<String, dynamic>? lineStyle;
  180. if (textAfter.contains('\n')) {
  181. lineStyle = cur.attributes;
  182. } else {
  183. while (it.hasNext) {
  184. final op = it.next();
  185. if ((op.data is String ? op.data as String? : '')!.contains('\n')) {
  186. lineStyle = op.attributes;
  187. break;
  188. }
  189. }
  190. }
  191. if (!isNewlineBefore) {
  192. delta.insert('\n', lineStyle);
  193. }
  194. delta.insert(data);
  195. if (!isNewlineAfter) {
  196. delta.insert('\n');
  197. }
  198. return delta;
  199. }
  200. }
  201. class ForceNewlineForInsertsAroundEmbedRule extends InsertRule {
  202. const ForceNewlineForInsertsAroundEmbedRule();
  203. @override
  204. Delta? applyRule(Delta document, int index,
  205. {int? length, Object? data, Attribute? attribute}) {
  206. if (data is! String) {
  207. return null;
  208. }
  209. final text = data;
  210. final it = DeltaIterator(document);
  211. final prev = it.skip(index), cur = it.next();
  212. final cursorBeforeEmbed = cur.data is! String;
  213. final cursorAfterEmbed = prev != null && prev.data is! String;
  214. if (!cursorBeforeEmbed && !cursorAfterEmbed) {
  215. return null;
  216. }
  217. final delta = Delta()..retain(index + (length ?? 0));
  218. if (cursorBeforeEmbed && !text.endsWith('\n')) {
  219. return delta..insert(text)..insert('\n');
  220. }
  221. if (cursorAfterEmbed && !text.startsWith('\n')) {
  222. return delta..insert('\n')..insert(text);
  223. }
  224. return delta..insert(text);
  225. }
  226. }
  227. class AutoFormatLinksRule extends InsertRule {
  228. const AutoFormatLinksRule();
  229. @override
  230. Delta? applyRule(Delta document, int index,
  231. {int? length, Object? data, Attribute? attribute}) {
  232. if (data is! String || data != ' ') {
  233. return null;
  234. }
  235. final it = DeltaIterator(document);
  236. final prev = it.skip(index);
  237. if (prev == null || prev.data is! String) {
  238. return null;
  239. }
  240. try {
  241. final cand = (prev.data as String).split('\n').last.split(' ').last;
  242. final link = Uri.parse(cand);
  243. if (!['https', 'http'].contains(link.scheme)) {
  244. return null;
  245. }
  246. final attributes = prev.attributes ?? <String, dynamic>{};
  247. if (attributes.containsKey(Attribute.link.key)) {
  248. return null;
  249. }
  250. attributes.addAll(LinkAttribute(link.toString()).toJson());
  251. return Delta()
  252. ..retain(index + (length ?? 0) - cand.length)
  253. ..retain(cand.length, attributes)
  254. ..insert(data, prev.attributes);
  255. } on FormatException {
  256. return null;
  257. }
  258. }
  259. }
  260. class PreserveInlineStylesRule extends InsertRule {
  261. const PreserveInlineStylesRule();
  262. @override
  263. Delta? applyRule(Delta document, int index,
  264. {int? length, Object? data, Attribute? attribute}) {
  265. if (data is! String || data.contains('\n')) {
  266. return null;
  267. }
  268. final it = DeltaIterator(document);
  269. final prev = it.skip(index);
  270. if (prev == null ||
  271. prev.data is! String ||
  272. (prev.data as String).contains('\n')) {
  273. return null;
  274. }
  275. final attributes = prev.attributes;
  276. final text = data;
  277. if (attributes == null || !attributes.containsKey(Attribute.link.key)) {
  278. return Delta()
  279. ..retain(index + (length ?? 0))
  280. ..insert(text, attributes);
  281. }
  282. attributes.remove(Attribute.link.key);
  283. final delta = Delta()
  284. ..retain(index + (length ?? 0))
  285. ..insert(text, attributes.isEmpty ? null : attributes);
  286. final next = it.next();
  287. final nextAttributes = next.attributes ?? const <String, dynamic>{};
  288. if (!nextAttributes.containsKey(Attribute.link.key)) {
  289. return delta;
  290. }
  291. if (attributes[Attribute.link.key] == nextAttributes[Attribute.link.key]) {
  292. return Delta()
  293. ..retain(index + (length ?? 0))
  294. ..insert(text, attributes);
  295. }
  296. return delta;
  297. }
  298. }
  299. class CatchAllInsertRule extends InsertRule {
  300. const CatchAllInsertRule();
  301. @override
  302. Delta applyRule(Delta document, int index,
  303. {int? length, Object? data, Attribute? attribute}) {
  304. return Delta()
  305. ..retain(index + (length ?? 0))
  306. ..insert(data);
  307. }
  308. }
  309. /* --------------------------------- Helper --------------------------------- */
  310. Tuple2<Operation?, int?> _getNextNewLine(DeltaIterator iterator) {
  311. Operation op;
  312. for (var skipped = 0; iterator.hasNext; skipped += op.length!) {
  313. op = iterator.next();
  314. final lineBreak =
  315. (op.data is String ? op.data as String? : '')!.indexOf('\n');
  316. if (lineBreak >= 0) {
  317. return Tuple2(op, skipped);
  318. }
  319. }
  320. return const Tuple2(null, null);
  321. }