UNPKG

@syncfusion/ej2-pdfviewer

Version:
397 lines (396 loc) 18.6 kB
/** * @hidden * Builds filter predicates from a filter settings object. * * Returns two predicates: * 1. commentPredicate: For comment panel filtering (includes reply logic) * 2. documentPredicate: For document annotation filtering * * Both predicates are stateless, O(1) functions. * * @param {CommentFilterSettings} settings - Filter criteria * @returns {FilterPredicates} Object containing commentPredicate and documentPredicate */ export function buildFilterPredicates(settings) { /** * Helper: Map measurement (calibrate) annotation user-facing names to internal indent identifiers. * Bidirectional: handles both user-facing names and internal identifiers. * @param {string} value - The measurement type value * @returns {string} - The mapped internal identifier (indent) */ var mapMeasurementType = function (value) { // Handle case when object is received for Radius if (typeof value === 'object' && value !== null) { if (value.PolygonRadius) { return value.PolygonRadius; } } // Handle string cases if (typeof value === 'string') { switch (value) { case 'Distance': return 'LineDimension'; case 'Perimeter': return 'PolyLineDimension'; case 'Area': return 'PolygonDimension'; case 'Radius': return 'PolygonRadius'; case 'Volume': return 'PolygonVolume'; case 'Rectangle': return 'Square'; // Also handle reverse mapping (internal identifiers pass through) case 'LineDimension': case 'PolyLineDimension': case 'PolygonDimension': case 'PolygonRadius': case 'PolygonVolume': return value; default: return value; } } return String(value); }; /** * Helper: Pad a string with zeros to reach desired length. * Compatible with older ES versions. * @param {string} str - The string to pad * @param {number} length - The desired length * @returns {string} - The padded string */ var padWithZeros = function (str, length) { while (str.length < length) { str = '0' + str; } return str; }; /** * Helper: Normalize color values to hexadecimal format. * Converts rgba(), rgb(), and other formats to hex for comparison. * @param {any} colorValue - The color value in any format (may be string, null, undefined, etc.) * @returns {string} - The color value in hex format */ var normalizeColorToHex = function (colorValue) { // Type check: ensure colorValue is a string if (typeof colorValue !== 'string') { return ''; } if (!colorValue || colorValue === '') { return ''; } // Convert to string to be safe var colorStr = String(colorValue).trim(); // If already in hex format, return as is if (colorStr.indexOf('#') === 0) { if (colorStr.length === 9) { return colorStr.substring(0, 7).toLowerCase(); } return colorStr.toLowerCase(); } // Handle rgba format: rgba(255, 0, 0, 0.5) or rgba(255,0,0,0.5) // eslint-disable-next-line security/detect-unsafe-regex var rgbaMatch = colorStr.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/i); if (rgbaMatch) { var r = padWithZeros(parseInt(rgbaMatch[1], 10).toString(16), 2); var g = padWithZeros(parseInt(rgbaMatch[2], 10).toString(16), 2); var b = padWithZeros(parseInt(rgbaMatch[3], 10).toString(16), 2); return ('#' + r + g + b).toLowerCase(); } // If it's a named color or other format, try to return as-is lowercased return colorStr.toLowerCase(); }; /** * Helper: Get the annotation type using proper field lookup with fallback strategy. * Tries specific type fields first, then falls back to shapeAnnotationType. * Handles: Measurement, TextMarkup, FreeText, and Shape annotations. * Distinguishes between lines and arrows by checking lineHeadStart and lineHeadEnd. * @param {any} annotation - The annotation object * @returns {string} - The annotation type */ var getAnnotationType = function (annotation) { // PRIORITY ORDER: Check specific type fields, then fallback to shapeAnnotationType // 1. Check for Measurement/Calibrate annotations via indent field var indent = annotation.indent || annotation.Indent; if (indent) { // indent contains the internal identifier (e.g., "LineDimension") return indent; } // 2. Check for TextMarkup annotations (Highlight, Underline, Strikethrough) var textMarkupType = annotation.textMarkupAnnotationType || annotation.TextMarkupAnnotationType; if (textMarkupType) { return textMarkupType; } // 3. Check for FreeText annotations var freeTextType = annotation.freeTextAnnotationType || annotation.FreeTextAnnotationType; if (freeTextType) { return freeTextType; } // FALLBACK: Use shapeAnnotationType (catches Shape, Sticky, Stamp, etc.) var shapeType = annotation.shapeAnnotationType || annotation.ShapeAnnotationType || ''; // 4. Special handling for Arrow annotation: distinguish arrows from lines // Arrows have shapeAnnotationType === 'line' but with lineHeadStart/lineHeadEnd !== "none" if (shapeType === 'Line' && annotation.lineHeadStart !== 'None' && annotation.lineHeadEnd !== 'None') { return 'Arrow'; } return shapeType; }; /** * Helper: Check if annotation matches filter criteria (ignoring replies). * Supports multiple annotation type systems with proper field lookup. * Handles: Shape, Sticky, Stamp, TextMarkup, FreeText, and Measurement annotations. * Used by both predicates. * @param {any} annotation - The annotation object to evaluate * @returns {boolean} - Returns true if annotation matches filter criteria */ var matchesAnnotation = function (annotation) { // Type filter with proper annotation type retrieval if (settings.type && Array.isArray(settings.type) && settings.type.length > 0) { var annotType = getAnnotationType(annotation); // Check if the annotation type matches any of the filter types var typeMatches = false; for (var _i = 0, _a = settings.type; _i < _a.length; _i++) { var filterType = _a[_i]; // Map measurement type if needed (handles both user-facing and internal names) var mappedFilterType = mapMeasurementType(filterType); if (annotType === mappedFilterType || annotType === filterType) { typeMatches = true; break; } } if (!typeMatches) { return false; } } // Color filter with proper color normalization and field lookup if (settings.color && Array.isArray(settings.color) && settings.color.length > 0) { // Try to get color from various fields (different annotation types use different fields) var annotColorRaw = annotation.color || annotation.Color || annotation.strokeColor || annotation.StrokeColor || annotation.fillColor || annotation.FillColor || annotation.fontColor || annotation.FontColor || ''; // Normalize the annotation color to hex format var annotColor = normalizeColorToHex(annotColorRaw); // Normalize all filter colors to hex for comparison var normalizedFilterColors = settings.color.map(function (c) { return normalizeColorToHex(c); }); if (!annotColor) { return false; } // Check if the normalized annotation color matches any of the filter colors if (annotColor && normalizedFilterColors.indexOf(annotColor) === -1) { return false; } } // Status filter (can be single value or array) if (settings.status && Array.isArray(settings.status) && settings.status.length > 0) { var annotStatus = annotation.state || annotation.State || annotation.stateModel || annotation.StateModel || annotation.review.state || 'None'; // Handle both single status value and array of status values var statusMatches = false; if (Array.isArray(settings.status)) { // If status is an array, check if annotation status is in the array statusMatches = settings.status.indexOf(annotStatus) !== -1; } else { // If status is a single value, compare directly statusMatches = annotStatus === settings.status; } if (!statusMatches) { return false; } } // Author filter (parent only, not considering replies) if (settings.author && Array.isArray(settings.author) && settings.author.length > 0) { var annotAuthor = annotation.author || annotation.Author || ''; if (settings.author.indexOf(annotAuthor) === -1) { return false; } } // Modified date filter (collection-based, not range-based) if (settings.modifiedDate && Array.isArray(settings.modifiedDate) && settings.modifiedDate.length > 0) { var annotDate_1 = annotation.modifiedDate || annotation.ModifiedDate || ''; var annotDateOnly = new Date(annotDate_1).toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }); var match = settings.modifiedDate.some(function (d) { return new Date(d).toDateString() === new Date(annotDate_1).toDateString(); }); if (!match) { return false; } } return true; }; /** * Helper: Check if a reply (nested comment) matches thread-aware filter criteria. * Supports both lowercase and capitalized field names. * Checks author, status, and modifiedDate at reply level. * Used only when thread-aware filtering (author, status, or modifiedDate) is active. * @param {any} reply - The reply object to evaluate * @returns {boolean} - Returns true if the reply matches filter criteri */ var replyMatchesFilter = function (reply) { //AUTHOR filter if (settings.author && Array.isArray(settings.author) && settings.author.length > 0) { var replyAuthor = reply.author || reply.Author || ''; if (settings.author.indexOf(replyAuthor) === -1) { return false; } } //STATUS filter if (settings.status && Array.isArray(settings.status) && settings.status.length > 0) { var replyStatus = reply.state || reply.State || reply.stateModel || reply.StateModel || (reply.review && reply.review.state) || 'None'; if (settings.status.indexOf(replyStatus) === -1) { return false; } } //DATE filter if (settings.modifiedDate && Array.isArray(settings.modifiedDate) && settings.modifiedDate.length > 0) { var replyDate_1 = reply.modifiedDate || reply.ModifiedDate || ''; var replyDateOnly = new Date(replyDate_1).toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }); var match = settings.modifiedDate.some(function (d) { return new Date(d).toDateString() === new Date(replyDate_1).toDateString(); }); if (!match) { return false; } } return true; }; /** * Helper: Check if a thread (parent + replies) should be displayed. * Used only in comment panel (not document) when thread-aware filters are active. * Thread-aware filters: author, status, modifiedDate. * * Implements includeReplies logic: * - true: Show if parent matches OR any reply matches * - false: Show only if parent matches * * Supports both lowercase and capitalized Comments/comments field names. * @param {any} annotation - The parent annotation (thread root) to evaluate * @returns {boolean} - Returns true if the thread should be displayed */ var threadMatchesFilter = function (annotation) { //STRICT TYPE filter (annotation only) if (settings.type && Array.isArray(settings.type) && settings.type.length > 0) { var annotType = getAnnotationType(annotation); var typeMatch = false; for (var i = 0; i < settings.type.length; i++) { // eslint-disable-next-line security/detect-object-injection var mappedFilterType = mapMeasurementType(settings.type[i]); // eslint-disable-next-line security/detect-object-injection if (annotType === mappedFilterType || annotType === settings.type[i]) { typeMatch = true; break; } } if (!typeMatch) { return false; } } //STRICT COLOR filter (annotation only) if (settings.color && Array.isArray(settings.color) && settings.color.length > 0) { var annotColorRaw = annotation.color || annotation.Color || annotation.strokeColor || annotation.StrokeColor || annotation.fillColor || annotation.FillColor || annotation.fontColor || annotation.FontColor || ''; var annotColor = normalizeColorToHex(annotColorRaw); var normalizedFilterColors = settings.color.map(function (c) { return normalizeColorToHex(c); }); if (!annotColor || normalizedFilterColors.indexOf(annotColor) === -1) { return false; } } //STEP 3: check annotation-level filters (author/status/date) if (matchesAnnotation(annotation)) { return true; } // check replies (OR logic) var replies = annotation.comments || annotation.Comments || []; if (settings.includeReplies !== false && Array.isArray(replies)) { for (var i = 0; i < replies.length; i++) { // eslint-disable-next-line security/detect-object-injection var reply = replies[i]; if (replyMatchesFilter(reply)) { return true; } } } return false; }; /** * Helper: Check if any thread-aware filter is active. * Thread-aware filters are: author, status, and modifiedDate. * These filters can match against replies when includeReplies is enabled. * @returns {boolean} Returns true if any thread-aware filter is active */ var isThreadAwareFilterActive = function () { var hasAuthorFilter = settings.author && settings.author.length > 0; var hasStatusFilter = settings.status && Array.isArray(settings.status) && settings.status.length > 0; var hasModifiedDateFilter = settings.modifiedDate && settings.modifiedDate.length > 0; return !!(hasAuthorFilter || hasStatusFilter || hasModifiedDateFilter); }; /** * Predicate for comment panel (includes reply thread logic) * * When thread-aware filtering is active (author, status, or modifiedDate), * uses thread logic (parent + replies). * Otherwise, uses simple annotation matching. * * Thread-aware filters include replies in results when includeReplies is enabled: * - true: Show if parent matches OR any reply matches * - false: Show only if parent matches * @param {any} annotation - The annotation (thread root) to evaluate * @returns {boolean} - Returns true if the annotation should be shown in the comment panel */ var commentPredicate = function (annotation) { // If any thread-aware filter is active, use thread logic if (isThreadAwareFilterActive()) { return threadMatchesFilter(annotation); } // Otherwise, just match the annotation return matchesAnnotation(annotation); }; /** * Predicate for document annotations. * * When includeReplies is true and thread-aware filters are active, * use thread logic (parent + replies) to ensure consistency with comment panel. * Otherwise, use basic annotation matching. * @param {any} annotation - The annotation to evaluate * @returns {boolean} Returns true if the annotation should be include */ var documentPredicate = function (annotation) { // If includeReplies is enabled and thread-aware filters are active, // use thread logic to ensure document visibility matches comment panel if (settings.includeReplies !== false && isThreadAwareFilterActive()) { return threadMatchesFilter(annotation); } // Otherwise, just match the annotation return matchesAnnotation(annotation); }; return { commentPredicate: commentPredicate, documentPredicate: documentPredicate }; } /** * @hidden * Helper function to check if a filter is empty (no criteria set). * Used to determine if filtering is active. * * @param {CommentFilterSettings} settings - Filter settings to check * @returns {boolean} true if filter has no criteria, false otherwise */ export function isFilterEmpty(settings) { if (!settings) { return true; } return ((!settings.type || settings.type.length === 0) && (!settings.color || settings.color.length === 0) && (!settings.status || (Array.isArray(settings.status) && settings.status.length === 0)) && (!settings.author || settings.author.length === 0) && (!settings.modifiedDate || settings.modifiedDate.length === 0)); }