@ckeditor/ckeditor5-find-and-replace 44.3.0 → 45.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+
6
+ import type { Translations } from '@ckeditor/ckeditor5-utils';
7
+ declare const translations: Translations;
8
+ export default translations;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ export default {"be":{"dictionary":{"Find and replace":"Знайсці і замяніць","Find in text…":"Знайсці ў тэксце…","Find":"Знайсці","Previous result":"Папярэдні вынік","Next result":"Наступны вынік","Replace":"Замяніць","Replace all":"Замяніць усё","Match case":"З улікам рэгістру","Whole words only":"Толькі слова цалкам","Replace with…":"Замяніць на…","Text to find must not be empty.":"Тэкст для пошуку не павінен быць пустым.","Tip: Find some text first in order to replace it.":"Падказка: спачатку знайдзіце тэкст, каб замяніць яго.","Advanced options":"Дадатковыя параметры","Find in the document":"Знайсці ў дакуменце"},getPluralForm(n){return (n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);}}}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+
6
+ ( e => {
7
+ const { [ 'be' ]: { dictionary, getPluralForm } } = {"be":{"dictionary":{"Find and replace":"Знайсці і замяніць","Find in text…":"Знайсці ў тэксце…","Find":"Знайсці","Previous result":"Папярэдні вынік","Next result":"Наступны вынік","Replace":"Замяніць","Replace all":"Замяніць усё","Match case":"З улікам рэгістру","Whole words only":"Толькі слова цалкам","Replace with…":"Замяніць на…","Text to find must not be empty.":"Тэкст для пошуку не павінен быць пустым.","Tip: Find some text first in order to replace it.":"Падказка: спачатку знайдзіце тэкст, каб замяніць яго.","Advanced options":"Дадатковыя параметры","Find in the document":"Знайсці ў дакуменце"},getPluralForm(n){return (n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);}}};
8
+ e[ 'be' ] ||= { dictionary: {}, getPluralForm: null };
9
+ e[ 'be' ].dictionary = Object.assign( e[ 'be' ].dictionary, dictionary );
10
+ e[ 'be' ].getPluralForm = getPluralForm;
11
+ } )( window.CKEDITOR_TRANSLATIONS ||= {} );
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
2
+ #
3
+ # Want to contribute to this file? Submit your changes via a GitHub Pull Request.
4
+ #
5
+ # Check out the official contributor's guide:
6
+ # https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
7
+ #
8
+ msgid ""
9
+ msgstr ""
10
+ "Language: be\n"
11
+ "Plural-Forms: nplurals=3; plural=(n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);\n"
12
+ "Content-Type: text/plain; charset=UTF-8\n"
13
+
14
+ msgctxt "The tooltip of a find and replace button in the toolbar. Also, the title of the find and replace form."
15
+ msgid "Find and replace"
16
+ msgstr "Знайсці і замяніць"
17
+
18
+ msgctxt "The label for the searched text in the find and replace dropdown."
19
+ msgid "Find in text…"
20
+ msgstr "Знайсці ў тэксце…"
21
+
22
+ msgctxt "The label for the find action button in the find and replace dropdown."
23
+ msgid "Find"
24
+ msgstr "Знайсці"
25
+
26
+ msgctxt "The label for the previous result button in the find and replace dropdown."
27
+ msgid "Previous result"
28
+ msgstr "Папярэдні вынік"
29
+
30
+ msgctxt "The label for the previous result button in the find and replace dropdown."
31
+ msgid "Next result"
32
+ msgstr "Наступны вынік"
33
+
34
+ msgctxt "The label for the (single) replace action button in the find and replace dropdown."
35
+ msgid "Replace"
36
+ msgstr "Замяніць"
37
+
38
+ msgctxt "The label for the replace all action button in the find and replace dropdown."
39
+ msgid "Replace all"
40
+ msgstr "Замяніць усё"
41
+
42
+ msgctxt "The label for the match case checkbox in the find and replace dropdown."
43
+ msgid "Match case"
44
+ msgstr "З улікам рэгістру"
45
+
46
+ msgctxt "The label for the whole words only checkbox in the find and replace dropdown."
47
+ msgid "Whole words only"
48
+ msgstr "Толькі слова цалкам"
49
+
50
+ msgctxt "The label for the text replacement in the find and replace dropdown."
51
+ msgid "Replace with…"
52
+ msgstr "Замяніць на…"
53
+
54
+ msgctxt "An error text displayed when user attempted to find an empty text."
55
+ msgid "Text to find must not be empty."
56
+ msgstr "Тэкст для пошуку не павінен быць пустым."
57
+
58
+ msgctxt "A message displayed next to the replace field when disabled but user tries to use it."
59
+ msgid "Tip: Find some text first in order to replace it."
60
+ msgstr "Падказка: спачатку знайдзіце тэкст, каб замяніць яго."
61
+
62
+ msgctxt "The label and the tooltip of the options dropdown button in the find and replace form."
63
+ msgid "Advanced options"
64
+ msgstr "Дадатковыя параметры"
65
+
66
+ msgctxt "Keystroke description for assistive technologies: keystroke for opening the find and replace UI."
67
+ msgid "Find in the document"
68
+ msgstr "Знайсці ў дакуменце"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-find-and-replace",
3
- "version": "44.3.0",
3
+ "version": "45.0.0-alpha.0",
4
4
  "description": "Find and replace feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -13,11 +13,12 @@
