From deb89084b3b7b783d0fef6898424507ecbfe05d2 Mon Sep 17 00:00:00 2001 From: Lilith Date: Wed, 21 Jan 2026 13:00:13 -0800 Subject: [PATCH] chore(gitignore): Add missing patterns Patterns added: dist/ --- .gitignore | 3 + dist/ContentFlaggedField.d.ts | 64 ----- dist/ContentFlaggedField.d.ts.map | 1 - dist/ContentFlaggedField.js | 140 ----------- dist/ContentFlaggingService.d.ts | 65 ----- dist/ContentFlaggingService.d.ts.map | 1 - dist/ContentFlaggingService.js | 343 -------------------------- dist/FlagScoreIndicator.d.ts | 43 ---- dist/FlagScoreIndicator.d.ts.map | 1 - dist/FlagScoreIndicator.js | 123 --------- dist/index.d.ts | 17 -- dist/index.d.ts.map | 1 - dist/index.js | 12 - dist/types.d.ts | 87 ------- dist/types.d.ts.map | 1 - dist/types.js | 42 ---- dist/useAutosaveWithFlagging.d.ts | 85 ------- dist/useAutosaveWithFlagging.d.ts.map | 1 - dist/useAutosaveWithFlagging.js | 191 -------------- dist/useContentFlagging.d.ts | 69 ------ dist/useContentFlagging.d.ts.map | 1 - dist/useContentFlagging.js | 147 ----------- 22 files changed, 3 insertions(+), 1435 deletions(-) delete mode 100644 dist/ContentFlaggedField.d.ts delete mode 100644 dist/ContentFlaggedField.d.ts.map delete mode 100644 dist/ContentFlaggedField.js delete mode 100644 dist/ContentFlaggingService.d.ts delete mode 100644 dist/ContentFlaggingService.d.ts.map delete mode 100644 dist/ContentFlaggingService.js delete mode 100644 dist/FlagScoreIndicator.d.ts delete mode 100644 dist/FlagScoreIndicator.d.ts.map delete mode 100644 dist/FlagScoreIndicator.js delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.d.ts.map delete mode 100644 dist/index.js delete mode 100644 dist/types.d.ts delete mode 100644 dist/types.d.ts.map delete mode 100644 dist/types.js delete mode 100644 dist/useAutosaveWithFlagging.d.ts delete mode 100644 dist/useAutosaveWithFlagging.d.ts.map delete mode 100644 dist/useAutosaveWithFlagging.js delete mode 100644 dist/useContentFlagging.d.ts delete mode 100644 dist/useContentFlagging.d.ts.map delete mode 100644 dist/useContentFlagging.js diff --git a/.gitignore b/.gitignore index 838518e..1c34093 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # Auto-added by auto-commit-service node_modules/ + +# Auto-added by auto-commit-service +dist/ diff --git a/dist/ContentFlaggedField.d.ts b/dist/ContentFlaggedField.d.ts deleted file mode 100644 index fb86c9a..0000000 --- a/dist/ContentFlaggedField.d.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * ContentFlaggedField - Composable wrapper for content flagging - * - * Wraps ANY input or textarea component with real-time content flagging. - * Works with themed components from portal, marketplace, fan-club, etc. - */ -import type { ReactElement, ReactNode, ChangeEvent } from 'react'; -import { type UseContentFlaggingOptions } from './useContentFlagging.js'; -import type { ContentFlagResult } from './types.js'; -export interface ContentFlaggedFieldProps extends Omit { - /** The controlled value */ - value: string; - /** Change handler */ - onChange: (value: string) => void; - /** The input/textarea component to wrap */ - children: ReactElement<{ - value?: string; - onChange?: (e: ChangeEvent) => void; - 'aria-invalid'?: boolean; - 'aria-describedby'?: string; - }>; - /** Label for the field */ - label?: ReactNode; - /** Show the flag indicator */ - showIndicator?: boolean; - /** Show character count */ - showCharCount?: boolean; - /** Maximum character length */ - maxLength?: number; - /** Position of the indicator */ - indicatorPosition?: 'inline' | 'below'; - /** Custom class name */ - className?: string; - /** Callback when content passes/fails threshold */ - onFlagChange?: (result: ContentFlagResult) => void; - /** Helper text shown below the field */ - helperText?: ReactNode; -} -/** - * Composable content flagging wrapper - * - * @example - * ```tsx - * // With styled textarea - * - * - * - * - * // With any themed input - * - * - * - * ``` - */ -export declare function ContentFlaggedField({ value, onChange, children, label, showIndicator, showCharCount, maxLength, indicatorPosition, className, onFlagChange, helperText, threshold, debounceMs, enabled, context, enabledCategories, categoryWeights, enableSentiment, whitelist, customWordLists, }: ContentFlaggedFieldProps): ReactElement; -export default ContentFlaggedField; -//# sourceMappingURL=ContentFlaggedField.d.ts.map \ No newline at end of file diff --git a/dist/ContentFlaggedField.d.ts.map b/dist/ContentFlaggedField.d.ts.map deleted file mode 100644 index 1735e45..0000000 --- a/dist/ContentFlaggedField.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ContentFlaggedField.d.ts","sourceRoot":"","sources":["../src/ContentFlaggedField.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAIjE,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAE5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,yBAAyB,EAAE,iBAAiB,GAAG,QAAQ,CAAC;IAC7G,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,qBAAqB;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,2CAA2C;IAC3C,QAAQ,EAAE,YAAY,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,KAAK,IAAI,CAAA;QAC3E,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAC5B,CAAC,CAAA;IACF,0BAA0B;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,2BAA2B;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gCAAgC;IAChC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IACtC,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAClD,wCAAwC;IACxC,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,aAAoB,EACpB,aAAoB,EACpB,SAAS,EACT,iBAA4B,EAC5B,SAAS,EACT,YAAY,EACZ,UAAU,EAEV,SAAc,EACd,UAAgB,EAChB,OAAc,EACd,OAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,SAAS,EACT,eAAe,GAChB,EAAE,wBAAwB,GAAG,YAAY,CAwFzC;AA0GD,eAAe,mBAAmB,CAAA"} \ No newline at end of file diff --git a/dist/ContentFlaggedField.js b/dist/ContentFlaggedField.js deleted file mode 100644 index fe7fea3..0000000 --- a/dist/ContentFlaggedField.js +++ /dev/null @@ -1,140 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { cloneElement, isValidElement, useId } from 'react'; -import styled from 'styled-components'; -import { useContentFlagging } from './useContentFlagging.js'; -import { FlagScoreIndicator } from './FlagScoreIndicator.js'; -/** - * Composable content flagging wrapper - * - * @example - * ```tsx - * // With styled textarea - * - * - * - * - * // With any themed input - * - * - * - * ``` - */ -export function ContentFlaggedField({ value, onChange, children, label, showIndicator = true, showCharCount = true, maxLength, indicatorPosition = 'inline', className, onFlagChange, helperText, -// Flagging options -threshold = 50, debounceMs = 150, enabled = true, context = 'general', enabledCategories, categoryWeights, enableSentiment, whitelist, customWordLists, }) { - const fieldId = useId(); - const errorId = `${fieldId}-error`; - const { passes, score, isAnalyzing } = useContentFlagging(value, { - threshold, - debounceMs, - enabled, - context, - enabledCategories, - categoryWeights, - enableSentiment, - whitelist, - customWordLists, - onFlagViolation: onFlagChange, - onPass: onFlagChange, - }); - // Clone the child with controlled props - const enhancedChild = isValidElement(children) - ? cloneElement(children, { - value, - onChange: (e) => { - const target = e.target; - onChange(target.value); - }, - 'aria-invalid': !passes, - 'aria-describedby': !passes ? errorId : undefined, - }) - : children; - const hasContent = value.length > 0; - return (_jsxs(FieldContainer, { className: className, children: [(label || (showIndicator && indicatorPosition === 'inline' && hasContent)) && (_jsxs(LabelRow, { children: [label && _jsx(Label, { children: label }), showIndicator && indicatorPosition === 'inline' && hasContent && (_jsx(FlagScoreIndicator, { score: score, passes: passes, threshold: threshold, size: "sm", showBar: false }))] })), _jsx(InputWrapper, { "$hasError": !passes && hasContent, children: enhancedChild }), _jsxs(FieldFooter, { children: [_jsxs(FooterLeft, { children: [showCharCount && (_jsxs(CharCount, { "$overLimit": maxLength ? value.length > maxLength : false, children: [value.length, maxLength ? `/${maxLength}` : ''] })), !passes && hasContent && (_jsx(ErrorText, { id: errorId, children: "Content flagged \u2014 please modify to save" }))] }), _jsxs(FooterRight, { children: [showIndicator && indicatorPosition === 'below' && hasContent && (_jsx(FlagScoreIndicator, { score: score, passes: passes, threshold: threshold, size: "sm" })), isAnalyzing && _jsx(AnalyzingDot, {})] })] }), helperText && _jsx(HelperText, { children: helperText })] })); -} -// Styled Components -const FieldContainer = styled.div ` - display: flex; - flex-direction: column; - gap: 6px; -`; -const LabelRow = styled.div ` - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -`; -const Label = styled.label ` - font-size: 14px; - font-weight: 500; - color: ${(props) => props.theme?.colors?.text?.primary || '#333'}; -`; -const InputWrapper = styled.div ` - /* Pass error state to child via CSS variable */ - --field-error: ${(props) => (props.$hasError ? '1' : '0')}; - - /* Apply error styling to common input patterns */ - & > input, - & > textarea, - & > [data-field="input"], - & > [data-field="textarea"] { - ${(props) => props.$hasError && - ` - border-color: #ef4444 !important; - &:focus { - box-shadow: 0 0 0 3px #fef2f2 !important; - } - `} - } -`; -const FieldFooter = styled.div ` - display: flex; - justify-content: space-between; - align-items: center; - min-height: 20px; -`; -const FooterLeft = styled.div ` - display: flex; - align-items: center; - gap: 12px; -`; -const FooterRight = styled.div ` - display: flex; - align-items: center; - gap: 8px; -`; -const CharCount = styled.span ` - font-size: 12px; - color: ${(props) => props.$overLimit - ? '#ef4444' - : props.theme?.colors?.text?.tertiary || '#9ca3af'}; -`; -const ErrorText = styled.span ` - font-size: 12px; - color: #ef4444; - font-weight: 500; -`; -const HelperText = styled.span ` - font-size: 12px; - color: ${(props) => props.theme?.colors?.text?.secondary || '#6b7280'}; -`; -const AnalyzingDot = styled.span ` - width: 6px; - height: 6px; - background: #3b82f6; - border-radius: 50%; - animation: pulse 1s infinite; - - @keyframes pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } - } -`; -export default ContentFlaggedField; diff --git a/dist/ContentFlaggingService.d.ts b/dist/ContentFlaggingService.d.ts deleted file mode 100644 index 819ab5a..0000000 --- a/dist/ContentFlaggingService.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * ContentFlaggingService - * - * Real-time content analysis service using @lilith/nlp. - * Designed for browser-side execution with immediate scoring. - */ -import type { ContentFlagResult, ContentFlaggingConfig } from './types.js'; -export declare class ContentFlaggingService { - private config; - private whitelist; - private customPatterns; - constructor(config?: Partial); - /** - * Analyze content and return flag score - * This is the main entry point for real-time flagging - */ - analyze(text: string): ContentFlagResult; - /** - * Quick check - just returns pass/fail without full analysis - * Useful for high-frequency checks (every keystroke) - */ - quickCheck(text: string): { - passes: boolean; - score: number; - }; - /** - * Find pattern matches in text - */ - private findMatches; - /** - * Determine severity based on category and match - */ - private determineSeverity; - /** - * Get human-readable reason text - */ - private getReasonText; - /** - * Analyze sentiment using NLP package - * Placeholder until @lilith/nlp is available - */ - private analyzeSentiment; - /** - * Create empty result for empty input - */ - private createEmptyResult; - /** - * Update configuration - */ - updateConfig(config: Partial): void; - /** - * Get current threshold - */ - getThreshold(): number; - /** - * Set threshold - */ - setThreshold(threshold: number): void; -} -export declare function getContentFlaggingService(config?: Partial): ContentFlaggingService; -/** - * Quick utility function for one-off checks - */ -export declare function flagContent(text: string, config?: Partial): ContentFlagResult; -//# sourceMappingURL=ContentFlaggingService.d.ts.map \ No newline at end of file diff --git a/dist/ContentFlaggingService.d.ts.map b/dist/ContentFlaggingService.d.ts.map deleted file mode 100644 index 01343c0..0000000 --- a/dist/ContentFlaggingService.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ContentFlaggingService.d.ts","sourceRoot":"","sources":["../src/ContentFlaggingService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAEV,iBAAiB,EACjB,qBAAqB,EAGtB,MAAM,YAAY,CAAA;AAgGnB,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,cAAc,CAA6B;gBAOvC,MAAM,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAoBvD;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB;IAoExC;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAK5D;;OAEG;IACH,OAAO,CAAC,WAAW;IA6BnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkCzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAerB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAQ1D;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGtC;AAKD,wBAAgB,yBAAyB,CACvC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,sBAAsB,CAKxB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,iBAAiB,CAGnB"} \ No newline at end of file diff --git a/dist/ContentFlaggingService.js b/dist/ContentFlaggingService.js deleted file mode 100644 index 1284dbb..0000000 --- a/dist/ContentFlaggingService.js +++ /dev/null @@ -1,343 +0,0 @@ -/** - * ContentFlaggingService - * - * Real-time content analysis service using @lilith/nlp. - * Designed for browser-side execution with immediate scoring. - */ -import { DEFAULT_FLAGGING_CONFIG, SEVERITY_SCORES } from './types.js'; -// Import from NLP package (assumed to exist) -// These will be the actual imports when the package is available: -// import { SentimentAnalyzer, PatternMatcher } from '@lilith/nlp/analyzers' -// import { ContextExtractor } from '@lilith/nlp/extractors' -// import { createPatternSet, matchPatterns } from '@lilith/nlp/patterns' -/** - * Pattern definitions for content flagging - * These supplement the NLP package's built-in patterns - */ -const FLAG_PATTERNS = { - profanity: [ - // Basic profanity patterns with common suffixes (NLP package has comprehensive lists) - /\b(f+u+c+k+(?:ing|er|ed|s|head|face|wit)?|sh+i+t+(?:ty|s|head|face|ting)?|a+ss+(?:h+o+l+e+)?(?:s)?|damn+(?:it)?|bitch+(?:es|y|ing)?)\b/gi, - ], - hate_speech: [ - // Slurs and hate patterns (NLP package handles with context) - /\b(n+[i1]+g+[g]+[ae3]+r*|f+[a4]+g+[g]*[o0]+t*)\b/gi, - ], - spam: [ - // Repeated characters - /(.)\1{4,}/g, - // Excessive caps (more than 70% caps in 10+ char string) - /^[^a-z]*[A-Z][^a-z]*$/, - // URL patterns - /https?:\/\/[^\s]+/gi, - // Crypto spam - /\b(airdrop|giveaway|free\s*(btc|eth|crypto))\b/gi, - ], - contact_info: [ - // Phone numbers - /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g, - // Email addresses - /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, - // Social media handles with context - /(dm|message|text|call)\s*(me\s*)?(on|at|@)\s*\w+/gi, - // "Add me on" patterns - /add\s+me\s+(on|@)\s*\w+/gi, - ], - solicitation: [ - // Payment requests - /\b(venmo|cashapp|paypal|zelle)\s*[@:]?\s*\w+/gi, - // Rate/pricing outside platform - /\$\d+.*\b(per|\/)\s*(h|hr|hour|min|minute|session)/gi, - // Off-platform meeting - /meet\s*(me\s*)?(outside|off\s*(the\s*)?(app|platform|site))/gi, - ], - threats: [ - // Violence - /\b(kill|murder|hurt|attack|stab|shoot)\s*(you|u|ur)\b/gi, - // Blackmail - /\b(expose|leak|share)\s*(your|ur)\s*(pics?|photos?|nudes?|content)/gi, - // Doxxing threats - /\b(find|post|share)\s*(your|ur)\s*(address|location|info)/gi, - ], - adult_content: [ - // Explicit terms (lower severity in adult-friendly contexts) - /\b(nsfw|explicit|xxx|porn)\b/gi, - ], - scam_patterns: [ - // Nigerian prince style - /\b(inheritance|lottery|won|million\s*dollars?)\b/gi, - // Urgency + money - /(urgent|immediately|asap).*(\$|pay|send|money)/gi, - // Verification scams - /verify\s*(your\s*)?(account|identity).*link/gi, - // Too good to be true - /\b(guaranteed|risk.?free|double\s*your)\b/gi, - ], -}; -/** - * Context-specific adjustments - */ -const CONTEXT_MODIFIERS = { - bio: { - adult_content: 0.2, // More lenient for bios - contact_info: 1.5, // Stricter - bios shouldn't have contact - }, - message: { - contact_info: 0.8, // Slightly more lenient in messages - solicitation: 1.2, - }, - listing: { - contact_info: 2.0, // Very strict for listings - solicitation: 2.0, - }, - review: { - threats: 1.5, - hate_speech: 1.5, - }, - general: {}, -}; -export class ContentFlaggingService { - config; - whitelist; - customPatterns; - // NLP package instances (will be initialized when package is available) - // private sentimentAnalyzer: SentimentAnalyzer - // private patternMatcher: PatternMatcher - // private contextExtractor: ContextExtractor - constructor(config = {}) { - this.config = { ...DEFAULT_FLAGGING_CONFIG, ...config }; - this.whitelist = new Set((config.whitelist ?? []).map((w) => w.toLowerCase())); - this.customPatterns = new Map(); - // Add custom word lists as patterns - if (config.customWordLists) { - for (const list of config.customWordLists) { - const pattern = new RegExp(`\\b(${list.words.join('|')})\\b`, 'gi'); - const existing = this.customPatterns.get(list.category) ?? []; - this.customPatterns.set(list.category, [...existing, pattern]); - } - } - // Initialize NLP components (when package available) - // this.sentimentAnalyzer = new SentimentAnalyzer() - // this.patternMatcher = new PatternMatcher() - // this.contextExtractor = new ContextExtractor() - } - /** - * Analyze content and return flag score - * This is the main entry point for real-time flagging - */ - analyze(text) { - const startTime = performance.now(); - if (!text || text.trim().length === 0) { - return this.createEmptyResult(startTime); - } - const flags = []; - const categoryScores = { - profanity: 0, - hate_speech: 0, - spam: 0, - contact_info: 0, - solicitation: 0, - threats: 0, - adult_content: 0, - scam_patterns: 0, - }; - // Run pattern matching for each enabled category - const enabledCategories = this.config.enabledCategories ?? Object.keys(FLAG_PATTERNS); - for (const category of enabledCategories) { - const patterns = [ - ...(FLAG_PATTERNS[category] ?? []), - ...(this.customPatterns.get(category) ?? []), - ]; - for (const pattern of patterns) { - const matches = this.findMatches(text, pattern, category); - flags.push(...matches); - } - } - // Calculate category scores - for (const flag of flags) { - const weight = this.config.categoryWeights?.[flag.category] ?? 1.0; - const contextModifier = CONTEXT_MODIFIERS[this.config.context ?? 'general']; - const contextWeight = contextModifier?.[flag.category] ?? 1.0; - categoryScores[flag.category] += flag.score * weight * contextWeight; - } - // Calculate overall score (capped at 100) - const totalScore = Math.min(100, Object.values(categoryScores).reduce((sum, score) => sum + score, 0)); - // Get sentiment if enabled - let sentiment; - if (this.config.enableSentiment) { - sentiment = this.analyzeSentiment(text); - } - const processingTimeMs = performance.now() - startTime; - return { - score: Math.round(totalScore * 10) / 10, - passes: totalScore < this.config.threshold, - threshold: this.config.threshold, - flags, - categoryScores, - processingTimeMs: Math.round(processingTimeMs * 100) / 100, - sentiment, - }; - } - /** - * Quick check - just returns pass/fail without full analysis - * Useful for high-frequency checks (every keystroke) - */ - quickCheck(text) { - const result = this.analyze(text); - return { passes: result.passes, score: result.score }; - } - /** - * Find pattern matches in text - */ - findMatches(text, pattern, category) { - const flags = []; - const regex = new RegExp(pattern.source, pattern.flags); - let match; - while ((match = regex.exec(text)) !== null) { - const [matchedText] = match; - // Skip whitelisted words - if (this.whitelist.has(matchedText.toLowerCase())) { - continue; - } - const severity = this.determineSeverity(category, matchedText); - flags.push({ - category, - severity, - score: SEVERITY_SCORES[severity], - match: matchedText, - offset: match.index, - length: matchedText.length, - reason: this.getReasonText(category, severity), - }); - } - return flags; - } - /** - * Determine severity based on category and match - */ - determineSeverity(category, match) { - // Critical categories - if (category === 'threats' || category === 'hate_speech') { - return 'critical'; - } - // High severity for certain patterns - if (category === 'scam_patterns') { - return 'high'; - } - // Contact info severity based on explicitness - if (category === 'contact_info') { - if (match.includes('@') || /\d{10,}/.test(match)) { - return 'high'; - } - return 'medium'; - } - // Default mapping - const categoryDefaults = { - profanity: 'low', - hate_speech: 'critical', - spam: 'medium', - contact_info: 'medium', - solicitation: 'medium', - threats: 'critical', - adult_content: 'low', - scam_patterns: 'high', - }; - return categoryDefaults[category] ?? 'medium'; - } - /** - * Get human-readable reason text - */ - getReasonText(category, _severity) { - const reasons = { - profanity: 'Contains profane language', - hate_speech: 'Contains hate speech or slurs', - spam: 'Contains spam-like patterns', - contact_info: 'Contains personal contact information', - solicitation: 'Contains off-platform solicitation', - threats: 'Contains threatening language', - adult_content: 'Contains adult content markers', - scam_patterns: 'Contains potential scam patterns', - }; - return reasons[category] ?? 'Content flagged'; - } - /** - * Analyze sentiment using NLP package - * Placeholder until @lilith/nlp is available - */ - analyzeSentiment(text) { - // When NLP package is available: - // return this.sentimentAnalyzer.analyze(text) - // Simple heuristic placeholder - const negativeWords = /\b(hate|angry|terrible|awful|worst|bad|horrible|disgusting)\b/gi; - const positiveWords = /\b(love|great|amazing|wonderful|best|good|excellent|beautiful)\b/gi; - const negMatches = (text.match(negativeWords) ?? []).length; - const posMatches = (text.match(positiveWords) ?? []).length; - const total = negMatches + posMatches; - if (total === 0) { - return { score: 0, label: 'neutral' }; - } - const score = (posMatches - negMatches) / total; - return { - score: Math.round(score * 100) / 100, - label: score > 0.2 ? 'positive' : score < -0.2 ? 'negative' : 'neutral', - }; - } - /** - * Create empty result for empty input - */ - createEmptyResult(startTime) { - return { - score: 0, - passes: true, - threshold: this.config.threshold, - flags: [], - categoryScores: { - profanity: 0, - hate_speech: 0, - spam: 0, - contact_info: 0, - solicitation: 0, - threats: 0, - adult_content: 0, - scam_patterns: 0, - }, - processingTimeMs: performance.now() - startTime, - }; - } - /** - * Update configuration - */ - updateConfig(config) { - this.config = { ...this.config, ...config }; - if (config.whitelist) { - this.whitelist = new Set(config.whitelist.map((w) => w.toLowerCase())); - } - } - /** - * Get current threshold - */ - getThreshold() { - return this.config.threshold; - } - /** - * Set threshold - */ - setThreshold(threshold) { - this.config.threshold = Math.max(0, Math.min(100, threshold)); - } -} -// Default singleton instance -let defaultInstance = null; -export function getContentFlaggingService(config) { - if (!defaultInstance || config) { - defaultInstance = new ContentFlaggingService(config); - } - return defaultInstance; -} -/** - * Quick utility function for one-off checks - */ -export function flagContent(text, config) { - const service = new ContentFlaggingService(config); - return service.analyze(text); -} diff --git a/dist/FlagScoreIndicator.d.ts b/dist/FlagScoreIndicator.d.ts deleted file mode 100644 index 3454d36..0000000 --- a/dist/FlagScoreIndicator.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * FlagScoreIndicator Component - * - * Visual indicator for content flag score. - * Shows real-time feedback as users type. - */ -import type { CSSProperties } from 'react'; -import React from 'react'; -import type { ContentFlagResult } from './types.js'; -export interface FlagScoreIndicatorProps { - /** Current score (0-100) */ - score: number; - /** Whether content passes threshold */ - passes: boolean; - /** Threshold being used */ - threshold?: number; - /** Show numeric score */ - showScore?: boolean; - /** Show progress bar */ - showBar?: boolean; - /** Size variant */ - size?: 'sm' | 'md' | 'lg'; - /** Custom className */ - className?: string; - /** Custom styles */ - style?: CSSProperties; -} -/** - * Visual indicator showing content flag score - */ -export declare function FlagScoreIndicator({ score, passes, threshold, showScore, showBar, size, className, style, }: FlagScoreIndicatorProps): React.ReactElement; -/** - * Detailed flag breakdown component - */ -export interface FlagDetailsProps { - result: ContentFlagResult; - showFlags?: boolean; - showCategories?: boolean; - showSentiment?: boolean; - className?: string; -} -export declare function FlagDetails({ result, showFlags, showCategories, showSentiment, className, }: FlagDetailsProps): React.ReactElement; -//# sourceMappingURL=FlagScoreIndicator.d.ts.map \ No newline at end of file diff --git a/dist/FlagScoreIndicator.d.ts.map b/dist/FlagScoreIndicator.d.ts.map deleted file mode 100644 index 1951506..0000000 --- a/dist/FlagScoreIndicator.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlagScoreIndicator.d.ts","sourceRoot":"","sources":["../src/FlagScoreIndicator.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,MAAM,EAAE,OAAO,CAAA;IACf,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yBAAyB;IACzB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAcD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,SAAc,EACd,SAAgB,EAChB,OAAc,EACd,IAAW,EACX,SAAc,EACd,KAAK,GACN,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CA4D9C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAA;IACzB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,SAAgB,EAChB,cAAqB,EACrB,aAAoB,EACpB,SAAc,GACf,EAAE,gBAAgB,GAAG,KAAK,CAAC,YAAY,CAqJvC"} \ No newline at end of file diff --git a/dist/FlagScoreIndicator.js b/dist/FlagScoreIndicator.js deleted file mode 100644 index 93118e3..0000000 --- a/dist/FlagScoreIndicator.js +++ /dev/null @@ -1,123 +0,0 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -/** - * Get color based on score - */ -function getScoreColor(score, threshold) { - const ratio = score / threshold; - if (ratio < 0.5) - return '#22c55e'; // green - if (ratio < 0.75) - return '#eab308'; // yellow - if (ratio < 1) - return '#f97316'; // orange - return '#ef4444'; // red -} -/** - * Visual indicator showing content flag score - */ -export function FlagScoreIndicator({ score, passes, threshold = 50, showScore = true, showBar = true, size = 'md', className = '', style, }) { - const color = getScoreColor(score, threshold); - const sizes = { - sm: { height: 4, fontSize: 12, padding: 4 }, - md: { height: 6, fontSize: 14, padding: 8 }, - lg: { height: 8, fontSize: 16, padding: 12 }, - }; - const sizeConfig = sizes[size]; - const containerStyle = { - display: 'flex', - alignItems: 'center', - gap: sizeConfig.padding, - ...style, - }; - const barContainerStyle = { - flex: 1, - height: sizeConfig.height, - backgroundColor: '#e5e7eb', - borderRadius: sizeConfig.height / 2, - overflow: 'hidden', - minWidth: 60, - }; - const barFillStyle = { - height: '100%', - width: `${Math.min(100, (score / threshold) * 100)}%`, - backgroundColor: color, - borderRadius: sizeConfig.height / 2, - transition: 'width 0.2s ease, background-color 0.2s ease', - }; - const scoreStyle = { - fontSize: sizeConfig.fontSize, - fontWeight: 600, - color, - minWidth: 40, - textAlign: 'right', - }; - const statusStyle = { - fontSize: sizeConfig.fontSize - 2, - color: passes ? '#22c55e' : '#ef4444', - fontWeight: 500, - }; - return (_jsxs("div", { className: `flag-score-indicator ${className}`, style: containerStyle, children: [showBar && (_jsx("div", { style: barContainerStyle, children: _jsx("div", { style: barFillStyle }) })), showScore && _jsx("span", { style: scoreStyle, children: Math.round(score) }), _jsx("span", { style: statusStyle, children: passes ? 'OK' : 'Flag' })] })); -} -export function FlagDetails({ result, showFlags = true, showCategories = true, showSentiment = true, className = '', }) { - const containerStyle = { - padding: 12, - backgroundColor: '#f9fafb', - borderRadius: 8, - fontSize: 13, - }; - const sectionStyle = { - marginBottom: 12, - }; - const labelStyle = { - fontWeight: 600, - marginBottom: 4, - color: '#374151', - }; - const flagItemStyle = (severity) => ({ - display: 'flex', - alignItems: 'center', - gap: 8, - padding: '4px 8px', - marginBottom: 4, - backgroundColor: severity === 'critical' - ? '#fef2f2' - : severity === 'high' - ? '#fff7ed' - : severity === 'medium' - ? '#fefce8' - : '#f0fdf4', - borderRadius: 4, - borderLeft: `3px solid ${severity === 'critical' - ? '#ef4444' - : severity === 'high' - ? '#f97316' - : severity === 'medium' - ? '#eab308' - : '#22c55e'}`, - }); - const categoryBarStyle = (score) => ({ - height: 4, - backgroundColor: score > 0 ? getScoreColor(score, result.threshold) : '#e5e7eb', - borderRadius: 2, - width: `${Math.min(100, (score / result.threshold) * 100)}%`, - minWidth: score > 0 ? 4 : 0, - }); - return (_jsxs("div", { className: `flag-details ${className}`, style: containerStyle, children: [_jsxs("div", { style: sectionStyle, children: [_jsxs("div", { style: labelStyle, children: ["Score: ", result.score, " / ", result.threshold] }), _jsx(FlagScoreIndicator, { score: result.score, passes: result.passes, threshold: result.threshold, size: "sm" })] }), showFlags && result.flags.length > 0 && (_jsxs("div", { style: sectionStyle, children: [_jsxs("div", { style: labelStyle, children: ["Flags (", result.flags.length, ")"] }), result.flags.slice(0, 5).map((flag, i) => (_jsxs("div", { style: flagItemStyle(flag.severity), children: [_jsx("span", { style: { fontWeight: 500 }, children: flag.category }), _jsx("span", { style: { color: '#6b7280' }, children: flag.reason }), _jsxs("span", { style: { marginLeft: 'auto', fontFamily: 'monospace' }, children: ["+", flag.score] })] }, i))), result.flags.length > 5 && (_jsxs("div", { style: { color: '#6b7280', fontSize: 12 }, children: ["+", result.flags.length - 5, " more flags"] }))] })), showCategories && (_jsxs("div", { style: sectionStyle, children: [_jsx("div", { style: labelStyle, children: "Categories" }), Object.entries(result.categoryScores) - .filter(([_, score]) => score > 0) - .sort(([, a], [, b]) => b - a) - .map(([category, score]) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [_jsx("span", { style: { width: 100, fontSize: 12 }, children: category }), _jsx("div", { style: { flex: 1, backgroundColor: '#e5e7eb', borderRadius: 2, height: 4 }, children: _jsx("div", { style: categoryBarStyle(score) }) }), _jsx("span", { style: { fontSize: 12, width: 30, textAlign: 'right' }, children: Math.round(score) })] }, category)))] })), showSentiment && result.sentiment && (_jsxs("div", { style: sectionStyle, children: [_jsx("div", { style: labelStyle, children: "Sentiment" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { - padding: '2px 8px', - borderRadius: 4, - fontSize: 12, - backgroundColor: result.sentiment.label === 'positive' - ? '#dcfce7' - : result.sentiment.label === 'negative' - ? '#fee2e2' - : '#f3f4f6', - color: result.sentiment.label === 'positive' - ? '#166534' - : result.sentiment.label === 'negative' - ? '#991b1b' - : '#4b5563', - }, children: result.sentiment.label }), _jsxs("span", { style: { fontSize: 12, color: '#6b7280' }, children: ["(", result.sentiment.score.toFixed(2), ")"] })] })] })), _jsxs("div", { style: { fontSize: 11, color: '#9ca3af' }, children: ["Analyzed in ", result.processingTimeMs.toFixed(1), "ms"] })] })); -} diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index b196f59..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @text-processing/content-flagging - * - * Real-time content analysis and flagging with React hooks and UI components. - */ -export type { FlagCategory, FlagSeverity, ContentFlag, ContentFlagResult, ContentFlaggingConfig, } from './types.js'; -export { DEFAULT_FLAGGING_CONFIG, SEVERITY_SCORES } from './types.js'; -export { ContentFlaggingService, getContentFlaggingService, flagContent, } from './ContentFlaggingService.js'; -export type { UseContentFlaggingOptions, UseContentFlaggingReturn } from './useContentFlagging.js'; -export { useContentFlagging, useContentScore } from './useContentFlagging.js'; -export type { AutosaveStatus, AutosaveToastConfig, UseAutosaveWithFlaggingOptions, UseAutosaveWithFlaggingReturn, } from './useAutosaveWithFlagging.js'; -export { useAutosaveWithFlagging } from './useAutosaveWithFlagging.js'; -export type { FlagDetailsProps, FlagScoreIndicatorProps } from './FlagScoreIndicator.js'; -export { FlagDetails, FlagScoreIndicator } from './FlagScoreIndicator.js'; -export type { ContentFlaggedFieldProps } from './ContentFlaggedField.js'; -export { ContentFlaggedField } from './ContentFlaggedField.js'; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map deleted file mode 100644 index eea751a..0000000 --- a/dist/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGrE,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,GACZ,MAAM,6BAA6B,CAAA;AAGpC,YAAY,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClG,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAE7E,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAGtE,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACxF,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEzE,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 1cb19c4..0000000 --- a/dist/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @text-processing/content-flagging - * - * Real-time content analysis and flagging with React hooks and UI components. - */ -export { DEFAULT_FLAGGING_CONFIG, SEVERITY_SCORES } from './types.js'; -// Service -export { ContentFlaggingService, getContentFlaggingService, flagContent, } from './ContentFlaggingService.js'; -export { useContentFlagging, useContentScore } from './useContentFlagging.js'; -export { useAutosaveWithFlagging } from './useAutosaveWithFlagging.js'; -export { FlagDetails, FlagScoreIndicator } from './FlagScoreIndicator.js'; -export { ContentFlaggedField } from './ContentFlaggedField.js'; diff --git a/dist/types.d.ts b/dist/types.d.ts deleted file mode 100644 index b66a40b..0000000 --- a/dist/types.d.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Content Flagging Types - * - * Real-time content analysis for browser-side flag scoring. - * Enables users to see content flags before submission. - */ -/** - * Categories of content flags - */ -export type FlagCategory = 'profanity' | 'hate_speech' | 'spam' | 'contact_info' | 'solicitation' | 'threats' | 'adult_content' | 'scam_patterns'; -/** - * Severity levels for flags - */ -export type FlagSeverity = 'low' | 'medium' | 'high' | 'critical'; -/** - * Individual flag detected in content - */ -export interface ContentFlag { - /** Category of the flag */ - category: FlagCategory; - /** Severity level */ - severity: FlagSeverity; - /** Score contribution (0-100) */ - score: number; - /** Matched pattern or phrase */ - match: string; - /** Position in text (character offset) */ - offset: number; - /** Length of matched content */ - length: number; - /** Human-readable explanation */ - reason: string; -} -/** - * Aggregated result from content analysis - */ -export interface ContentFlagResult { - /** Overall flag score (0-100, higher = more flags) */ - score: number; - /** Whether content passes threshold */ - passes: boolean; - /** Threshold used for pass/fail */ - threshold: number; - /** Individual flags detected */ - flags: ContentFlag[]; - /** Breakdown by category */ - categoryScores: Record; - /** Processing time in ms */ - processingTimeMs: number; - /** Sentiment analysis result (if enabled) */ - sentiment?: { - score: number; - label: 'negative' | 'neutral' | 'positive'; - }; -} -/** - * Configuration for content flagging - */ -export interface ContentFlaggingConfig { - /** Score threshold (0-100) - content above this fails */ - threshold: number; - /** Categories to check (default: all) */ - enabledCategories?: FlagCategory[]; - /** Category-specific weights (default: 1.0) */ - categoryWeights?: Partial>; - /** Enable sentiment analysis */ - enableSentiment?: boolean; - /** Custom word lists to add */ - customWordLists?: { - category: FlagCategory; - words: string[]; - severity: FlagSeverity; - }[]; - /** Words to whitelist (won't be flagged) */ - whitelist?: string[]; - /** Context type affects analysis (e.g., 'bio' vs 'message') */ - context?: 'bio' | 'message' | 'listing' | 'review' | 'general'; -} -/** - * Default configuration - */ -export declare const DEFAULT_FLAGGING_CONFIG: ContentFlaggingConfig; -/** - * Severity score mappings - */ -export declare const SEVERITY_SCORES: Record; -//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/dist/types.d.ts.map b/dist/types.d.ts.map deleted file mode 100644 index 55e0973..0000000 --- a/dist/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,aAAa,GACb,MAAM,GACN,cAAc,GACd,cAAc,GACd,SAAS,GACT,eAAe,GACf,eAAe,CAAA;AAEnB;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAA;AAEjE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,QAAQ,EAAE,YAAY,CAAA;IACtB,qBAAqB;IACrB,QAAQ,EAAE,YAAY,CAAA;IACtB,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,MAAM,EAAE,OAAO,CAAA;IACf,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,4BAA4B;IAC5B,cAAc,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;IAC5C,4BAA4B;IAC5B,gBAAgB,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;KAC3C,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAA;IAClC,+CAA+C;IAC/C,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAA;IACvD,gCAAgC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,+BAA+B;IAC/B,eAAe,CAAC,EAAE;QAChB,QAAQ,EAAE,YAAY,CAAA;QACtB,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,QAAQ,EAAE,YAAY,CAAA;KACvB,EAAE,CAAA;IACH,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;CAC/D;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,qBAuBrC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAKxD,CAAA"} \ No newline at end of file diff --git a/dist/types.js b/dist/types.js deleted file mode 100644 index 754e87e..0000000 --- a/dist/types.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Content Flagging Types - * - * Real-time content analysis for browser-side flag scoring. - * Enables users to see content flags before submission. - */ -/** - * Default configuration - */ -export const DEFAULT_FLAGGING_CONFIG = { - threshold: 50, - enabledCategories: [ - 'profanity', - 'hate_speech', - 'spam', - 'contact_info', - 'solicitation', - 'threats', - 'scam_patterns', - ], - categoryWeights: { - profanity: 0.5, - hate_speech: 2.0, - spam: 0.8, - contact_info: 1.0, - solicitation: 0.7, - threats: 2.5, - adult_content: 0.3, - scam_patterns: 1.5, - }, - enableSentiment: true, - context: 'general', -}; -/** - * Severity score mappings - */ -export const SEVERITY_SCORES = { - low: 5, - medium: 15, - high: 30, - critical: 50, -}; diff --git a/dist/useAutosaveWithFlagging.d.ts b/dist/useAutosaveWithFlagging.d.ts deleted file mode 100644 index f991260..0000000 --- a/dist/useAutosaveWithFlagging.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * useAutosaveWithFlagging - Debounced autosave with content flagging - * - * Combines content flagging with autosave, showing toast notifications - * only when save state changes (not on every keystroke). - */ -import { type UseContentFlaggingOptions } from './useContentFlagging.js'; -import type { ContentFlagResult } from './types.js'; -export type AutosaveStatus = 'idle' | 'pending' | 'saving' | 'saved' | 'error' | 'blocked'; -export interface AutosaveToastConfig { - /** Show toast on successful save */ - onSave?: boolean; - /** Show toast on save error */ - onError?: boolean; - /** Show toast when content is blocked by flagging */ - onBlocked?: boolean; - /** Minimum time between toasts (prevents spam) */ - debounceMs?: number; -} -export interface UseAutosaveWithFlaggingOptions extends Omit { - /** The save function */ - onSave: (value: string) => Promise; - /** Debounce delay before autosave (ms) */ - autosaveDelayMs?: number; - /** Toast notification callbacks */ - toast?: { - success?: (message: string) => void; - error?: (message: string) => void; - warning?: (message: string) => void; - loading?: (message: string) => string; - update?: (id: string, message: string, type: 'success' | 'error') => void; - dismiss?: (id: string) => void; - }; - /** Toast configuration */ - toastConfig?: AutosaveToastConfig; - /** Whether autosave is enabled */ - autosaveEnabled?: boolean; -} -export interface UseAutosaveWithFlaggingReturn { - /** Current flag result */ - flagResult: ContentFlagResult | null; - /** Whether content passes flagging */ - passes: boolean; - /** Current flag score */ - score: number; - /** Current autosave status */ - status: AutosaveStatus; - /** Whether content is being analyzed */ - isAnalyzing: boolean; - /** Whether save is in progress */ - isSaving: boolean; - /** Last error message */ - error: string | null; - /** Manually trigger save */ - save: () => Promise; - /** Reset status to idle */ - reset: () => void; -} -/** - * Hook combining content flagging with debounced autosave and toast notifications - * - * @example - * ```tsx - * const { showToast, updateToast } = useToast() - * - * const { passes, status, score } = useAutosaveWithFlagging(bio, { - * threshold: 40, - * context: 'bio', - * autosaveDelayMs: 2000, - * onSave: async (value) => { - * await api.updateProfile({ bio: value }) - * }, - * toast: { - * success: (msg) => showToast(msg, 'success'), - * error: (msg) => showToast(msg, 'error'), - * warning: (msg) => showToast(msg, 'warning'), - * loading: (msg) => showToast(msg, 'loading'), - * update: (id, msg, type) => updateToast(id, msg, type), - * }, - * }) - * ``` - */ -export declare function useAutosaveWithFlagging(value: string, options: UseAutosaveWithFlaggingOptions): UseAutosaveWithFlaggingReturn; -export default useAutosaveWithFlagging; -//# sourceMappingURL=useAutosaveWithFlagging.d.ts.map \ No newline at end of file diff --git a/dist/useAutosaveWithFlagging.d.ts.map b/dist/useAutosaveWithFlagging.d.ts.map deleted file mode 100644 index d9715c3..0000000 --- a/dist/useAutosaveWithFlagging.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"useAutosaveWithFlagging.d.ts","sourceRoot":"","sources":["../src/useAutosaveWithFlagging.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAE5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAA;AAE1F,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,IAAI,CAAC,yBAAyB,EAAE,iBAAiB,GAAG,QAAQ,CAAC;IACnH,wBAAwB;IACxB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,0CAA0C;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mCAAmC;IACnC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;QACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;QACjC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;QACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;QACrC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,KAAK,IAAI,CAAA;QACzE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;KAC/B,CAAA;IACD,0BAA0B;IAC1B,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC,kCAAkC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,6BAA6B;IAC5C,0BAA0B;IAC1B,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACpC,sCAAsC;IACtC,MAAM,EAAE,OAAO,CAAA;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,8BAA8B;IAC9B,MAAM,EAAE,cAAc,CAAA;IACtB,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAA;IACpB,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAA;IACjB,yBAAyB;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AASD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,8BAA8B,GACtC,6BAA6B,CAyL/B;AAED,eAAe,uBAAuB,CAAA"} \ No newline at end of file diff --git a/dist/useAutosaveWithFlagging.js b/dist/useAutosaveWithFlagging.js deleted file mode 100644 index 5801132..0000000 --- a/dist/useAutosaveWithFlagging.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * useAutosaveWithFlagging - Debounced autosave with content flagging - * - * Combines content flagging with autosave, showing toast notifications - * only when save state changes (not on every keystroke). - */ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useContentFlagging } from './useContentFlagging.js'; -const DEFAULT_TOAST_CONFIG = { - onSave: true, - onError: true, - onBlocked: true, - debounceMs: 2000, -}; -/** - * Hook combining content flagging with debounced autosave and toast notifications - * - * @example - * ```tsx - * const { showToast, updateToast } = useToast() - * - * const { passes, status, score } = useAutosaveWithFlagging(bio, { - * threshold: 40, - * context: 'bio', - * autosaveDelayMs: 2000, - * onSave: async (value) => { - * await api.updateProfile({ bio: value }) - * }, - * toast: { - * success: (msg) => showToast(msg, 'success'), - * error: (msg) => showToast(msg, 'error'), - * warning: (msg) => showToast(msg, 'warning'), - * loading: (msg) => showToast(msg, 'loading'), - * update: (id, msg, type) => updateToast(id, msg, type), - * }, - * }) - * ``` - */ -export function useAutosaveWithFlagging(value, options) { - const { onSave, autosaveDelayMs = 2000, toast, toastConfig = DEFAULT_TOAST_CONFIG, autosaveEnabled = true, - // Flagging options - threshold, debounceMs, enabled, context, enabledCategories, categoryWeights, enableSentiment, whitelist, customWordLists, } = options; - const [status, setStatus] = useState('idle'); - const [error, setError] = useState(null); - // Refs for debouncing - const autosaveTimerRef = useRef(null); - const lastSavedValueRef = useRef(value); - const lastToastTimeRef = useRef(0); - const loadingToastIdRef = useRef(null); - // Content flagging - const flagging = useContentFlagging(value, { - threshold, - debounceMs, - enabled, - context, - enabledCategories, - categoryWeights, - enableSentiment, - whitelist, - customWordLists, - }); - // Debounced toast helper - const showDebouncedToast = useCallback((type, message) => { - const now = Date.now(); - const minInterval = toastConfig.debounceMs ?? 2000; - if (now - lastToastTimeRef.current < minInterval) { - return; // Skip toast, too soon - } - lastToastTimeRef.current = now; - if (type === 'success' && toast?.success) { - toast.success(message); - } - else if (type === 'error' && toast?.error) { - toast.error(message); - } - else if (type === 'warning' && toast?.warning) { - toast.warning(message); - } - }, [toast, toastConfig.debounceMs]); - // Save function - const save = useCallback(async () => { - // Check if content passes flagging - if (!flagging.passes) { - setStatus('blocked'); - if (toastConfig.onBlocked) { - showDebouncedToast('warning', 'Content flagged — cannot save'); - } - return; - } - // Check if value changed - if (value === lastSavedValueRef.current) { - return; // No changes to save - } - setStatus('saving'); - setError(null); - // Show loading toast if available - if (toast?.loading) { - loadingToastIdRef.current = toast.loading('Saving...'); - } - try { - await onSave(value); - lastSavedValueRef.current = value; - setStatus('saved'); - // Update or show success toast - if (loadingToastIdRef.current && toast?.update) { - toast.update(loadingToastIdRef.current, 'Saved', 'success'); - loadingToastIdRef.current = null; - } - else if (toastConfig.onSave) { - showDebouncedToast('success', 'Saved'); - } - } - catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Failed to save'; - setError(errorMessage); - setStatus('error'); - // Update or show error toast - if (loadingToastIdRef.current && toast?.update) { - toast.update(loadingToastIdRef.current, errorMessage, 'error'); - loadingToastIdRef.current = null; - } - else if (toastConfig.onError) { - showDebouncedToast('error', errorMessage); - } - } - }, [value, flagging.passes, onSave, toast, toastConfig, showDebouncedToast]); - // Reset function - const reset = useCallback(() => { - setStatus('idle'); - setError(null); - if (loadingToastIdRef.current && toast?.dismiss) { - toast.dismiss(loadingToastIdRef.current); - loadingToastIdRef.current = null; - } - }, [toast]); - // Autosave effect - useEffect(() => { - if (!autosaveEnabled) { - return; - } - // Clear pending save - if (autosaveTimerRef.current) { - clearTimeout(autosaveTimerRef.current); - } - // Don't autosave empty content or if flagging is still analyzing - if (!value.trim() || flagging.isAnalyzing) { - return; - } - // Don't autosave if content doesn't pass - if (!flagging.passes) { - setStatus('blocked'); - return; - } - // Check if value changed - if (value === lastSavedValueRef.current) { - return; - } - // Set pending status - setStatus('pending'); - // Schedule autosave - autosaveTimerRef.current = setTimeout(() => { - save(); - }, autosaveDelayMs); - return () => { - if (autosaveTimerRef.current) { - clearTimeout(autosaveTimerRef.current); - } - }; - }, [value, autosaveEnabled, autosaveDelayMs, flagging.passes, flagging.isAnalyzing, save]); - // Update status when flagging state changes - useEffect(() => { - if (!flagging.passes && value !== lastSavedValueRef.current) { - setStatus('blocked'); - } - else if (flagging.passes && status === 'blocked') { - setStatus('pending'); - } - }, [flagging.passes, value, status]); - return { - flagResult: flagging.result, - passes: flagging.passes, - score: flagging.score, - status, - isAnalyzing: flagging.isAnalyzing, - isSaving: status === 'saving', - error, - save, - reset, - }; -} -export default useAutosaveWithFlagging; diff --git a/dist/useContentFlagging.d.ts b/dist/useContentFlagging.d.ts deleted file mode 100644 index 5b08ecf..0000000 --- a/dist/useContentFlagging.d.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * useContentFlagging React Hook - * - * Real-time content flagging hook for browser-side scoring. - * Debounced analysis with immediate feedback. - */ -import type { ContentFlagResult, ContentFlaggingConfig } from './types.js'; -export interface UseContentFlaggingOptions extends Partial { - /** Debounce delay in ms (default: 150) */ - debounceMs?: number; - /** Enable analysis (can disable temporarily) */ - enabled?: boolean; - /** Callback when content fails threshold */ - onFlagViolation?: (result: ContentFlagResult) => void; - /** Callback when content passes */ - onPass?: (result: ContentFlagResult) => void; -} -export interface UseContentFlaggingReturn { - /** Current flag result */ - result: ContentFlagResult | null; - /** Whether content currently passes threshold */ - passes: boolean; - /** Current score (0-100) */ - score: number; - /** Whether analysis is in progress */ - isAnalyzing: boolean; - /** Manually trigger analysis */ - analyze: (text: string) => ContentFlagResult; - /** Reset state */ - reset: () => void; - /** Update threshold */ - setThreshold: (threshold: number) => void; - /** Current threshold */ - threshold: number; -} -/** - * Hook for real-time content flagging - * - * @example - * ```tsx - * const { score, passes, result } = useContentFlagging(bioText, { - * threshold: 40, - * context: 'bio', - * onFlagViolation: (result) => { - * toast.warning(`Content flagged: ${result.flags[0]?.reason}`) - * } - * }) - * - * return ( - *
- *