actiontext 7.1.3.2 → 7.1.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce8b2b2628c11a9dd67e83a7ba948df06d10e616756d6ce8e509c96b356e060c
4
- data.tar.gz: 1ac97fa71f50ac3e568f81a5c57f05a3d81387a47233104e7a16ea823830a296
3
+ metadata.gz: 2c64b902311d318e3166ecc33e11d36f3b489b173941df779cac70e5523037e9
4
+ data.tar.gz: 262303fcd96805d2739cff5ace1eddeb35daa5d2205677cf18ab858abbd3b9b2
5
5
  SHA512:
6
- metadata.gz: 919197c2541430986c06da4c8d5fd069d6007092e73ed051af06452ceda46a4e807d6a877fb4e4338d2c9fc42fada5881e5a45317e8dba69a381e2e538718269
7
- data.tar.gz: 2012b4fc3f3fd5f725e1ffecdb050be22555ac3a67136bc0a10dabe5f41559784d69983085041a6fb2a71cbdd82bfd45d5e29b64cd821df55eb3a676d31c17ac
6
+ metadata.gz: '0192e5197f027ad24a0acb6a79deef6f380ad816de72058d5cac1804e8bd25a7bdeaf898479fd1080ddcc15cbe4e1fd35da2af292ba681a2683f9bc60aa983e7'
7
+ data.tar.gz: 2045ea65d4bfc311886aa9b53f0cae50a1c8921d64cf8437ccf7e99ca53a1decb10e50b210507b744417af1aa29b184ed7b7103e046f319af57e7114db49a92c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## Rails 7.1.3.3 (May 16, 2024) ##
2
+
3
+ * Upgrade Trix to 1.3.2 to fix [CVE-2024-34341](https://github.com/basecamp/trix/security/advisories/GHSA-qjqp-xr96-cj99).
4
+
5
+ *Rafael Mendonça França*
6
+
7
+
1
8
  ## Rails 7.1.3.2 (February 21, 2024) ##
2
9
 
3
10
  * No changes.
@@ -1,6 +1,6 @@
1
1
  /*
2
- Trix 2.0.7
3
- Copyright © 2023 37signals, LLC
2
+ Trix 2.1.1
3
+ Copyright © 2024 37signals, LLC
4
4
  */
5
5
  (function (global, factory) {
6
6
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -9,7 +9,7 @@ Copyright © 2023 37signals, LLC
9
9
  })(this, (function () { 'use strict';
10
10
 
11
11
  var name = "trix";
12
- var version = "2.0.7";
12
+ var version = "2.1.1";
13
13
  var description = "A rich text editor for everyday writing";
14
14
  var main = "dist/trix.umd.min.js";
15
15
  var module = "dist/trix.esm.min.js";
@@ -17,6 +17,7 @@ Copyright © 2023 37signals, LLC
17
17
  var files = [
18
18
  "dist/*.css",
19
19
  "dist/*.js",
20
+ "dist/*.map",
20
21
  "src/{inspector,trix}/*.js"
21
22
  ];
22
23
  var repository = {
@@ -130,6 +131,7 @@ Copyright © 2023 37signals, LLC
130
131
  code: {
131
132
  tagName: "pre",
132
133
  terminal: true,
134
+ htmlAttributes: ["language"],
133
135
  text: {
134
136
  plaintext: true
135
137
  }
@@ -1215,7 +1217,7 @@ $\
1215
1217
  no-useless-escape,
1216
1218
  */
1217
1219
  const normalizeSpaces = string => string.replace(new RegExp("".concat(ZERO_WIDTH_SPACE), "g"), "").replace(new RegExp("".concat(NON_BREAKING_SPACE), "g"), " ");
1218
- const normalizeNewlines = string => string.replace(/\r\n/g, "\n");
1220
+ const normalizeNewlines = string => string.replace(/\r\n?/g, "\n");
1219
1221
  const breakableWhitespacePattern = new RegExp("[^\\S".concat(NON_BREAKING_SPACE, "]"));
1220
1222
  const squishBreakableWhitespace = string => string
1221
1223
  // Replace all breakable whitespace characters with a space
@@ -2144,20 +2146,28 @@ $\
2144
2146
  }
2145
2147
  }
2146
2148
  createContainerElement(depth) {
2147
- let attributes, className;
2149
+ const attributes = {};
2150
+ let className;
2148
2151
  const attributeName = this.attributes[depth];
2149
2152
  const {
2150
- tagName
2153
+ tagName,
2154
+ htmlAttributes = []
2151
2155
  } = getBlockConfig(attributeName);
2152
2156
  if (depth === 0 && this.block.isRTL()) {
2153
- attributes = {
2157
+ Object.assign(attributes, {
2154
2158
  dir: "rtl"
2155
- };
2159
+ });
2156
2160
  }
2157
2161
  if (attributeName === "attachmentGallery") {
2158
2162
  const size = this.block.getBlockBreakPosition();
2159
2163
  className = "".concat(css$1.attachmentGallery, " ").concat(css$1.attachmentGallery, "--").concat(size);
2160
2164
  }
2165
+ Object.entries(this.block.htmlAttributes).forEach(_ref => {
2166
+ let [name, value] = _ref;
2167
+ if (htmlAttributes.includes(name)) {
2168
+ attributes[name] = value;
2169
+ }
2170
+ });
2161
2171
  return makeElement({
2162
2172
  tagName,
2163
2173
  className,
@@ -5828,28 +5838,29 @@ $\
5828
5838
  class Block extends TrixObject {
5829
5839
  static fromJSON(blockJSON) {
5830
5840
  const text = Text.fromJSON(blockJSON.text);
5831
- return new this(text, blockJSON.attributes);
5841
+ return new this(text, blockJSON.attributes, blockJSON.htmlAttributes);
5832
5842
  }
5833
- constructor(text, attributes) {
5843
+ constructor(text, attributes, htmlAttributes) {
5834
5844
  super(...arguments);
5835
5845
  this.text = applyBlockBreakToText(text || new Text());
5836
5846
  this.attributes = attributes || [];
5847
+ this.htmlAttributes = htmlAttributes || {};
5837
5848
  }
5838
5849
  isEmpty() {
5839
5850
  return this.text.isBlockBreak();
5840
5851
  }
5841
5852
  isEqualTo(block) {
5842
5853
  if (super.isEqualTo(block)) return true;
5843
- return this.text.isEqualTo(block === null || block === void 0 ? void 0 : block.text) && arraysAreEqual(this.attributes, block === null || block === void 0 ? void 0 : block.attributes);
5854
+ return this.text.isEqualTo(block === null || block === void 0 ? void 0 : block.text) && arraysAreEqual(this.attributes, block === null || block === void 0 ? void 0 : block.attributes) && objectsAreEqual(this.htmlAttributes, block === null || block === void 0 ? void 0 : block.htmlAttributes);
5844
5855
  }
5845
5856
  copyWithText(text) {
5846
- return new Block(text, this.attributes);
5857
+ return new Block(text, this.attributes, this.htmlAttributes);
5847
5858
  }
5848
5859
  copyWithoutText() {
5849
5860
  return this.copyWithText(null);
5850
5861
  }
5851
5862
  copyWithAttributes(attributes) {
5852
- return new Block(this.text, attributes);
5863
+ return new Block(this.text, attributes, this.htmlAttributes);
5853
5864
  }
5854
5865
  copyWithoutAttributes() {
5855
5866
  return this.copyWithAttributes(null);
@@ -5866,6 +5877,12 @@ $\
5866
5877
  const attributes = this.attributes.concat(expandAttribute(attribute));
5867
5878
  return this.copyWithAttributes(attributes);
5868
5879
  }
5880
+ addHTMLAttribute(attribute, value) {
5881
+ const htmlAttributes = Object.assign({}, this.htmlAttributes, {
5882
+ [attribute]: value
5883
+ });
5884
+ return new Block(this.text, this.attributes, htmlAttributes);
5885
+ }
5869
5886
  removeAttribute(attribute) {
5870
5887
  const {
5871
5888
  listAttribute
@@ -5962,7 +5979,8 @@ $\
5962
5979
  toJSON() {
5963
5980
  return {
5964
5981
  text: this.text,
5965
- attributes: this.attributes
5982
+ attributes: this.attributes,
5983
+ htmlAttributes: this.htmlAttributes
5966
5984
  };
5967
5985
  }
5968
5986
 
@@ -6325,6 +6343,11 @@ $\
6325
6343
  const range = this.getRangeOfAttachment(attachment);
6326
6344
  return this.removeAttributeAtRange(attribute, range);
6327
6345
  }
6346
+ setHTMLAttributeAtPosition(position, name, value) {
6347
+ const block = this.getBlockAtPosition(position);
6348
+ const updatedBlock = block.addHTMLAttribute(name, value);
6349
+ return this.replaceBlock(block, updatedBlock);
6350
+ }
6328
6351
  insertBlockBreakAtRange(range) {
6329
6352
  let blocks;
6330
6353
  range = normalizeRange(range);
@@ -6793,9 +6816,9 @@ $\
6793
6816
  return attributes;
6794
6817
  };
6795
6818
 
6796
- const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height class".split(" ");
6819
+ const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height language class".split(" ");
6797
6820
  const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" ");
6798
- const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form".split(" ");
6821
+ const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" ");
6799
6822
  class HTMLSanitizer extends BasicObject {
6800
6823
  static sanitize(html, options) {
6801
6824
  const sanitizer = new this(html, options);
@@ -6923,15 +6946,21 @@ $\
6923
6946
  };
6924
6947
  const blockForAttributes = function () {
6925
6948
  let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6949
+ let htmlAttributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
6926
6950
  const text = [];
6927
6951
  return {
6928
6952
  text,
6929
- attributes
6953
+ attributes,
6954
+ htmlAttributes
6930
6955
  };
6931
6956
  };
6932
6957
  const parseTrixDataAttribute = (element, name) => {
6933
6958
  try {
6934
- return JSON.parse(element.getAttribute("data-trix-".concat(name)));
6959
+ const data = JSON.parse(element.getAttribute("data-trix-".concat(name)));
6960
+ if (data.contentType === "text/html" && data.content) {
6961
+ data.content = HTMLSanitizer.sanitize(data.content).getHTML();
6962
+ }
6963
+ return data;
6935
6964
  } catch (error) {
6936
6965
  return {};
6937
6966
  }
@@ -7027,8 +7056,9 @@ $\
7027
7056
  } else if (element === this.containerElement || this.isBlockElement(element)) {
7028
7057
  var _this$currentBlock;
7029
7058
  const attributes = this.getBlockAttributes(element);
7059
+ const htmlAttributes = this.getBlockHTMLAttributes(element);
7030
7060
  if (!arraysAreEqual(attributes, (_this$currentBlock = this.currentBlock) === null || _this$currentBlock === void 0 ? void 0 : _this$currentBlock.attributes)) {
7031
- this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element);
7061
+ this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes);
7032
7062
  this.currentBlockElement = element;
7033
7063
  }
7034
7064
  }
@@ -7039,9 +7069,10 @@ $\
7039
7069
  if (elementIsBlockElement && !this.isBlockElement(element.firstChild)) {
7040
7070
  if (!this.isInsignificantTextNode(element.firstChild) || !this.isBlockElement(element.firstElementChild)) {
7041
7071
  const attributes = this.getBlockAttributes(element);
7072
+ const htmlAttributes = this.getBlockHTMLAttributes(element);
7042
7073
  if (element.firstChild) {
7043
7074
  if (!(currentBlockContainsElement && arraysAreEqual(attributes, this.currentBlock.attributes))) {
7044
- this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element);
7075
+ this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes);
7045
7076
  this.currentBlockElement = element;
7046
7077
  } else {
7047
7078
  return this.appendStringWithAttributes("\n");
@@ -7129,8 +7160,9 @@ $\
7129
7160
  // Document construction
7130
7161
 
7131
7162
  appendBlockForAttributesWithElement(attributes, element) {
7163
+ let htmlAttributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
7132
7164
  this.blockElements.push(element);
7133
- const block = blockForAttributes(attributes);
7165
+ const block = blockForAttributes(attributes, htmlAttributes);
7134
7166
  this.blocks.push(block);
7135
7167
  return block;
7136
7168
  }
@@ -7235,6 +7267,17 @@ $\
7235
7267
  }
7236
7268
  return attributes$1.reverse();
7237
7269
  }
7270
+ getBlockHTMLAttributes(element) {
7271
+ const attributes$1 = {};
7272
+ const blockConfig = Object.values(attributes).find(settings => settings.tagName === tagName(element));
7273
+ const allowedAttributes = (blockConfig === null || blockConfig === void 0 ? void 0 : blockConfig.htmlAttributes) || [];
7274
+ allowedAttributes.forEach(attribute => {
7275
+ if (element.hasAttribute(attribute)) {
7276
+ attributes$1[attribute] = element.getAttribute(attribute);
7277
+ }
7278
+ });
7279
+ return attributes$1;
7280
+ }
7238
7281
  findBlockElementAncestors(element) {
7239
7282
  const ancestors = [];
7240
7283
  while (element && element !== this.containerElement) {
@@ -7830,6 +7873,15 @@ $\
7830
7873
  return this.notifyDelegateOfCurrentAttributesChange();
7831
7874
  }
7832
7875
  }
7876
+ setHTMLAtributeAtPosition(position, attributeName, value) {
7877
+ var _getBlockConfig;
7878
+ const block = this.document.getBlockAtPosition(position);
7879
+ const allowedHTMLAttributes = (_getBlockConfig = getBlockConfig(block.getLastAttribute())) === null || _getBlockConfig === void 0 ? void 0 : _getBlockConfig.htmlAttributes;
7880
+ if (block && allowedHTMLAttributes !== null && allowedHTMLAttributes !== void 0 && allowedHTMLAttributes.includes(attributeName)) {
7881
+ const newDocument = this.document.setHTMLAttributeAtPosition(position, attributeName, value);
7882
+ this.setDocument(newDocument);
7883
+ }
7884
+ }
7833
7885
  setTextAttribute(attributeName, value) {
7834
7886
  const selectedRange = this.getSelectedRange();
7835
7887
  if (!selectedRange) return;
@@ -7877,10 +7929,10 @@ $\
7877
7929
  return ((_this$getBlock = this.getBlock()) === null || _this$getBlock === void 0 ? void 0 : _this$getBlock.getNestingLevel()) > 0;
7878
7930
  }
7879
7931
  canIncreaseNestingLevel() {
7880
- var _getBlockConfig;
7932
+ var _getBlockConfig2;
7881
7933
  const block = this.getBlock();
7882
7934
  if (!block) return;
7883
- if ((_getBlockConfig = getBlockConfig(block.getLastNestableAttribute())) !== null && _getBlockConfig !== void 0 && _getBlockConfig.listAttribute) {
7935
+ if ((_getBlockConfig2 = getBlockConfig(block.getLastNestableAttribute())) !== null && _getBlockConfig2 !== void 0 && _getBlockConfig2.listAttribute) {
7884
7936
  const previousBlock = this.getPreviousBlock();
7885
7937
  if (previousBlock) {
7886
7938
  return arrayStartsWith(previousBlock.getListItemAttributes(), block.getListItemAttributes());
@@ -8521,6 +8573,11 @@ $\
8521
8573
  return this.composition.removeCurrentAttribute(name);
8522
8574
  }
8523
8575
 
8576
+ // HTML attributes
8577
+ setHTMLAtributeAtPosition(position, name, value) {
8578
+ this.composition.setHTMLAtributeAtPosition(position, name, value);
8579
+ }
8580
+
8524
8581
  // Nesting level
8525
8582
 
8526
8583
  canDecreaseNestingLevel() {
@@ -10941,8 +10998,12 @@ $\
10941
10998
  });
10942
10999
  },
10943
11000
  insertReplacementText() {
10944
- return this.insertString(this.event.dataTransfer.getData("text/plain"), {
10945
- updatePosition: false
11001
+ const replacement = this.event.dataTransfer.getData("text/plain");
11002
+ const domRange = this.event.getTargetRanges()[0];
11003
+ this.withTargetDOMRange(domRange, () => {
11004
+ this.insertString(replacement, {
11005
+ updatePosition: false
11006
+ });
10946
11007
  });
10947
11008
  },
10948
11009
  insertText() {
@@ -11064,7 +11125,7 @@ $\
11064
11125
  return this.toggleDialog(actionName);
11065
11126
  } else {
11066
11127
  var _this$delegate2;
11067
- return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 ? void 0 : _this$delegate2.toolbarDidInvokeAction(actionName);
11128
+ return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 ? void 0 : _this$delegate2.toolbarDidInvokeAction(actionName, element);
11068
11129
  }
11069
11130
  }
11070
11131
  didClickAttributeButton(event, element) {
@@ -11509,8 +11570,8 @@ $\
11509
11570
  });
11510
11571
  }
11511
11572
  }
11512
- toolbarDidInvokeAction(actionName) {
11513
- return this.invokeAction(actionName);
11573
+ toolbarDidInvokeAction(actionName, invokingElement) {
11574
+ return this.invokeAction(actionName, invokingElement);
11514
11575
  }
11515
11576
  toolbarDidToggleAttribute(attributeName) {
11516
11577
  this.recordFormattingUndoEntry(attributeName);
@@ -11579,10 +11640,11 @@ $\
11579
11640
  return !!((_this$actions$actionN = this.actions[actionName]) !== null && _this$actions$actionN !== void 0 && (_this$actions$actionN = _this$actions$actionN.test) !== null && _this$actions$actionN !== void 0 && _this$actions$actionN.call(this));
11580
11641
  }
11581
11642
  }
11582
- invokeAction(actionName) {
11643
+ invokeAction(actionName, invokingElement) {
11583
11644
  if (this.actionIsExternal(actionName)) {
11584
11645
  return this.notifyEditorElement("action-invoke", {
11585
- actionName
11646
+ actionName,
11647
+ invokingElement
11586
11648
  });
11587
11649
  } else {
11588
11650
  var _this$actions$actionN2;
@@ -334,7 +334,9 @@ trix-editor .attachment__metadata {
334
334
  white-space: nowrap; }
335
335
 
336
336
  .trix-content {
337
- line-height: 1.5; }
337
+ line-height: 1.5;
338
+ overflow-wrap: break-word;
339
+ word-break: break-word; }
338
340
  .trix-content * {
339
341
  box-sizing: border-box;
340
342
  margin: 0;
@@ -10,7 +10,7 @@ module ActionText
10
10
  MAJOR = 7
11
11
  MINOR = 1
12
12
  TINY = 3
13
- PRE = "2"
13
+ PRE = "3"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rails/actiontext",
3
- "version": "7.1.3-2",
3
+ "version": "7.1.3-3",
4
4
  "description": "Edit and display rich text in Rails applications",
5
5
  "module": "app/assets/javascripts/actiontext.esm.js",
6
6
  "main": "app/assets/javascripts/actiontext.js",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actiontext
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.3.2
4
+ version: 7.1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javan Makhmali
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-02-21 00:00:00.000000000 Z
13
+ date: 2024-05-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -18,56 +18,56 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 7.1.3.2
21
+ version: 7.1.3.3
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 7.1.3.2
28
+ version: 7.1.3.3
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: activerecord
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - '='
34
34
  - !ruby/object:Gem::Version
35
- version: 7.1.3.2
35
+ version: 7.1.3.3
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - '='
41
41
  - !ruby/object:Gem::Version
42
- version: 7.1.3.2
42
+ version: 7.1.3.3
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: activestorage
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - '='
48
48
  - !ruby/object:Gem::Version
49
- version: 7.1.3.2
49
+ version: 7.1.3.3
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - '='
55
55
  - !ruby/object:Gem::Version
56
- version: 7.1.3.2
56
+ version: 7.1.3.3
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: actionpack
59
59
  requirement: !ruby/object:Gem::Requirement
60
60
  requirements:
61
61
  - - '='
62
62
  - !ruby/object:Gem::Version
63
- version: 7.1.3.2
63
+ version: 7.1.3.3
64
64
  type: :runtime
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - '='
69
69
  - !ruby/object:Gem::Version
70
- version: 7.1.3.2
70
+ version: 7.1.3.3
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: nokogiri
73
73
  requirement: !ruby/object:Gem::Requirement
@@ -163,10 +163,10 @@ licenses:
163
163
  - MIT
164
164
  metadata:
165
165
  bug_tracker_uri: https://github.com/rails/rails/issues
166
- changelog_uri: https://github.com/rails/rails/blob/v7.1.3.2/actiontext/CHANGELOG.md
167
- documentation_uri: https://api.rubyonrails.org/v7.1.3.2/
166
+ changelog_uri: https://github.com/rails/rails/blob/v7.1.3.3/actiontext/CHANGELOG.md
167
+ documentation_uri: https://api.rubyonrails.org/v7.1.3.3/
168
168
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
169
- source_code_uri: https://github.com/rails/rails/tree/v7.1.3.2/actiontext
169
+ source_code_uri: https://github.com/rails/rails/tree/v7.1.3.3/actiontext
170
170
  rubygems_mfa_required: 'true'
171
171
  post_install_message:
172
172
  rdoc_options: []
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  - !ruby/object:Gem::Version
184
184
  version: '0'
185
185
  requirements: []
186
- rubygems_version: 3.5.3
186
+ rubygems_version: 3.5.10
187
187
  signing_key:
188
188
  specification_version: 4
189
189
  summary: Rich text framework.