13
13
  "type": "module",
14
14
  "main": "src/index.js",
15
15
  "dependencies": {
16
- "@ckeditor/ckeditor5-core": "44.3.0",
17
- "@ckeditor/ckeditor5-ui": "44.3.0",
18
- "@ckeditor/ckeditor5-utils": "44.3.0",
19
- "ckeditor5": "44.3.0",
20
- "lodash-es": "4.17.21"
16
+ "@ckeditor/ckeditor5-core": "45.0.0-alpha.0",
17
+ "@ckeditor/ckeditor5-icons": "45.0.0-alpha.0",
18
+ "@ckeditor/ckeditor5-ui": "45.0.0-alpha.0",
19
+ "@ckeditor/ckeditor5-utils": "45.0.0-alpha.0",
20
+ "ckeditor5": "45.0.0-alpha.0",
21
+ "es-toolkit": "1.32.0"
21
22
  },
22
23
  "author": "CKSource (http://cksource.com/)",
23
24
  "license": "SEE LICENSE IN LICENSE.md",
@@ -14,86 +14,13 @@ import FindNextCommand from './findnextcommand.js';
14
14
  import FindPreviousCommand from './findpreviouscommand.js';
15
15
  import FindAndReplaceState from './findandreplacestate.js';
16
16
  import FindAndReplaceUtils from './findandreplaceutils.js';
17
- import { debounce } from 'lodash-es';
17
+ import { debounce } from 'es-toolkit/compat';
18
18
  import '../theme/findandreplace.css';
19
19
  const HIGHLIGHT_CLASS = 'ck-find-result_selected';
20
20
  /**
21
21
  * Implements the editing part for find and replace plugin. For example conversion, commands etc.
22
22
  */
23
23
  export default class FindAndReplaceEditing extends Plugin {
24
- constructor() {
25
- super(...arguments);
26
- /**
27
- * Reacts to document changes in order to update search list.
28
- */
29
- this._onDocumentChange = () => {
30
- const changedNodes = new Set();
31
- const removedMarkers = new Set();
32
- const model = this.editor.model;
33
- const { results } = this.state;
34
- const changes = model.document.differ.getChanges();
35
- const changedMarkers = model.document.differ.getChangedMarkers();
36
- // Get nodes in which changes happened to re-run a search callback on them.
37
- changes.forEach(change => {
38
- if (!change.position) {
39
- return;
40
- }
41
- if (change.name === '$text' || (change.position.nodeAfter && model.schema.isInline(change.position.nodeAfter))) {
42
- changedNodes.add(change.position.parent);
43
- [...model.markers.getMarkersAtPosition(change.position)].forEach(markerAtChange => {
44
- removedMarkers.add(markerAtChange.name);
45
- });
46
- }
47
- else if (change.type === 'insert' && change.position.nodeAfter) {
48
- changedNodes.add(change.position.nodeAfter);
49
- }
50
- });
51
- // Get markers from removed nodes also.
52
- changedMarkers.forEach(({ name, data: { newRange } }) => {
53
- if (newRange && newRange.start.root.rootName === '$graveyard') {
54
- removedMarkers.add(name);
55
- }
56
- });
57
- // Get markers from the updated nodes and remove all (search will be re-run on these nodes).
58
- changedNodes.forEach(node => {
59
- const markersInNode = [...model.markers.getMarkersIntersectingRange(model.createRangeIn(node))];
60
- markersInNode.forEach(marker => removedMarkers.add(marker.name));
61
- });
62
- // Remove results from the changed part of content.
63
- removedMarkers.forEach(markerName => {
64
- if (!results.has(markerName)) {
65
- return;
66
- }
67
- if (results.get(markerName) === this.state.highlightedResult) {
68
- this.state.highlightedResult = null;
69
- }
70
- results.remove(markerName);
71
- });
72
- // Run search callback again on updated nodes.
73
- const changedSearchResults = [];
74
- const findAndReplaceUtils = this.editor.plugins.get('FindAndReplaceUtils');
75
- changedNodes.forEach(nodeToCheck => {
76
- const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
77
- changedSearchResults.push(...changedNodeSearchResults);
78
- });
79
- changedMarkers.forEach(markerToCheck => {
80
- // Handle search result highlight update when T&C plugin is active.
81
- // Lookup is performed only on newly inserted markers.
82
- if (markerToCheck.data.newRange) {
83
- const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
84
- changedSearchResults.push(...changedNodeSearchResults);
85
- }
86
- });
87
- if (!this.state.highlightedResult && changedSearchResults.length) {
88
- // If there are found phrases but none is selected, select the first one.
89
- this.state.highlightedResult = changedSearchResults[0];
90
- }
91
- else {
92
- // If there is already highlight item then refresh highlight offset after appending new items.
93
- this.state.refreshHighlightOffset(model);
94
- }
95
- };
96
- }
97
24
  /**
98
25
  * @inheritDoc
99
26
  */
@@ -112,6 +39,10 @@ export default class FindAndReplaceEditing extends Plugin {
112
39
  static get isOfficialPlugin() {
113
40
  return true;
114
41
  }
42
+ /**
43
+ * An object storing the find and replace state within a given editor instance.
44
+ */
45
+ state;
115
46
  /**
116
47
  * @inheritDoc
117
48
  */
@@ -230,4 +161,74 @@ export default class FindAndReplaceEditing extends Plugin {
230
161
  }
231
162
  });
