logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

iss_utils.js (5999B)


  1. import { sortBy } from 'lodash'
  2. // "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
  3. // into an array [item2, item3] for iterating
  4. export const unroll = (item) => {
  5. const out = []
  6. let currentParent = item
  7. while (currentParent) {
  8. out.push(currentParent)
  9. currentParent = currentParent.parent
  10. }
  11. return out
  12. }
  13. // This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
  14. // Can only accept primitives. Duplicates are not supported and can cause unexpected behavior
  15. export const getAllPossibleCombinations = (array) => {
  16. const combos = [array.map(x => [x])]
  17. for (let comboSize = 2; comboSize <= array.length; comboSize++) {
  18. const previous = combos[combos.length - 1]
  19. const newCombos = previous.map(self => {
  20. const selfSet = new Set()
  21. self.forEach(x => selfSet.add(x))
  22. const nonSelf = array.filter(x => !selfSet.has(x))
  23. return nonSelf.map(x => [...self, x])
  24. })
  25. const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
  26. const uniqueComboStrings = new Set()
  27. const uniqueCombos = flatCombos.map(sortBy).filter(x => {
  28. if (uniqueComboStrings.has(x.join())) {
  29. return false
  30. } else {
  31. uniqueComboStrings.add(x.join())
  32. return true
  33. }
  34. })
  35. combos.push(uniqueCombos)
  36. }
  37. return combos.reduce((acc, x) => [...acc, ...x], [])
  38. }
  39. /**
  40. * Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true)
  41. * selector.
  42. *
  43. * "path" here refers to "fake" selector that cannot be actually used in UI but is used for internal
  44. * purposes
  45. *
  46. * @param {Object} components - object containing all components definitions
  47. *
  48. * @returns {Function}
  49. * @param {Object} rule - rule in question to convert to CSS selector
  50. * @param {boolean} ignoreOutOfTreeSelector - wthether to ignore aformentioned field in
  51. * component definition and use selector
  52. * @param {boolean} isParent - (mostly) internal argument used when recursing
  53. *
  54. * @returns {String} CSS selector (or path)
  55. */
  56. export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
  57. if (!rule && !isParent) return null
  58. const component = components[rule.component]
  59. const { states = {}, variants = {}, selector, outOfTreeSelector } = component
  60. const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state])
  61. const applicableVariantName = (rule.variant || 'normal')
  62. let applicableVariant = ''
  63. if (applicableVariantName !== 'normal') {
  64. applicableVariant = variants[applicableVariantName]
  65. } else {
  66. applicableVariant = variants?.normal ?? ''
  67. }
  68. let realSelector
  69. if (selector === ':root') {
  70. realSelector = ''
  71. } else if (isParent) {
  72. realSelector = selector
  73. } else {
  74. if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
  75. else realSelector = selector
  76. }
  77. const selectors = [realSelector, applicableVariant, ...applicableStates]
  78. .sort((a, b) => {
  79. if (a.startsWith(':')) return 1
  80. if (/^[a-z]/.exec(a)) return -1
  81. else return 0
  82. })
  83. .join('')
  84. if (rule.parent) {
  85. return (genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
  86. }
  87. return selectors.trim()
  88. }
  89. /**
  90. * Check if combination matches
  91. *
  92. * @param {Object} criteria - criteria to match against
  93. * @param {Object} subject - rule/combination to check match
  94. * @param {boolean} strict - strict checking:
  95. * By default every variant and state inherits from "normal" state/variant
  96. * so when checking if combination matches, it WILL match against "normal"
  97. * state/variant. In strict mode inheritance is ignored an "normal" does
  98. * not match
  99. */
  100. export const combinationsMatch = (criteria, subject, strict) => {
  101. if (criteria.component !== subject.component) return false
  102. // All variants inherit from normal
  103. if (subject.variant !== 'normal' || strict) {
  104. if (criteria.variant !== subject.variant) return false
  105. }
  106. // Subject states > 1 essentially means state is "normal" and therefore matches
  107. if (subject.state.length > 1 || strict) {
  108. const subjectStatesSet = new Set(subject.state)
  109. const criteriaStatesSet = new Set(criteria.state)
  110. const setsAreEqual =
  111. [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
  112. [...subjectStatesSet].every(state => criteriaStatesSet.has(state))
  113. if (!setsAreEqual) return false
  114. }
  115. return true
  116. }
  117. /**
  118. * Search for rule that matches `criteria` in set of rules
  119. * meant to be used in a ruleset.filter() function
  120. *
  121. * @param {Object} criteria - criteria to search for
  122. * @param {boolean} strict - whether search strictly or not (see combinationsMatch)
  123. *
  124. * @return function that returns true/false if subject matches
  125. */
  126. export const findRules = (criteria, strict) => subject => {
  127. // If we searching for "general" rules - ignore "specific" ones
  128. if (criteria.parent === null && !!subject.parent) return false
  129. if (!combinationsMatch(criteria, subject, strict)) return false
  130. if (criteria.parent !== undefined && criteria.parent !== null) {
  131. if (!subject.parent && !strict) return true
  132. const pathCriteria = unroll(criteria)
  133. const pathSubject = unroll(subject)
  134. if (pathCriteria.length < pathSubject.length) return false
  135. // Search: .a .b .c
  136. // Matches: .a .b .c; .b .c; .c; .z .a .b .c
  137. // Does not match .a .b .c .d, .a .b .e
  138. for (let i = 0; i < pathCriteria.length; i++) {
  139. const criteriaParent = pathCriteria[i]
  140. const subjectParent = pathSubject[i]
  141. if (!subjectParent) return true
  142. if (!combinationsMatch(criteriaParent, subjectParent, strict)) return false
  143. }
  144. }
  145. return true
  146. }
  147. // Pre-fills 'normal' state/variant if missing
  148. export const normalizeCombination = rule => {
  149. rule.variant = rule.variant ?? 'normal'
  150. rule.state = [...new Set(['normal', ...(rule.state || [])])]
  151. }