@riebeckite/plugin-text-fragment 0.0.31 → 0.0.33

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.
package/README.md CHANGED
@@ -7,6 +7,10 @@ you select in an article.
7
7
 
8
8
  [日本語](./README_ja.md)
9
9
 
10
+ ## Live example
11
+
12
+ Select any sentence on this page. The plugin adds a copy action that produces a URL containing a `#:~:text=` fragment for the selected text.
13
+
10
14
  ## Overview
11
15
 
12
16
  Client-only plugin. On page load it installs one selection popover with two
@@ -21,17 +25,17 @@ actions:
21
25
 
22
26
  ```ts
23
27
  import { defineConfig } from "@riebeckite/core";
24
- import { textFragmentPlugin } from "@riebeckite/plugin-text-fragment";
28
+ import { textFragment } from "@riebeckite/plugin-text-fragment";
25
29
 
26
30
  export default defineConfig({
27
31
  // ...
28
- plugins: [textFragmentPlugin()],
32
+ plugins: [textFragment()],
29
33
  });
30
34
  ```
31
35
 
32
- `textFragmentPlugin()` registers `style.css` and the `initTextFragmentShare`
36
+ `textFragment()` registers `style.css` and the `initTextFragmentShare`
33
37
  client entry, which the app calls during page initialization. Pass
34
- `textFragmentPlugin({ labels })` to override individual UI labels.
38
+ `textFragment({ labels })` to override individual UI labels.
35
39
 
36
40
  ## Behavior
37
41
 
@@ -59,7 +63,7 @@ The fragment follows `#:~:text=[prefix-,]start[,end][,-suffix]`:
59
63
 
60
64
  ## API
61
65
 
62
- - `textFragmentPlugin(options?)` — plugin factory; `options.labels` overrides
66
+ - `textFragment(options?)` — plugin factory; `options.labels` overrides
63
67
  the UI labels
64
68
  - `initTextFragmentShare(labels?)` — client initializer (also via
65
69
  `@riebeckite/plugin-text-fragment/client`)
package/README_ja.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  [English](./README.md)
8
8
 
9
+ ## 実行例
10
+
11
+ このページの任意の文を選択してください。選択したテキストへの `#:~:text=` フラグメントを含む URL をコピーする操作が追加されます。
12
+
9
13
  ## できること
10
14
 
11
15
  クライアント専用のプラグインです。ページ表示時に、選択範囲のそばへ小さなポップオーバーを出し、次の二つをコピーできます。
@@ -17,15 +21,15 @@
17
21
 
18
22
  ```ts
19
23
  import { defineConfig } from "@riebeckite/core";
20
- import { textFragmentPlugin } from "@riebeckite/plugin-text-fragment";
24
+ import { textFragment } from "@riebeckite/plugin-text-fragment";
21
25
 
22
26
  export default defineConfig({
23
27
  // ...
24
- plugins: [textFragmentPlugin()],
28
+ plugins: [textFragment()],
25
29
  });
26
30
  ```
27
31
 
28
- `textFragmentPlugin()` はスタイルと `initTextFragmentShare` を登録します。初期化処理はアプリのページ初期化時に呼ばれます。`textFragmentPlugin({ labels })` で UI ラベルだけを差し替えられます。
32
+ `textFragment()` はスタイルと `initTextFragmentShare` を登録します。初期化処理はアプリのページ初期化時に呼ばれます。`textFragment({ labels })` で UI ラベルだけを差し替えられます。
29
33
 
30
34
  ## 挙動
31
35
 