232
163
  }
164
+ /**
165
+ * Reacts to document changes in order to update search list.
166
+ */
167
+ _onDocumentChange = () => {
168
+ const changedNodes = new Set();
169
+ const removedMarkers = new Set();
170
+ const model = this.editor.model;
171
+ const { results } = this.state;
172
+ const changes = model.document.differ.getChanges();
173
+ const changedMarkers = model.document.differ.getChangedMarkers();
174
+ // Get nodes in which changes happened to re-run a search callback on them.
175
+ changes.forEach(change => {
176
+ if (!change.position) {
177
+ return;
178
+ }
179
+ if (change.name === '$text' || (change.position.nodeAfter && model.schema.isInline(change.position.nodeAfter))) {
180
+ changedNodes.add(change.position.parent);
181
+ [...model.markers.getMarkersAtPosition(change.position)].forEach(markerAtChange => {
182
+ removedMarkers.add(markerAtChange.name);
183
+ });
184
+ }
185
+ else if (change.type === 'insert' && change.position.nodeAfter) {
186
+ changedNodes.add(change.position.nodeAfter);
187
+ }
188
+ });
189
+ // Get markers from removed nodes also.
190
+ changedMarkers.forEach(({ name, data: { newRange } }) => {
191
+ if (newRange && newRange.start.root.rootName === '$graveyard') {
192
+ removedMarkers.add(name);
193
+ }
194
+ });
195
+ // Get markers from the updated nodes and remove all (search will be re-run on these nodes).
196
+ changedNodes.forEach(node => {
197
+ const markersInNode = [...model.markers.getMarkersIntersectingRange(model.createRangeIn(node))];
198
+ markersInNode.forEach(marker => removedMarkers.add(marker.name));
199
+ });
200
+ // Remove results from the changed part of content.
201
+ removedMarkers.forEach(markerName => {
202
+ if (!results.has(markerName)) {
203
+ return;
204
+ }
205
+ if (results.get(markerName) === this.state.highlightedResult) {
206
+ this.state.highlightedResult = null;
207
+ }
208
+ results.remove(markerName);
209
+ });
210
+ // Run search callback again on updated nodes.
211
+ const changedSearchResults = [];
212
+ const findAndReplaceUtils = this.editor.plugins.get('FindAndReplaceUtils');
213
+ changedNodes.forEach(nodeToCheck => {
214
+ const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
215
+ changedSearchResults.push(...changedNodeSearchResults);
216
+ });
217
+ changedMarkers.forEach(markerToCheck => {
218
+ // Handle search result highlight update when T&C plugin is active.
219
+ // Lookup is performed only on newly inserted markers.
220
+ if (markerToCheck.data.newRange) {
221
+ const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
222
+ changedSearchResults.push(...changedNodeSearchResults);
223
+ }
224
+ });
225
+ if (!this.state.highlightedResult && changedSearchResults.length) {
226
+ // If there are found phrases but none is selected, select the first one.
227
+ this.state.highlightedResult = changedSearchResults[0];
228
+ }
229
+ else {
230
+ // If there is already highlight item then refresh highlight offset after appending new items.
231
+ this.state.refreshHighlightOffset(model);
232
+ }
233
+ };
233
234
  }
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module find-and-replace/findandreplaceui
7
7
  */
