@allior/wmake-utils 0.0.5 → 0.0.8
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/dist/react/index.iife.js +1 -1
- package/dist/react/index.iife.js.map +1 -1
- package/dist/react/index.js +19 -18
- package/dist/root/allior-cache.d.ts +10 -0
- package/dist/root/allior-cache.d.ts.map +1 -0
- package/dist/root/index.d.ts +1 -0
- package/dist/root/index.d.ts.map +1 -1
- package/dist/root/index.iife.js +1 -1
- package/dist/root/index.iife.js.map +1 -1
- package/dist/root/index.js +83 -50
- package/dist/root/index.js.map +1 -1
- package/package.json +8 -6
- package/src/root/allior-cache.ts +47 -0
- package/src/root/index.ts +2 -1
package/dist/react/index.iife.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
var J=Object.defineProperty;var Q=(a,c,f)=>c in a?J(a,c,{enumerable:!0,configurable:!0,writable:!0,value:f}):a[c]=f;var E=(a,c,f)=>Q(a,typeof c!="symbol"?c+"":c,f);(function(a,c){"use strict";let f=15,_=[".message-row",".reply"],w=null;function M(t){w=t}function A(t){const e=w;if(!e)return;const s=_.join(", ");for(;;){const n=e.querySelectorAll(s),o=n.length;if(f<=0||o<=f)break;const r=n[0];if(!r)break;t==null||t(r),r.remove()}}function T(t,e){const s=w;if(!s)return;const n=_.map(r=>`${r}[data-msgid="${t}"]`).join(", ");s.querySelectorAll(n).forEach(r=>{e==null||e(t,r),r.remove()})}function $(t,e){const s=w;if(!s)return;const n=_.map(r=>`${r}[data-userid="${t}"]`).join(", ");s.querySelectorAll(n).forEach(r=>{e==null||e(t,r),r.remove()})}function k(t=[".message-row",".reply"],e){f=e??15,_=t}function j(t){return t.replace(/\s/g," ").replace(/[<>"^]/g,e=>"&#"+e.charCodeAt(0)+";")}function z(t,e){const s=t.querySelectorAll("img"),n=s.length;if(n===0){e(t);return}let o=0;function r(){o++,o===n&&e(t)}s.forEach(i=>{i.complete?r():(i.addEventListener("load",r),i.addEventListener("error",r))})}const K=t=>t.replace(/([A-Z])/g,"-$1").toLowerCase();function O(t){return t.toLowerCase().replace(/^vite_/,"").replace(/_([a-z])/g,(e,s)=>s.toUpperCase()).replace(/^[a-z]/,e=>e.toLowerCase())}function U(t){return"--"+t.toLowerCase().replace(/^vite_/,"").replace(/_/g,"-")}class W{constructor(e=50){E(this,"_map");E(this,"_limit");return this._map=new Map,this._limit=e,new Proxy(this,{set:(s,n,o)=>s._set(n,o),get:(s,n)=>s._get(n),has:(s,n)=>s._has(n)})}_has(e){return this._map.has(e)}_set(e,s){return this._map.set(e,s),this._handleLimit(),!0}_get(e){return this._map.get(e)}_handleLimit(){if(this._map.size>this._limit){const e=this._map.keys().next().value;e!==void 0&&this._map.delete(e)}}}const N=({ignoredUsers:t=[],ignoreCommands:e=!0})=>{const s=t.map(i=>i.toLowerCase()),n=i=>s.includes(i.toLowerCase()),o=i=>e&&i.trim().startsWith("!");return{shouldIgnore:(i,m)=>n(i)||o(m)}};function I(t,e={}){const{messageLimit:s,limitSelectors:n,onRemove:o}=e,r=c.useRef(o);return r.current=o,c.useEffect(()=>{const i=t.current;return M(i),()=>M(null)},[t]),c.useEffect(()=>{k(n,s)},[s,n]),()=>{A(i=>{var m;return(m=r.current)==null?void 0:m.call(r,i)})}}function Z(t,e){const{getRowId:s,rowIdAttribute:n="data-row-id",preparingRef:o,onPreparedEnter:r,...i}=e,[m,C]=c.useState([]),[B,P]=c.useState([]),L=c.useRef(r);L.current=r;const R=c.useRef(new Map),h=I(t,{...i,onRemove:l=>{const u=l.getAttribute(n);if(u!=null){const d=Number(u);C(g=>g.filter(p=>s(p)!==d))}}}),y=c.useCallback(l=>{P(u=>{const d=u.find(g=>s(g)===l);return d==null?u:(C(g=>g.some(p=>s(p)===l)?g:[...g,d]),queueMicrotask(h),u.filter(g=>s(g)!==l))})},[s,h]),D=c.useCallback((l,u)=>{const d=R.current,g=d.get(l);if(g&&(g.disconnect(),d.delete(l)),u==null||!(o!=null&&o.current))return;const p=o.current,S=new IntersectionObserver(H=>{var q;const b=H[0];if(!(b!=null&&b.isIntersecting))return;const v=l;S.disconnect(),d.delete(v),(q=L.current)==null||q.call(L,v,b.target),y(v)},{root:p,threshold:0,rootMargin:"0px"});S.observe(u),d.set(l,S)},[o,y]);c.useEffect(()=>()=>{R.current.forEach(l=>l.disconnect()),R.current.clear()},[]);const G=c.useCallback(l=>{o?P(u=>[...u,l]):(C(u=>[...u,l]),h())},[o,h]);return{rows:m,preparedRows:o?B:[],setRows:C,addRow:G,moveRow:y,registerPreparedRow:o?D:()=>{},applyLimit:h}}a.AlliorCache=W,a.deleteMessage=T,a.deleteMessages=$,a.handleMessageLimit=A,a.htmlEncode=j,a.plugMessagesLimit=k,a.preloadImagesThenShow=z,a.setMessageLimitRoot=M,a.toCamelCase=O,a.toCssCustomProperty=U,a.toKebabCase=K,a.useIgnoreMessage=N,a.useMessageLimit=I,a.useMessageRows=Z,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})})(this.WmakeTtsReact=this.WmakeTtsReact||{},React);
|
|
2
2
|
//# sourceMappingURL=index.iife.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.iife.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts","../../src/react/hooks/use-ignore-message.ts","../../src/react/hooks/use-message-limit.ts","../../src/react/hooks/use-message-rows.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n","interface Props {\r\n ignoredUsers?: string[];\r\n ignoreCommands?: boolean;\r\n}\r\n\r\nexport const useIgnoreMessage = ({\r\n ignoredUsers = [],\r\n ignoreCommands = true,\r\n}: Props) => {\r\n const ignoredUsersLower = ignoredUsers.map((user) => user.toLowerCase());\r\n\r\n const shouldIgnoreUser = (username: string) => {\r\n return ignoredUsersLower.includes(username.toLowerCase());\r\n };\r\n\r\n const shouldIgnoreMessage = (text: string) => {\r\n return ignoreCommands && text.trim().startsWith(\"!\");\r\n };\r\n\r\n const shouldIgnore = (username: string, text: string) => {\r\n return shouldIgnoreUser(username) || shouldIgnoreMessage(text);\r\n };\r\n\r\n return { shouldIgnore };\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport type { RefObject } from \"react\";\r\nimport {\r\n setMessageLimitRoot,\r\n handleMessageLimit as handleMessageLimitRoot,\r\n plugMessagesLimit,\r\n} from \"@/root\";\r\n\r\nexport interface UseMessageLimitOptions {\r\n messageLimit?: number;\r\n /** Селекторы строк для лимита (по умолчанию [\".message-row\", \".reply\"]) */\r\n limitSelectors?: string[];\r\n /** Вызов при удалении элемента из DOM (для синхронизации state в React) */\r\n onRemove?: (element: Element) => void;\r\n}\r\n\r\n/**\r\n * Подключает message limit к корневому элементу и опциям из widget load.\r\n * Возвращает handleMessageLimit — вызывать после добавления новой строки.\r\n */\r\nexport function useMessageLimit(\r\n rootRef: RefObject<Element | null>,\r\n options: UseMessageLimitOptions = {},\r\n): () => void {\r\n const { messageLimit, limitSelectors, onRemove } = options;\r\n const onRemoveRef = useRef(onRemove);\r\n onRemoveRef.current = onRemove;\r\n\r\n useEffect(() => {\r\n const root = rootRef.current;\r\n setMessageLimitRoot(root);\r\n return () => setMessageLimitRoot(null);\r\n }, [rootRef]);\r\n\r\n useEffect(() => {\r\n plugMessagesLimit(limitSelectors, messageLimit);\r\n }, [messageLimit, limitSelectors]);\r\n\r\n return () => {\r\n handleMessageLimitRoot((el) => onRemoveRef.current?.(el));\r\n };\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Dispatch, RefObject, SetStateAction } from \"react\";\r\nimport { useMessageLimit } from \"./use-message-limit\";\r\nimport type { UseMessageLimitOptions } from \"./use-message-limit\";\r\n\r\nexport interface UseMessageRowsOptions<T> extends Omit<\r\n UseMessageLimitOptions,\r\n \"onRemove\"\r\n> {\r\n /**\r\n * Функция получения id строки (для синхронизации state при удалении из DOM по data-row-id).\r\n */\r\n getRowId: (row: T) => number;\r\n /** Имя атрибута с id на DOM-элементе строки (по умолчанию \"data-row-id\"). */\r\n rowIdAttribute?: string;\r\n /**\r\n * Ref контейнера «подготовки»: при наличии addRow сначала добавляет в preparedRows;\r\n * при пересечении строки с этим контейнером вызывается onPreparedEnter, затем строка переносится в rows.\r\n */\r\n preparingRef?: RefObject<Element | null>;\r\n /**\r\n * Вызывается, когда строка из preparedRows попадает в зону preparingRef (например, для processZeroWidthEmotes).\r\n * После вызова строка переносится в основной список (main rows).\r\n */\r\n onPreparedEnter?: (rowId: number, element: Element) => void;\r\n}\r\n\r\nexport interface UseMessageRowsReturn<T> {\r\n /** Текущий список строк (основной список, рендер в main). */\r\n rows: T[];\r\n /** Строки в зоне подготовки (рендер внутри элемента с preparingRef). При отсутствии preparingRef — пустой массив. */\r\n preparedRows: T[];\r\n /** Установить список строк (полная замена). */\r\n setRows: Dispatch<SetStateAction<T[]>>;\r\n /** Добавить строку: при наличии preparingRef — в preparedRows, иначе в rows с применением лимита. */\r\n addRow: (row: T) => void;\r\n /** Перенести строку из preparedRows в rows по id. Вызывается автоматически после onPreparedEnter при использовании preparingRef. */\r\n moveRow: (rowId: number) => void;\r\n /**\r\n * Зарегистрировать DOM-элемент подготовленной строки для наблюдения (вызывать при монтировании/размонтировании строки).\r\n * Когда элемент попадает в зону preparingRef, вызывается onPreparedEnter и затем moveRow(rowId).\r\n */\r\n registerPreparedRow: (rowId: number, element: Element | null) => void;\r\n /** Применить лимит сообщений (удалить лишние строки из DOM и state). */\r\n applyLimit: () => void;\r\n}\r\n\r\n/**\r\n * Хук для управления списком строк с учётом message limit:\r\n * хранит rows в state, при applyLimit удаляет лишние из DOM и синхронизирует state по data-row-id.\r\n * При передаче preparingRef новые строки сначала попадают в preparedRows; при пересечении с контейнером\r\n * вызывается onPreparedEnter и строка переносится в rows.\r\n */\r\nexport function useMessageRows<T>(\r\n rootRef: RefObject<Element | null>,\r\n options: UseMessageRowsOptions<T>,\r\n): UseMessageRowsReturn<T> {\r\n const {\r\n getRowId,\r\n rowIdAttribute = \"data-row-id\",\r\n preparingRef,\r\n onPreparedEnter,\r\n ...limitOptions\r\n } = options;\r\n const [rows, setRows] = useState<T[]>([]);\r\n const [preparedRows, setPreparedRows] = useState<T[]>([]);\r\n const onPreparedEnterRef = useRef(onPreparedEnter);\r\n onPreparedEnterRef.current = onPreparedEnter;\r\n const observersRef = useRef<Map<number, IntersectionObserver>>(new Map());\r\n\r\n const applyLimit = useMessageLimit(rootRef, {\r\n ...limitOptions,\r\n onRemove: (el: Element) => {\r\n const idStr = el.getAttribute(rowIdAttribute);\r\n if (idStr != null) {\r\n const id = Number(idStr);\r\n setRows((prev) => prev.filter((r) => getRowId(r) !== id));\r\n }\r\n },\r\n });\r\n\r\n const moveRow = useCallback(\r\n (rowId: number) => {\r\n setPreparedRows((prev) => {\r\n const row = prev.find((r) => getRowId(r) === rowId);\r\n if (row == null) return prev;\r\n setRows((r) =>\r\n r.some((x) => getRowId(x) === rowId) ? r : [...r, row],\r\n );\r\n queueMicrotask(applyLimit);\r\n return prev.filter((r) => getRowId(r) !== rowId);\r\n });\r\n },\r\n [getRowId, applyLimit],\r\n );\r\n\r\n const registerPreparedRow = useCallback(\r\n (rowId: number, element: Element | null) => {\r\n const observers = observersRef.current;\r\n const existing = observers.get(rowId);\r\n if (existing) {\r\n existing.disconnect();\r\n observers.delete(rowId);\r\n }\r\n if (element == null || !preparingRef?.current) return;\r\n\r\n const root = preparingRef.current;\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n const entry = entries[0];\r\n if (!entry?.isIntersecting) return;\r\n const id = rowId;\r\n observer.disconnect();\r\n observers.delete(id);\r\n onPreparedEnterRef.current?.(id, entry.target);\r\n moveRow(id);\r\n },\r\n { root, threshold: 0, rootMargin: \"0px\" },\r\n );\r\n observer.observe(element);\r\n observers.set(rowId, observer);\r\n },\r\n [preparingRef, moveRow],\r\n );\r\n\r\n useEffect(() => {\r\n return () => {\r\n observersRef.current.forEach((o) => o.disconnect());\r\n observersRef.current.clear();\r\n };\r\n }, []);\r\n\r\n const addRow = useCallback(\r\n (row: T) => {\r\n if (preparingRef) {\r\n setPreparedRows((prev) => [...prev, row]);\r\n } else {\r\n setRows((prev) => [...prev, row]);\r\n applyLimit();\r\n }\r\n },\r\n [preparingRef, applyLimit],\r\n );\r\n\r\n return {\r\n rows,\r\n preparedRows: preparingRef ? preparedRows : [],\r\n setRows,\r\n addRow,\r\n moveRow,\r\n registerPreparedRow: preparingRef ? registerPreparedRow : () => {},\r\n applyLimit,\r\n };\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty","useIgnoreMessage","ignoredUsers","ignoreCommands","ignoredUsersLower","user","shouldIgnoreUser","username","shouldIgnoreMessage","text","useMessageLimit","options","messageLimit","onRemove","onRemoveRef","useRef","useEffect","handleMessageLimitRoot","_a","useMessageRows","getRowId","rowIdAttribute","preparingRef","onPreparedEnter","limitOptions","rows","setRows","useState","preparedRows","setPreparedRows","onPreparedEnterRef","observersRef","applyLimit","idStr","id","prev","r","moveRow","useCallback","rowId","row","x","registerPreparedRow","observers","existing","observer","entries","entry","o","addRow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.iife.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts","../../src/root/allior-cache.ts","../../src/react/hooks/use-ignore-message.ts","../../src/react/hooks/use-message-limit.ts","../../src/react/hooks/use-message-rows.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n","export class AlliorCache<K = unknown, V = unknown> {\r\n private readonly _map: Map<K, V>;\r\n private readonly _limit: number;\r\n\r\n constructor(limit = 50) {\r\n this._map = new Map<K, V>();\r\n this._limit = limit;\r\n\r\n return new Proxy(\r\n this as AlliorCache<K, V> & Record<string | symbol, unknown>,\r\n {\r\n set: (target, key, value) => {\r\n return (target as any)._set(key, value);\r\n },\r\n get: (target, key) => {\r\n return (target as any)._get(key);\r\n },\r\n has: (target, key) => {\r\n return (target as any)._has(key);\r\n },\r\n },\r\n ) as any;\r\n }\r\n\r\n private _has(key: K): boolean {\r\n return this._map.has(key);\r\n }\r\n\r\n private _set(key: K, value: V): true {\r\n this._map.set(key, value);\r\n this._handleLimit();\r\n return true;\r\n }\r\n\r\n private _get(key: K): V | undefined {\r\n return this._map.get(key);\r\n }\r\n\r\n private _handleLimit(): void {\r\n if (this._map.size > this._limit) {\r\n const firstKey = this._map.keys().next().value;\r\n if (firstKey !== undefined) {\r\n this._map.delete(firstKey);\r\n }\r\n }\r\n }\r\n}\r\n","interface Props {\r\n ignoredUsers?: string[];\r\n ignoreCommands?: boolean;\r\n}\r\n\r\nexport const useIgnoreMessage = ({\r\n ignoredUsers = [],\r\n ignoreCommands = true,\r\n}: Props) => {\r\n const ignoredUsersLower = ignoredUsers.map((user) => user.toLowerCase());\r\n\r\n const shouldIgnoreUser = (username: string) => {\r\n return ignoredUsersLower.includes(username.toLowerCase());\r\n };\r\n\r\n const shouldIgnoreMessage = (text: string) => {\r\n return ignoreCommands && text.trim().startsWith(\"!\");\r\n };\r\n\r\n const shouldIgnore = (username: string, text: string) => {\r\n return shouldIgnoreUser(username) || shouldIgnoreMessage(text);\r\n };\r\n\r\n return { shouldIgnore };\r\n};\r\n","import { useEffect, useRef } from \"react\";\r\nimport type { RefObject } from \"react\";\r\nimport {\r\n setMessageLimitRoot,\r\n handleMessageLimit as handleMessageLimitRoot,\r\n plugMessagesLimit,\r\n} from \"@/root\";\r\n\r\nexport interface UseMessageLimitOptions {\r\n messageLimit?: number;\r\n /** Селекторы строк для лимита (по умолчанию [\".message-row\", \".reply\"]) */\r\n limitSelectors?: string[];\r\n /** Вызов при удалении элемента из DOM (для синхронизации state в React) */\r\n onRemove?: (element: Element) => void;\r\n}\r\n\r\n/**\r\n * Подключает message limit к корневому элементу и опциям из widget load.\r\n * Возвращает handleMessageLimit — вызывать после добавления новой строки.\r\n */\r\nexport function useMessageLimit(\r\n rootRef: RefObject<Element | null>,\r\n options: UseMessageLimitOptions = {},\r\n): () => void {\r\n const { messageLimit, limitSelectors, onRemove } = options;\r\n const onRemoveRef = useRef(onRemove);\r\n onRemoveRef.current = onRemove;\r\n\r\n useEffect(() => {\r\n const root = rootRef.current;\r\n setMessageLimitRoot(root);\r\n return () => setMessageLimitRoot(null);\r\n }, [rootRef]);\r\n\r\n useEffect(() => {\r\n plugMessagesLimit(limitSelectors, messageLimit);\r\n }, [messageLimit, limitSelectors]);\r\n\r\n return () => {\r\n handleMessageLimitRoot((el) => onRemoveRef.current?.(el));\r\n };\r\n}\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Dispatch, RefObject, SetStateAction } from \"react\";\r\nimport { useMessageLimit } from \"./use-message-limit\";\r\nimport type { UseMessageLimitOptions } from \"./use-message-limit\";\r\n\r\nexport interface UseMessageRowsOptions<T> extends Omit<\r\n UseMessageLimitOptions,\r\n \"onRemove\"\r\n> {\r\n /**\r\n * Функция получения id строки (для синхронизации state при удалении из DOM по data-row-id).\r\n */\r\n getRowId: (row: T) => number;\r\n /** Имя атрибута с id на DOM-элементе строки (по умолчанию \"data-row-id\"). */\r\n rowIdAttribute?: string;\r\n /**\r\n * Ref контейнера «подготовки»: при наличии addRow сначала добавляет в preparedRows;\r\n * при пересечении строки с этим контейнером вызывается onPreparedEnter, затем строка переносится в rows.\r\n */\r\n preparingRef?: RefObject<Element | null>;\r\n /**\r\n * Вызывается, когда строка из preparedRows попадает в зону preparingRef (например, для processZeroWidthEmotes).\r\n * После вызова строка переносится в основной список (main rows).\r\n */\r\n onPreparedEnter?: (rowId: number, element: Element) => void;\r\n}\r\n\r\nexport interface UseMessageRowsReturn<T> {\r\n /** Текущий список строк (основной список, рендер в main). */\r\n rows: T[];\r\n /** Строки в зоне подготовки (рендер внутри элемента с preparingRef). При отсутствии preparingRef — пустой массив. */\r\n preparedRows: T[];\r\n /** Установить список строк (полная замена). */\r\n setRows: Dispatch<SetStateAction<T[]>>;\r\n /** Добавить строку: при наличии preparingRef — в preparedRows, иначе в rows с применением лимита. */\r\n addRow: (row: T) => void;\r\n /** Перенести строку из preparedRows в rows по id. Вызывается автоматически после onPreparedEnter при использовании preparingRef. */\r\n moveRow: (rowId: number) => void;\r\n /**\r\n * Зарегистрировать DOM-элемент подготовленной строки для наблюдения (вызывать при монтировании/размонтировании строки).\r\n * Когда элемент попадает в зону preparingRef, вызывается onPreparedEnter и затем moveRow(rowId).\r\n */\r\n registerPreparedRow: (rowId: number, element: Element | null) => void;\r\n /** Применить лимит сообщений (удалить лишние строки из DOM и state). */\r\n applyLimit: () => void;\r\n}\r\n\r\n/**\r\n * Хук для управления списком строк с учётом message limit:\r\n * хранит rows в state, при applyLimit удаляет лишние из DOM и синхронизирует state по data-row-id.\r\n * При передаче preparingRef новые строки сначала попадают в preparedRows; при пересечении с контейнером\r\n * вызывается onPreparedEnter и строка переносится в rows.\r\n */\r\nexport function useMessageRows<T>(\r\n rootRef: RefObject<Element | null>,\r\n options: UseMessageRowsOptions<T>,\r\n): UseMessageRowsReturn<T> {\r\n const {\r\n getRowId,\r\n rowIdAttribute = \"data-row-id\",\r\n preparingRef,\r\n onPreparedEnter,\r\n ...limitOptions\r\n } = options;\r\n const [rows, setRows] = useState<T[]>([]);\r\n const [preparedRows, setPreparedRows] = useState<T[]>([]);\r\n const onPreparedEnterRef = useRef(onPreparedEnter);\r\n onPreparedEnterRef.current = onPreparedEnter;\r\n const observersRef = useRef<Map<number, IntersectionObserver>>(new Map());\r\n\r\n const applyLimit = useMessageLimit(rootRef, {\r\n ...limitOptions,\r\n onRemove: (el: Element) => {\r\n const idStr = el.getAttribute(rowIdAttribute);\r\n if (idStr != null) {\r\n const id = Number(idStr);\r\n setRows((prev) => prev.filter((r) => getRowId(r) !== id));\r\n }\r\n },\r\n });\r\n\r\n const moveRow = useCallback(\r\n (rowId: number) => {\r\n setPreparedRows((prev) => {\r\n const row = prev.find((r) => getRowId(r) === rowId);\r\n if (row == null) return prev;\r\n setRows((r) =>\r\n r.some((x) => getRowId(x) === rowId) ? r : [...r, row],\r\n );\r\n queueMicrotask(applyLimit);\r\n return prev.filter((r) => getRowId(r) !== rowId);\r\n });\r\n },\r\n [getRowId, applyLimit],\r\n );\r\n\r\n const registerPreparedRow = useCallback(\r\n (rowId: number, element: Element | null) => {\r\n const observers = observersRef.current;\r\n const existing = observers.get(rowId);\r\n if (existing) {\r\n existing.disconnect();\r\n observers.delete(rowId);\r\n }\r\n if (element == null || !preparingRef?.current) return;\r\n\r\n const root = preparingRef.current;\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n const entry = entries[0];\r\n if (!entry?.isIntersecting) return;\r\n const id = rowId;\r\n observer.disconnect();\r\n observers.delete(id);\r\n onPreparedEnterRef.current?.(id, entry.target);\r\n moveRow(id);\r\n },\r\n { root, threshold: 0, rootMargin: \"0px\" },\r\n );\r\n observer.observe(element);\r\n observers.set(rowId, observer);\r\n },\r\n [preparingRef, moveRow],\r\n );\r\n\r\n useEffect(() => {\r\n return () => {\r\n observersRef.current.forEach((o) => o.disconnect());\r\n observersRef.current.clear();\r\n };\r\n }, []);\r\n\r\n const addRow = useCallback(\r\n (row: T) => {\r\n if (preparingRef) {\r\n setPreparedRows((prev) => [...prev, row]);\r\n } else {\r\n setRows((prev) => [...prev, row]);\r\n applyLimit();\r\n }\r\n },\r\n [preparingRef, applyLimit],\r\n );\r\n\r\n return {\r\n rows,\r\n preparedRows: preparingRef ? preparedRows : [],\r\n setRows,\r\n addRow,\r\n moveRow,\r\n registerPreparedRow: preparingRef ? registerPreparedRow : () => {},\r\n applyLimit,\r\n };\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","s","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty","AlliorCache","limit","__publicField","target","key","value","firstKey","useIgnoreMessage","ignoredUsers","ignoreCommands","ignoredUsersLower","user","shouldIgnoreUser","username","shouldIgnoreMessage","text","useMessageLimit","options","messageLimit","onRemove","onRemoveRef","useRef","useEffect","handleMessageLimitRoot","_a","useMessageRows","getRowId","rowIdAttribute","preparingRef","onPreparedEnter","limitOptions","rows","setRows","useState","preparedRows","setPreparedRows","onPreparedEnterRef","observersRef","applyLimit","idStr","id","prev","r","moveRow","useCallback","rowId","row","x","registerPreparedRow","observers","existing","observer","entries","entry","o","addRow"],"mappings":"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"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setMessageLimitRoot as P, plugMessagesLimit as U, handleMessageLimit as q } from "../root/index.js";
|
|
2
|
-
import {
|
|
3
|
-
import { useRef as
|
|
2
|
+
import { AlliorCache as B, deleteMessage as D, deleteMessages as F, htmlEncode as G, preloadImagesThenShow as H, toCamelCase as J, toCssCustomProperty as Q, toKebabCase as V } from "../root/index.js";
|
|
3
|
+
import { useRef as L, useEffect as v, useState as S, useCallback as C } from "react";
|
|
4
4
|
const T = ({
|
|
5
5
|
ignoredUsers: u = [],
|
|
6
6
|
ignoreCommands: d = !0
|
|
@@ -9,7 +9,7 @@ const T = ({
|
|
|
9
9
|
return { shouldIgnore: (s, l) => a(s) || t(l) };
|
|
10
10
|
};
|
|
11
11
|
function y(u, d = {}) {
|
|
12
|
-
const { messageLimit: n, limitSelectors: a, onRemove: t } = d, i =
|
|
12
|
+
const { messageLimit: n, limitSelectors: a, onRemove: t } = d, i = L(t);
|
|
13
13
|
return i.current = t, v(() => {
|
|
14
14
|
const s = u.current;
|
|
15
15
|
return P(s), () => P(null);
|
|
@@ -29,9 +29,9 @@ function W(u, d) {
|
|
|
29
29
|
preparingRef: t,
|
|
30
30
|
onPreparedEnter: i,
|
|
31
31
|
...s
|
|
32
|
-
} = d, [l, p] = S([]), [
|
|
32
|
+
} = d, [l, p] = S([]), [A, I] = S([]), f = L(i);
|
|
33
33
|
f.current = i;
|
|
34
|
-
const h =
|
|
34
|
+
const h = L(/* @__PURE__ */ new Map()), g = y(u, {
|
|
35
35
|
...s,
|
|
36
36
|
onRemove: (e) => {
|
|
37
37
|
const r = e.getAttribute(a);
|
|
@@ -40,7 +40,7 @@ function W(u, d) {
|
|
|
40
40
|
p((o) => o.filter((m) => n(m) !== c));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
}), M =
|
|
43
|
+
}), M = C(
|
|
44
44
|
(e) => {
|
|
45
45
|
I((r) => {
|
|
46
46
|
const c = r.find((o) => n(o) === e);
|
|
@@ -50,7 +50,7 @@ function W(u, d) {
|
|
|
50
50
|
});
|
|
51
51
|
},
|
|
52
52
|
[n, g]
|
|
53
|
-
),
|
|
53
|
+
), E = C(
|
|
54
54
|
(e, r) => {
|
|
55
55
|
const c = h.current, o = c.get(e);
|
|
56
56
|
if (o && (o.disconnect(), c.delete(e)), r == null || !(t != null && t.current)) return;
|
|
@@ -71,7 +71,7 @@ function W(u, d) {
|
|
|
71
71
|
v(() => () => {
|
|
72
72
|
h.current.forEach((e) => e.disconnect()), h.current.clear();
|
|
73
73
|
}, []);
|
|
74
|
-
const
|
|
74
|
+
const k = C(
|
|
75
75
|
(e) => {
|
|
76
76
|
t ? I((r) => [...r, e]) : (p((r) => [...r, e]), g());
|
|
77
77
|
},
|
|
@@ -79,26 +79,27 @@ function W(u, d) {
|
|
|
79
79
|
);
|
|
80
80
|
return {
|
|
81
81
|
rows: l,
|
|
82
|
-
preparedRows: t ?
|
|
82
|
+
preparedRows: t ? A : [],
|
|
83
83
|
setRows: p,
|
|
84
|
-
addRow:
|
|
84
|
+
addRow: k,
|
|
85
85
|
moveRow: M,
|
|
86
|
-
registerPreparedRow: t ?
|
|
86
|
+
registerPreparedRow: t ? E : () => {
|
|
87
87
|
},
|
|
88
88
|
applyLimit: g
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
export {
|
|
92
|
-
B as
|
|
93
|
-
D as
|
|
92
|
+
B as AlliorCache,
|
|
93
|
+
D as deleteMessage,
|
|
94
|
+
F as deleteMessages,
|
|
94
95
|
q as handleMessageLimit,
|
|
95
|
-
|
|
96
|
+
G as htmlEncode,
|
|
96
97
|
U as plugMessagesLimit,
|
|
97
|
-
|
|
98
|
+
H as preloadImagesThenShow,
|
|
98
99
|
P as setMessageLimitRoot,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
J as toCamelCase,
|
|
101
|
+
Q as toCssCustomProperty,
|
|
102
|
+
V as toKebabCase,
|
|
102
103
|
T as useIgnoreMessage,
|
|
103
104
|
y as useMessageLimit,
|
|
104
105
|
W as useMessageRows
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class AlliorCache<K = unknown, V = unknown> {
|
|
2
|
+
private readonly _map;
|
|
3
|
+
private readonly _limit;
|
|
4
|
+
constructor(limit?: number);
|
|
5
|
+
private _has;
|
|
6
|
+
private _set;
|
|
7
|
+
private _get;
|
|
8
|
+
private _handleLimit;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=allior-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allior-cache.d.ts","sourceRoot":"","sources":["../../src/root/allior-cache.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,KAAK,SAAK;IAoBtB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,YAAY;CAQrB"}
|
package/dist/root/index.d.ts
CHANGED
package/dist/root/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/root/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/root/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC"}
|
package/dist/root/index.iife.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(a){"use strict";let
|
|
1
|
+
var S=Object.defineProperty;var b=(a,l,n)=>l in a?S(a,l,{enumerable:!0,configurable:!0,writable:!0,value:n}):a[l]=n;var h=(a,l,n)=>b(a,typeof l!="symbol"?l+"":l,n);(function(a){"use strict";let l=15,n=[".message-row",".reply"],i=null;function g(t){i=t}function m(t){const e=i;if(!e)return;const s=n.join(", ");for(;;){const o=e.querySelectorAll(s),c=o.length;if(l<=0||c<=l)break;const r=o[0];if(!r)break;t==null||t(r),r.remove()}}function d(t,e){const s=i;if(!s)return;const o=n.map(r=>`${r}[data-msgid="${t}"]`).join(", ");s.querySelectorAll(o).forEach(r=>{e==null||e(t,r),r.remove()})}function f(t,e){const s=i;if(!s)return;const o=n.map(r=>`${r}[data-userid="${t}"]`).join(", ");s.querySelectorAll(o).forEach(r=>{e==null||e(t,r),r.remove()})}function _(t=[".message-row",".reply"],e){l=e??15,n=t}function p(t){return t.replace(/\s/g," ").replace(/[<>"^]/g,e=>"&#"+e.charCodeAt(0)+";")}function C(t,e){const s=t.querySelectorAll("img"),o=s.length;if(o===0){e(t);return}let c=0;function r(){c++,c===o&&e(t)}s.forEach(u=>{u.complete?r():(u.addEventListener("load",r),u.addEventListener("error",r))})}const L=t=>t.replace(/([A-Z])/g,"-$1").toLowerCase();function y(t){return t.toLowerCase().replace(/^vite_/,"").replace(/_([a-z])/g,(e,s)=>s.toUpperCase()).replace(/^[a-z]/,e=>e.toLowerCase())}function w(t){return"--"+t.toLowerCase().replace(/^vite_/,"").replace(/_/g,"-")}class M{constructor(e=50){h(this,"_map");h(this,"_limit");return this._map=new Map,this._limit=e,new Proxy(this,{set:(s,o,c)=>s._set(o,c),get:(s,o)=>s._get(o),has:(s,o)=>s._has(o)})}_has(e){return this._map.has(e)}_set(e,s){return this._map.set(e,s),this._handleLimit(),!0}_get(e){return this._map.get(e)}_handleLimit(){if(this._map.size>this._limit){const e=this._map.keys().next().value;e!==void 0&&this._map.delete(e)}}}a.AlliorCache=M,a.deleteMessage=d,a.deleteMessages=f,a.handleMessageLimit=m,a.htmlEncode=p,a.plugMessagesLimit=_,a.preloadImagesThenShow=C,a.setMessageLimitRoot=g,a.toCamelCase=y,a.toCssCustomProperty=w,a.toKebabCase=L,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})})(this.WmakeTts=this.WmakeTts||{});
|
|
2
2
|
//# sourceMappingURL=index.iife.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.iife.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.iife.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts","../../src/root/allior-cache.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n","export class AlliorCache<K = unknown, V = unknown> {\r\n private readonly _map: Map<K, V>;\r\n private readonly _limit: number;\r\n\r\n constructor(limit = 50) {\r\n this._map = new Map<K, V>();\r\n this._limit = limit;\r\n\r\n return new Proxy(\r\n this as AlliorCache<K, V> & Record<string | symbol, unknown>,\r\n {\r\n set: (target, key, value) => {\r\n return (target as any)._set(key, value);\r\n },\r\n get: (target, key) => {\r\n return (target as any)._get(key);\r\n },\r\n has: (target, key) => {\r\n return (target as any)._has(key);\r\n },\r\n },\r\n ) as any;\r\n }\r\n\r\n private _has(key: K): boolean {\r\n return this._map.has(key);\r\n }\r\n\r\n private _set(key: K, value: V): true {\r\n this._map.set(key, value);\r\n this._handleLimit();\r\n return true;\r\n }\r\n\r\n private _get(key: K): V | undefined {\r\n return this._map.get(key);\r\n }\r\n\r\n private _handleLimit(): void {\r\n if (this._map.size > this._limit) {\r\n const firstKey = this._map.keys().next().value;\r\n if (firstKey !== undefined) {\r\n this._map.delete(firstKey);\r\n }\r\n }\r\n }\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","s","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty","AlliorCache","limit","__publicField","target","key","value","firstKey"],"mappings":"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"}
|
package/dist/root/index.js
CHANGED
|
@@ -1,73 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
var h = Object.defineProperty;
|
|
2
|
+
var p = (t, e, s) => e in t ? h(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;
|
|
3
|
+
var i = (t, e, s) => p(t, typeof e != "symbol" ? e + "" : e, s);
|
|
4
|
+
let u = 15, l = [".message-row", ".reply"], n = null;
|
|
5
|
+
function g(t) {
|
|
6
|
+
n = t;
|
|
4
7
|
}
|
|
5
|
-
function
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
8
|
-
const
|
|
8
|
+
function m(t) {
|
|
9
|
+
const e = n;
|
|
10
|
+
if (!e) return;
|
|
11
|
+
const s = l.join(", ");
|
|
9
12
|
for (; ; ) {
|
|
10
|
-
const
|
|
11
|
-
if (
|
|
13
|
+
const o = e.querySelectorAll(s), a = o.length;
|
|
14
|
+
if (u <= 0 || a <= u)
|
|
12
15
|
break;
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
15
|
-
|
|
16
|
+
const r = o[0];
|
|
17
|
+
if (!r) break;
|
|
18
|
+
t == null || t(r), r.remove();
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
function _(t, e) {
|
|
22
|
+
const s = n;
|
|
23
|
+
if (!s) return;
|
|
24
|
+
const o = l.map((r) => `${r}[data-msgid="${t}"]`).join(", ");
|
|
25
|
+
s.querySelectorAll(o).forEach((r) => {
|
|
26
|
+
e == null || e(t, r), r.remove();
|
|
24
27
|
});
|
|
25
28
|
}
|
|
26
|
-
function d(
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function d(t, e) {
|
|
30
|
+
const s = n;
|
|
31
|
+
if (!s) return;
|
|
32
|
+
const o = l.map((r) => `${r}[data-userid="${t}"]`).join(", ");
|
|
33
|
+
s.querySelectorAll(o).forEach((r) => {
|
|
34
|
+
e == null || e(t, r), r.remove();
|
|
32
35
|
});
|
|
33
36
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
37
|
+
function L(t = [".message-row", ".reply"], e) {
|
|
38
|
+
u = e ?? 15, l = t;
|
|
36
39
|
}
|
|
37
|
-
function
|
|
38
|
-
return
|
|
40
|
+
function C(t) {
|
|
41
|
+
return t.replace(/\s/g, " ").replace(/[<>"^]/g, (e) => "&#" + e.charCodeAt(0) + ";");
|
|
39
42
|
}
|
|
40
|
-
function
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
t
|
|
43
|
+
function y(t, e) {
|
|
44
|
+
const s = t.querySelectorAll("img"), o = s.length;
|
|
45
|
+
if (o === 0) {
|
|
46
|
+
e(t);
|
|
44
47
|
return;
|
|
45
48
|
}
|
|
46
49
|
let a = 0;
|
|
47
|
-
function
|
|
48
|
-
a++, a ===
|
|
50
|
+
function r() {
|
|
51
|
+
a++, a === o && e(t);
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
s.forEach((c) => {
|
|
54
|
+
c.complete ? r() : (c.addEventListener("load", r), c.addEventListener("error", r));
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
|
-
const
|
|
55
|
-
function
|
|
56
|
-
return
|
|
57
|
+
const w = (t) => t.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
58
|
+
function A(t) {
|
|
59
|
+
return t.toLowerCase().replace(/^vite_/, "").replace(/_([a-z])/g, (e, s) => s.toUpperCase()).replace(/^[a-z]/, (e) => e.toLowerCase());
|
|
57
60
|
}
|
|
58
|
-
function
|
|
59
|
-
return "--" +
|
|
61
|
+
function E(t) {
|
|
62
|
+
return "--" + t.toLowerCase().replace(/^vite_/, "").replace(/_/g, "-");
|
|
63
|
+
}
|
|
64
|
+
class S {
|
|
65
|
+
constructor(e = 50) {
|
|
66
|
+
i(this, "_map");
|
|
67
|
+
i(this, "_limit");
|
|
68
|
+
return this._map = /* @__PURE__ */ new Map(), this._limit = e, new Proxy(
|
|
69
|
+
this,
|
|
70
|
+
{
|
|
71
|
+
set: (s, o, a) => s._set(o, a),
|
|
72
|
+
get: (s, o) => s._get(o),
|
|
73
|
+
has: (s, o) => s._has(o)
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
_has(e) {
|
|
78
|
+
return this._map.has(e);
|
|
79
|
+
}
|
|
80
|
+
_set(e, s) {
|
|
81
|
+
return this._map.set(e, s), this._handleLimit(), !0;
|
|
82
|
+
}
|
|
83
|
+
_get(e) {
|
|
84
|
+
return this._map.get(e);
|
|
85
|
+
}
|
|
86
|
+
_handleLimit() {
|
|
87
|
+
if (this._map.size > this._limit) {
|
|
88
|
+
const e = this._map.keys().next().value;
|
|
89
|
+
e !== void 0 && this._map.delete(e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
60
92
|
}
|
|
61
93
|
export {
|
|
62
|
-
|
|
94
|
+
S as AlliorCache,
|
|
95
|
+
_ as deleteMessage,
|
|
63
96
|
d as deleteMessages,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
m as handleMessageLimit,
|
|
98
|
+
C as htmlEncode,
|
|
99
|
+
L as plugMessagesLimit,
|
|
100
|
+
y as preloadImagesThenShow,
|
|
101
|
+
g as setMessageLimitRoot,
|
|
102
|
+
A as toCamelCase,
|
|
103
|
+
E as toCssCustomProperty,
|
|
104
|
+
w as toKebabCase
|
|
72
105
|
};
|
|
73
106
|
//# sourceMappingURL=index.js.map
|
package/dist/root/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","s","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty"],"mappings":"AAAA,IAAIA,IAAgB,IAChBC,IAA2B,CAAC,gBAAgB,QAAQ,GAEpDC,IAA0B;AAMvB,SAASC,EAAoBC,GAA4B;AAC9D,EAAAF,IAAUE;AACZ;AAMO,SAASC,EACdC,GACM;AACN,QAAMF,IAAOF;AACb,MAAI,CAACE,EAAM;AAEX,QAAMG,IAAWN,EAAe,KAAK,IAAI;AAEzC,aAAS;AACP,UAAMO,IAAWJ,EAAK,iBAAiBG,CAAQ,GACzCE,IAAgBD,EAAS;AAE/B,QAAIR,KAAiB,KAAKS,KAAiBT;AACzC;AAGF,UAAMU,IAAQF,EAAS,CAAC;AACxB,QAAI,CAACE,EAAO;AACZ,IAAAJ,KAAA,QAAAA,EAAgBI,IAChBA,EAAM,OAAA;AAAA,EACR;AACF;AAEO,SAASC,EACdC,GACAC,GACM;AACN,QAAMT,IAAOF;AACb,MAAI,CAACE,EAAM;AACX,QAAMU,IAAYb,EACf,IAAI,CAACc,MAAM,GAAGA,CAAC,gBAAgBH,CAAK,IAAI,EACxC,KAAK,IAAI;AAEZ,EADiBR,EAAK,iBAAiBU,CAAS,EACvC,QAAQ,CAACE,MAAO;AACvB,IAAAH,KAAA,QAAAA,EAAQD,GAAOI,IACfA,EAAG,OAAA;AAAA,EACL,CAAC;AACH;AAEO,SAASC,EACdC,GACAL,GACM;AACN,QAAMT,IAAOF;AACb,MAAI,CAACE,EAAM;AACX,QAAMU,IAAYb,EACf,IAAI,CAACc,MAAM,GAAGA,CAAC,iBAAiBG,CAAM,IAAI,EAC1C,KAAK,IAAI;AAEZ,EADiBd,EAAK,iBAAiBU,CAAS,EACvC,QAAQ,CAACE,MAAO;AACvB,IAAAH,KAAA,QAAAA,EAAQK,GAAQF,IAChBA,EAAG,OAAA;AAAA,EACL,CAAC;AACH;AAEO,SAASG,EACdC,IAA4B,CAAC,gBAAgB,QAAQ,GACrDC,GACM;AACN,EAAArB,IAAgBqB,KAAkB,IAClCpB,IAAiBmB;AACnB;AC1EO,SAASE,EAAWC,GAAsB;AAC/C,SAAOA,EACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,WAAW,CAACC,MAAU,OAAOA,EAAM,WAAW,CAAC,IAAI,GAAG;AACnE;ACJO,SAASC,EACdC,GACAC,GACM;AACN,QAAMC,IAASF,EAAQ,iBAAmC,KAAK,GACzDG,IAAcD,EAAO;AAE3B,MAAIC,MAAgB,GAAG;AACrB,IAAAF,EAAaD,CAAO;AACpB;AAAA,EACF;AAEA,MAAII,IAAe;AAEnB,WAASC,IAAc;AACrB,IAAAD,KACIA,MAAiBD,KACnBF,EAAaD,CAAO;AAAA,EAExB;AAEA,EAAAE,EAAO,QAAQ,CAACI,MAAQ;AACtB,IAAIA,EAAI,WACND,EAAA,KAEAC,EAAI,iBAAiB,QAAQD,CAAW,GACxCC,EAAI,iBAAiB,SAASD,CAAW;AAAA,EAE7C,CAAC;AACH;ACjCO,MAAME,IAAc,CAACC,MAC1BA,EAAI,QAAQ,YAAY,KAAK,EAAE,YAAA;AAE1B,SAASC,EAAYD,GAAqB;AAC/C,SAAOA,EACJ,cACA,QAAQ,UAAU,EAAE,EACpB,QAAQ,aAAa,CAACE,GAAGC,MAAWA,EAAO,YAAA,CAAa,EACxD,QAAQ,UAAU,CAACb,MAAUA,EAAM,aAAa;AACrD;AAEO,SAASc,EAAoBJ,GAAqB;AACvD,SACE,OACAA,EACG,YAAA,EACA,QAAQ,UAAU,EAAE,EACpB,QAAQ,MAAM,GAAG;AAExB;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/root/message-limit.ts","../../src/root/html-encode.ts","../../src/root/preload-images.ts","../../src/root/string-utils.ts","../../src/root/allior-cache.ts"],"sourcesContent":["let messagesLimit = 15;\r\nlet limitSelectors: string[] = [\".message-row\", \".reply\"];\r\n\r\nlet rootRef: Element | null = null;\r\n\r\n/**\r\n * Set the container element for message limit and delete operations.\r\n * Pass the DOM element that wraps all message rows (e.g. document.querySelector(\"#main\")).\r\n */\r\nexport function setMessageLimitRoot(root: Element | null): void {\r\n rootRef = root;\r\n}\r\n\r\n/**\r\n * Enforces message limit by recalculating current count each time and removing\r\n * excess messages in a loop until totalMessages <= messagesLimit.\r\n */\r\nexport function handleMessageLimit(\r\n onRemoveMixin?: (element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n\r\n const selector = limitSelectors.join(\", \");\r\n\r\n for (;;) {\r\n const children = root.querySelectorAll(selector);\r\n const totalMessages = children.length;\r\n\r\n if (messagesLimit <= 0 || totalMessages <= messagesLimit) {\r\n break;\r\n }\r\n\r\n const first = children[0];\r\n if (!first) break;\r\n onRemoveMixin?.(first);\r\n first.remove();\r\n }\r\n}\r\n\r\nexport function deleteMessage(\r\n msgId: string,\r\n mixin?: (msgId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-msgid=\"${msgId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(msgId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function deleteMessages(\r\n userId: string,\r\n mixin?: (userId: string, element: Element) => void,\r\n): void {\r\n const root = rootRef;\r\n if (!root) return;\r\n const selectors = limitSelectors\r\n .map((s) => `${s}[data-userid=\"${userId}\"]`)\r\n .join(\", \");\r\n const elements = root.querySelectorAll(selectors);\r\n elements.forEach((el) => {\r\n mixin?.(userId, el);\r\n el.remove();\r\n });\r\n}\r\n\r\nexport function plugMessagesLimit(\r\n _limitSelectors: string[] = [\".message-row\", \".reply\"],\r\n _messagesLimit?: number,\r\n): void {\r\n messagesLimit = _messagesLimit ?? 15;\r\n limitSelectors = _limitSelectors;\r\n}\r\n","/**\r\n * Экранирование строки для безопасной вставки в HTML:\r\n * нормализация пробелов и замена < > \" ^ на сущности.\r\n */\r\nexport function htmlEncode(html: string): string {\r\n return html\r\n .replace(/\\s/g, \" \")\r\n .replace(/[<>\"^]/g, (match) => \"&#\" + match.charCodeAt(0) + \";\");\r\n}\r\n","/**\r\n * Ожидает загрузки всех <img> внутри контейнера, затем вызывает callback.\r\n * Если изображений нет — callback вызывается сразу.\r\n */\r\nexport function preloadImagesThenShow<T extends Element>(\r\n element: T,\r\n showFunction: (element: T) => void,\r\n): void {\r\n const images = element.querySelectorAll<HTMLImageElement>(\"img\");\r\n const totalImages = images.length;\r\n\r\n if (totalImages === 0) {\r\n showFunction(element);\r\n return;\r\n }\r\n\r\n let loadedImages = 0;\r\n\r\n function imageLoaded() {\r\n loadedImages++;\r\n if (loadedImages === totalImages) {\r\n showFunction(element);\r\n }\r\n }\r\n\r\n images.forEach((img) => {\r\n if (img.complete) {\r\n imageLoaded();\r\n } else {\r\n img.addEventListener(\"load\", imageLoaded);\r\n img.addEventListener(\"error\", imageLoaded);\r\n }\r\n });\r\n}\r\n","export const toKebabCase = (str: string) =>\r\n str.replace(/([A-Z])/g, '-$1').toLowerCase();\r\n\r\nexport function toCamelCase(str: string): string {\r\n return str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\r\n .replace(/^[a-z]/, (match) => match.toLowerCase());\r\n}\r\n\r\nexport function toCssCustomProperty(str: string): string {\r\n return (\r\n \"--\" +\r\n str\r\n .toLowerCase()\r\n .replace(/^vite_/, \"\")\r\n .replace(/_/g, \"-\")\r\n );\r\n}\r\n","export class AlliorCache<K = unknown, V = unknown> {\r\n private readonly _map: Map<K, V>;\r\n private readonly _limit: number;\r\n\r\n constructor(limit = 50) {\r\n this._map = new Map<K, V>();\r\n this._limit = limit;\r\n\r\n return new Proxy(\r\n this as AlliorCache<K, V> & Record<string | symbol, unknown>,\r\n {\r\n set: (target, key, value) => {\r\n return (target as any)._set(key, value);\r\n },\r\n get: (target, key) => {\r\n return (target as any)._get(key);\r\n },\r\n has: (target, key) => {\r\n return (target as any)._has(key);\r\n },\r\n },\r\n ) as any;\r\n }\r\n\r\n private _has(key: K): boolean {\r\n return this._map.has(key);\r\n }\r\n\r\n private _set(key: K, value: V): true {\r\n this._map.set(key, value);\r\n this._handleLimit();\r\n return true;\r\n }\r\n\r\n private _get(key: K): V | undefined {\r\n return this._map.get(key);\r\n }\r\n\r\n private _handleLimit(): void {\r\n if (this._map.size > this._limit) {\r\n const firstKey = this._map.keys().next().value;\r\n if (firstKey !== undefined) {\r\n this._map.delete(firstKey);\r\n }\r\n }\r\n }\r\n}\r\n"],"names":["messagesLimit","limitSelectors","rootRef","setMessageLimitRoot","root","handleMessageLimit","onRemoveMixin","selector","children","totalMessages","first","deleteMessage","msgId","mixin","selectors","s","el","deleteMessages","userId","plugMessagesLimit","_limitSelectors","_messagesLimit","htmlEncode","html","match","preloadImagesThenShow","element","showFunction","images","totalImages","loadedImages","imageLoaded","img","toKebabCase","str","toCamelCase","_","letter","toCssCustomProperty","AlliorCache","limit","__publicField","target","key","value","firstKey"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allior/wmake-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Utilities: message content parsers, image preloading, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/root/index.d.ts",
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"README.md",
|
|
21
21
|
"README_RU.md"
|
|
22
22
|
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"pub": "npm publish --access public",
|
|
25
|
+
"build": "rimraf dist && vite build && cross-env VITE_IIFE_ENTRY=root vite build --config vite.iife.config.ts && cross-env VITE_IIFE_ENTRY=react vite build --config vite.iife.config.ts && tsc -p tsconfig.types.json",
|
|
26
|
+
"clean": "rimraf dist",
|
|
27
|
+
"prepublishOnly": "pnpm run build"
|
|
28
|
+
},
|
|
23
29
|
"keywords": [
|
|
24
30
|
"wmake",
|
|
25
31
|
"parser",
|
|
@@ -35,9 +41,5 @@
|
|
|
35
41
|
"devDependencies": {
|
|
36
42
|
"cross-env": "^10.1.0",
|
|
37
43
|
"react": "^19.0.0"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "rimraf dist && vite build && cross-env VITE_IIFE_ENTRY=root vite build --config vite.iife.config.ts && cross-env VITE_IIFE_ENTRY=react vite build --config vite.iife.config.ts && tsc -p tsconfig.types.json",
|
|
41
|
-
"clean": "rimraf dist"
|
|
42
44
|
}
|
|
43
|
-
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class AlliorCache<K = unknown, V = unknown> {
|
|
2
|
+
private readonly _map: Map<K, V>;
|
|
3
|
+
private readonly _limit: number;
|
|
4
|
+
|
|
5
|
+
constructor(limit = 50) {
|
|
6
|
+
this._map = new Map<K, V>();
|
|
7
|
+
this._limit = limit;
|
|
8
|
+
|
|
9
|
+
return new Proxy(
|
|
10
|
+
this as AlliorCache<K, V> & Record<string | symbol, unknown>,
|
|
11
|
+
{
|
|
12
|
+
set: (target, key, value) => {
|
|
13
|
+
return (target as any)._set(key, value);
|
|
14
|
+
},
|
|
15
|
+
get: (target, key) => {
|
|
16
|
+
return (target as any)._get(key);
|
|
17
|
+
},
|
|
18
|
+
has: (target, key) => {
|
|
19
|
+
return (target as any)._has(key);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
) as any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private _has(key: K): boolean {
|
|
26
|
+
return this._map.has(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private _set(key: K, value: V): true {
|
|
30
|
+
this._map.set(key, value);
|
|
31
|
+
this._handleLimit();
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private _get(key: K): V | undefined {
|
|
36
|
+
return this._map.get(key);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private _handleLimit(): void {
|
|
40
|
+
if (this._map.size > this._limit) {
|
|
41
|
+
const firstKey = this._map.keys().next().value;
|
|
42
|
+
if (firstKey !== undefined) {
|
|
43
|
+
this._map.delete(firstKey);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/root/index.ts
CHANGED