@@ -42,12 +46,12 @@ export default defineConfig({
42
46
  - `,`、`-`、`&` はパーセントエンコードします(`%2C`、`%2D`、`%26`)。
43
47
  - それ以外は UTF-8 単位でエンコードします(改行は `%0A`)。
44
48
  - ページ URL に既存のハッシュがあるときは、それを外してからディレクティブを付けます。
45
- - 200 文字前後より長い選択、または改行を含む選択は、先頭と末尾のトークンによる `start,end` に短縮します。
49
+ - 約 200 文字を超える選択、または改行を含む選択は、先頭と末尾のトークンによる `start,end` に短縮します。
46
50
  - 空または空白だけの選択は `""` を返します。
47
51
 
48
52
  ## 公開 API
49
53
 
50
- - `textFragmentPlugin(options?)`:プラグインファクトリ。`options.labels` で UI ラベルを上書きする
54
+ - `textFragment(options?)`:プラグインファクトリ。`options.labels` で UI ラベルを上書きする
51
55
  - `initTextFragmentShare(labels?)`:ブラウザ初期化関数(`@riebeckite/plugin-text-fragment/client` からも読み込める)
52
56
  - `encodeTextFragment(text)`:テキスト片をパーセントエンコードする
53
57
  - `buildTextFragmentUrl(pageUrl, selection, options?)`:ディープリンクを作る。`options` は `{ prefix?, suffix? }`
package/dist/client.js CHANGED
@@ -86,6 +86,7 @@ function initTextFragmentShare(labels = {}) {
86
86
  popover.appendChild(status);
87
87
  document.body.appendChild(popover);
88
88
  let active = null;
89
+ const copyingTimers = /* @__PURE__ */ new Set();
89
90
  let isCopying = false;
90
91
  const hide = () => {
91
92
  active = null;
@@ -118,18 +119,20 @@ function initTextFragmentShare(labels = {}) {
118
119
  popover.hidden = false;
119
120
  positionPopover(popover, selection.rect);
120
121
  } finally {
121
- window.setTimeout(() => {
122
+ const timer = window.setTimeout(() => {
123
+ copyingTimers.delete(timer);
122
124
  isCopying = false;
123
125
  }, 0);
126
+ copyingTimers.add(timer);
124
127
  }
125
128
  };
126
- linkButton.addEventListener("click", () => {
129
+ const onLinkButtonClick = () => {
127
130
  void copy(
128
131
  (selection) => buildTextFragmentUrl(resolvePageUrl(), selection),
129
132
  resolved.linkCopiedLabel
130
133
  );
131
- });
132
- quoteButton.addEventListener("click", () => {
134
+ };
135
+ const onQuoteButtonClick = () => {
133
136
  void copy(
134
137
  (selection) => buildQuoteMarkdown({
135
138
  url: resolvePageUrl(),
@@ -138,23 +141,43 @@ function initTextFragmentShare(labels = {}) {
138
141
  }),
139
142
  resolved.quoteCopiedLabel
140
143
  );
141
- });
142
- popover.addEventListener("mousedown", (event) => event.preventDefault());
143
- document.addEventListener("selectionchange", refresh);
144
- document.addEventListener("mouseup", refresh);
145
- document.addEventListener("keyup", refresh);
146
- document.addEventListener("keydown", (event) => {
144
+ };
145
+ const onPopoverMouseDown = (event) => event.preventDefault();
146
+ const onKeyDown = (event) => {
147
147
  if (event.key === "Escape" && !popover.hidden) {
148
148
  event.preventDefault();
149
149
  hide();
150
150
  }
151
- });
152
- document.addEventListener("mousedown", (event) => {
151
+ };
152
+ const onMouseDown = (event) => {
153
153
  if (popover.hidden) return;
154
154
  if (event.target instanceof Node && popover.contains(event.target)) return;
155
155
  hide();
156
- });
156
+ };
157
+ linkButton.addEventListener("click", onLinkButtonClick);
158
+ quoteButton.addEventListener("click", onQuoteButtonClick);
159
+ popover.addEventListener("mousedown", onPopoverMouseDown);
160
+ document.addEventListener("selectionchange", refresh);
161
+ document.addEventListener("mouseup", refresh);
162
+ document.addEventListener("keyup", refresh);
163
+ document.addEventListener("keydown", onKeyDown);
164
+ document.addEventListener("mousedown", onMouseDown);
157
165
  window.addEventListener("scroll", hide, { passive: true });
166
+ return () => {
167
+ initialized = false;
168
+ for (const timer of copyingTimers) window.clearTimeout(timer);
169
+ copyingTimers.clear();
170
+ linkButton.removeEventListener("click", onLinkButtonClick);
171
+ quoteButton.removeEventListener("click", onQuoteButtonClick);
172
+ popover.removeEventListener("mousedown", onPopoverMouseDown);
173
+ document.removeEventListener("selectionchange", refresh);
174
+ document.removeEventListener("mouseup", refresh);
175
+ document.removeEventListener("keyup", refresh);
176
+ document.removeEventListener("keydown", onKeyDown);
177
+ document.removeEventListener("mousedown", onMouseDown);
178
+ window.removeEventListener("scroll", hide);
179
+ popover.remove();
180
+ };
158
181
  }
159
182
  function createButton(label) {
160
183
  const button = document.createElement("button");
package/dist/index.d.ts CHANGED
@@ -9,4 +9,4 @@ export type TextFragmentPluginOptions = {
9
9
  * Registers the client-only text fragment share plugin: a selection popover
10
10
  * that copies a `#:~:text=` deep link or a Markdown quote.
11
11
  */
12
- export declare function textFragmentPlugin(options?: TextFragmentPluginOptions): RiebeckitePlugin;
12
+ export declare function textFragment(options?: TextFragmentPluginOptions): RiebeckitePlugin;
package/dist/index.js CHANGED
@@ -93,6 +93,7 @@ function initTextFragmentShare(labels = {}) {
93
93
  popover.appendChild(status);
94
94
  document.body.appendChild(popover);
95
95
  let active = null;
96
+ const copyingTimers = /* @__PURE__ */ new Set();
96
97
  let isCopying = false;
97
98
  const hide = () => {
98
99
  active = null;
@@ -125,18 +126,20 @@ function initTextFragmentShare(labels = {}) {
125
126
  popover.hidden = false;
126
127
  positionPopover(popover, selection.rect);
127
128
  } finally {
128
- window.setTimeout(() => {
129
+ const timer = window.setTimeout(() => {
130
+ copyingTimers.delete(timer);
129
131
  isCopying = false;
130
132
  }, 0);
133
+ copyingTimers.add(timer);
131
134
  }
132
135
  };
133
- linkButton.addEventListener("click", () => {
136
+ const onLinkButtonClick = () => {
134
137
  void copy(
135
138
  (selection) => buildTextFragmentUrl(resolvePageUrl(), selection),
136
139
  resolved.linkCopiedLabel
137
140
  );
138
- });
139
- quoteButton.addEventListener("click", () => {
141
+ };
142
+ const onQuoteButtonClick = () => {
140
143
  void copy(
141
144
  (selection) => buildQuoteMarkdown({
142
145
  url: resolvePageUrl(),
@@ -145,23 +148,43 @@ function initTextFragmentShare(labels = {}) {
145
148
  }),
146
149
  resolved.quoteCopiedLabel
147
150
  );
148
- });
149
- popover.addEventListener("mousedown", (event) => event.preventDefault());
150
- document.addEventListener("selectionchange", refresh);
151
- document.addEventListener("mouseup", refresh);
152
- document.addEventListener("keyup", refresh);
153
- document.addEventListener("keydown", (event) => {
151
+ };
152
+ const onPopoverMouseDown = (event) => event.preventDefault();
153
+ const onKeyDown = (event) => {
154
154
  if (event.key === "Escape" && !popover.hidden) {
155
155
  event.preventDefault();
156
156
  hide();
157
157
  }
158
- });
159
- document.addEventListener("mousedown", (event) => {
158
+ };
159
+ const onMouseDown = (event) => {
160
160
  if (popover.hidden) return;
161
161
  if (event.target instanceof Node && popover.contains(event.target)) return;
162
162
  hide();
163
- });
163
+ };
164
+ linkButton.addEventListener("click", onLinkButtonClick);
165
+ quoteButton.addEventListener("click", onQuoteButtonClick);
166
+ popover.addEventListener("mousedown", onPopoverMouseDown);
167
+ document.addEventListener("selectionchange", refresh);
168
+ document.addEventListener("mouseup", refresh);
169
+ document.addEventListener("keyup", refresh);
170
+ document.addEventListener("keydown", onKeyDown);
171
+ document.addEventListener("mousedown", onMouseDown);
164
172
  window.addEventListener("scroll", hide, { passive: true });
173
+ return () => {
174
+ initialized = false;
175
+ for (const timer of copyingTimers) window.clearTimeout(timer);
176
+ copyingTimers.clear();
177
+ linkButton.removeEventListener("click", onLinkButtonClick);
178
+ quoteButton.removeEventListener("click", onQuoteButtonClick);
179
+ popover.removeEventListener("mousedown", onPopoverMouseDown);
180
+ document.removeEventListener("selectionchange", refresh);
181
+ document.removeEventListener("mouseup", refresh);
182
+ document.removeEventListener("keyup", refresh);
183
+ document.removeEventListener("keydown", onKeyDown);
184
+ document.removeEventListener("mousedown", onMouseDown);
185
+ window.removeEventListener("scroll", hide);
186
+ popover.remove();
187
+ };
165
188
  }
166
189
  function createButton(label) {
167
190
  const button = document.createElement("button");
@@ -249,7 +272,7 @@ function copyWithExecCommand(value) {
249
272
  }
250
273
 
251
274
  // index.ts
252
- function textFragmentPlugin(options = {}) {
275
+ function textFragment(options = {}) {
253
276
  return definePlugin({
254
277
  name: "text-fragment",
255
278
  assets: [
@@ -277,5 +300,5 @@ export {
277
300
  buildTextFragmentUrl,
278
301
  encodeTextFragment,
279
302
  initTextFragmentShare,
280
- textFragmentPlugin
303
+ textFragment
281
304
  };
@@ -14,4 +14,4 @@ export declare const DEFAULT_TEXT_FRAGMENT_LABELS: TextFragmentLabels;
14
14
  * Client-only: returns immediately when there is no `document`, and only wires
15
15
  * its listeners once per page.
16
16
  */
17
- export declare function initTextFragmentShare(labels?: Partial<TextFragmentLabels>): void;
17
+ export declare function initTextFragmentShare(labels?: Partial<TextFragmentLabels>): (() => void) | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-text-fragment",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "./style.css": "./style.css"
22
22
  },
23
23
  "dependencies": {
24
- "@riebeckite/core": "0.0.31"
24
+ "@riebeckite/core": "0.0.33"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsx": "^4.22.4"