8
- import { type Editor, Plugin } from 'ckeditor5/src/core.js';
8
+ import { Plugin, type Editor } from 'ckeditor5/src/core.js';
9
9
  import { Dialog, type ViewWithCssTransitionDisabler } from 'ckeditor5/src/ui.js';
10
10
  import FindAndReplaceFormView from './ui/findandreplaceformview.js';
11
11
  /**
@@ -6,9 +6,9 @@
6
6
  * @module find-and-replace/findandreplaceui
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
+ import { IconFindReplace } from 'ckeditor5/src/icons.js';
9
10
  import { ButtonView, MenuBarMenuListItemButtonView, Dialog, DialogViewPosition, createDropdown, DropdownView, FormHeaderView, CssTransitionDisablerMixin } from 'ckeditor5/src/ui.js';
10
11
  import FindAndReplaceFormView from './ui/findandreplaceformview.js';
11
- import loupeIcon from '../theme/icons/find-replace.svg';
12
12
  /**
13
13
  * The default find and replace UI.
14
14
  *
@@ -34,6 +34,10 @@ export default class FindAndReplaceUI extends Plugin {
34
34
  static get isOfficialPlugin() {
35
35
  return true;
36
36
  }
37
+ /**
38
+ * A reference to the find and replace form view.
39
+ */
40
+ formView;
37
41
  /**
38
42
  * @inheritDoc
39
43
  */
@@ -133,7 +137,7 @@ export default class FindAndReplaceUI extends Plugin {
133
137
  }
134
138
  }, { priority: 'low' });
135
139
  dropdownView.buttonView.set({
136
- icon: loupeIcon,
140
+ icon: IconFindReplace,
137
141
  label: t('Find and replace'),
138
142
  keystroke: 'CTRL+F',
139
143
  tooltip: true
@@ -199,7 +203,7 @@ export default class FindAndReplaceUI extends Plugin {
199
203
  // Button should be disabled when in source editing mode. See #10001.
200
204
  buttonView.bind('isEnabled').to(findCommand);
201
205
  buttonView.set({
202
- icon: loupeIcon,
206
+ icon: IconFindReplace,
203
207
  label: t('Find and replace'),
204
208
  keystroke: 'CTRL+F'
205
209
  });
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { Plugin } from 'ckeditor5/src/core.js';
6
6
  import { Collection, uid } from 'ckeditor5/src/utils.js';
7
- import { escapeRegExp } from 'lodash-es';
7
+ import { escapeRegExp } from 'es-toolkit/compat';
8
8
  /**
9
9
  * A set of helpers related to find and replace.
10
10
  */
@@ -10,6 +10,10 @@ import { Command } from 'ckeditor5/src/core.js';
10
10
  * The find command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
11
11
  */
12
12
  export default class FindCommand extends Command {
13
+ /**
14
+ * The find and replace state object used for command operations.
15
+ */
16
+ _state;
13
17
  /**
14
18
  * Creates a new `FindCommand` instance.
15
19
  *
@@ -12,6 +12,10 @@ import { Command } from 'ckeditor5/src/core.js';
12
12
  * It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
13
13
  */
14
14
  export default class FindNextCommand extends Command {
15
+ /**
16
+ * The find and replace state object used for command operations.
17
+ */
18
+ _state;
15
19
  /**
16
20
  * Creates a new `FindNextCommand` instance.
17
21
  *
@@ -7,6 +7,10 @@
7
7
  */
8
8
  import { Command } from 'ckeditor5/src/core.js';
9
9
  export class ReplaceCommandBase extends Command {
10
+ /**
11
+ * The find and replace state object used for command operations.
12
+ */
13
+ _state;
10
14
  /**
11
15
  * Creates a new `ReplaceCommand` instance.
12
16
  *
@@ -11,13 +11,83 @@ import { FocusTracker, KeystrokeHandler, Rect, isVisible } from 'ckeditor5/src/u
11
11
  // eslint-disable-next-line ckeditor5-rules/ckeditor-imports
12
12
  import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
13
13
  import '../../theme/findandreplaceform.css';
14
- import { icons } from 'ckeditor5/src/core.js';
14
+ import { IconPreviousArrow } from 'ckeditor5/src/icons.js';
15
15
  /**
16
16
  * The find and replace form view class.
17
17
  *
18
18
  * See {@link module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView}.
19
19
  */
20
20
  export default class FindAndReplaceFormView extends View {
21
+ /**
22
+ * A collection of child views.
23
+ */
24
+ children;
25
+ /**
26
+ * The find in text input view that stores the searched string.
27
+ *
28
+ * @internal
29
+ */
30
+ _findInputView;
31
+ /**
32
+ * The replace input view.
33
+ */
34
+ _replaceInputView;
35
+ /**
36
+ * The find button view that initializes the search process.
37
+ */
38
+ _findButtonView;
39
+ /**
40
+ * The find previous button view.
41
+ */
42
+ _findPrevButtonView;
43
+ /**
44
+ * The find next button view.
45
+ */
46
+ _findNextButtonView;
47
+ /**
48
+ * A collapsible view aggregating the advanced search options.
49
+ */
50
+ _advancedOptionsCollapsibleView;
51
+ /**
52
+ * A switch button view controlling the "Match case" option.
53
+ */
54
+ _matchCaseSwitchView;
55
+ /**
56
+ * A switch button view controlling the "Whole words only" option.
57
+ */
58
+ _wholeWordsOnlySwitchView;
59
+ /**
60
+ * The replace button view.
61
+ */
62
+ _replaceButtonView;
63
+ /**
64
+ * The replace all button view.
65
+ */
66
+ _replaceAllButtonView;
67
+ /**
68
+ * The `div` aggregating the inputs.
69
+ */
70
+ _inputsDivView;
71
+ /**
72
+ * The `div` aggregating the action buttons.
73
+ */
74
+ _actionButtonsDivView;
75
+ /**
76
+ * Tracks information about the DOM focus in the form.
77
+ */
78
+ _focusTracker;
79
+ /**
80
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
81
+ */
82
+ _keystrokes;
83
+ /**
84
+ * A collection of views that can be focused in the form.
85
+ */
86
+ _focusables;
87
+ /**
88
+ * Helps cycling over {@link #_focusables} in the form.
89
+ */
90
+ focusCycler;
21
91
  /**
22
92
  * Creates a view of find and replace form.
23
93
  *
@@ -41,14 +111,14 @@ export default class FindAndReplaceFormView extends View {
41
111
  this._findPrevButtonView = this._createButton({
42
112
  label: t('Previous result'),
43
113
  class: 'ck-button-prev',
44
- icon: icons.previousArrow,
114
+ icon: IconPreviousArrow,
45
115
  keystroke: 'Shift+F3',
46
116
  tooltip: true
47
117
  });
48
118
  this._findNextButtonView = this._createButton({
49
119
  label: t('Next result'),
50
120
  class: 'ck-button-next',
51
- icon: icons.previousArrow,
121
+ icon: IconPreviousArrow,
52
122
  keystroke: 'F3',
53
123
  tooltip: true
54
124
  });
@@ -1 +0,0 @@
1
- <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m12.87 13.786 1.532-1.286 3.857 4.596a1 1 0 1 1-1.532 1.286l-3.857-4.596z"/><path d="M16.004 8.5a6.5 6.5 0 0 1-9.216 5.905c-1.154-.53-.863-1.415-.663-1.615.194-.194.564-.592 1.635-.141a4.5 4.5 0 0 0 5.89-5.904l-.104-.227 1.332-1.331c.045-.046.196-.041.224.007a6.47 6.47 0 0 1 .902 3.306zm-3.4-5.715c.562.305.742 1.106.354 1.494-.388.388-.995.414-1.476.178a4.5 4.5 0 0 0-6.086 5.882l.114.236-1.348 1.349c-.038.037-.17.022-.198-.023a6.5 6.5 0 0 1 5.54-9.9 6.469 6.469 0 0 1 3.1.784z"/><path d="M4.001 11.93.948 8.877a.2.2 0 0 1 .141-.341h6.106a.2.2 0 0 1 .141.341L4.283 11.93a.2.2 0 0 1-.282 0zm11.083-6.789 3.053 3.053a.2.2 0 0 1-.14.342H11.89a.2.2 0 0 1-.14-.342l3.052-3.053a.2.2 0 0 1 .282 0z"/></svg>