You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
11 KiB

  1. <template lang="pug">
  2. v-card.wiki-form
  3. v-card-text(v-if='group.id === 1')
  4. v-alert.radius-7(
  5. :class='$vuetify.dark ? "grey darken-4" : "orange lighten-5"'
  6. color='orange darken-2'
  7. outline
  8. :value='true'
  9. icon='lock_outline'
  10. ) This group has access to everything.
  11. template(v-else)
  12. v-card-title(:class='$vuetify.dark ? `grey darken-3-d5` : `grey lighten-5`')
  13. v-alert.radius-7(
  14. :class='$vuetify.dark ? `grey darken-3-d3` : `white`'
  15. :value='true'
  16. color='grey'
  17. outline
  18. icon='info'
  19. ) You must enable global content permissions (under Permissions tab) for page rules to have any effect.
  20. v-spacer
  21. v-btn(depressed, color='primary', @click='addRule')
  22. v-icon(left) add
  23. | Add Rule
  24. v-menu(
  25. right
  26. offset-y
  27. nudge-left='115'
  28. )
  29. v-btn.is-icon(slot='activator', flat, outline, color='primary')
  30. v-icon more_horiz
  31. v-list(dense)
  32. v-list-tile(@click='comingSoon')
  33. v-list-tile-avatar
  34. v-icon keyboard_capslock
  35. v-list-tile-title Load Preset
  36. v-divider
  37. v-list-tile(@click='comingSoon')
  38. v-list-tile-avatar
  39. v-icon publish
  40. v-list-tile-title Save As Preset
  41. v-divider
  42. v-list-tile(@click='comingSoon')
  43. v-list-tile-avatar
  44. v-icon cloud_upload
  45. v-list-tile-title Import Rules
  46. v-divider
  47. v-list-tile(@click='comingSoon')
  48. v-list-tile-avatar
  49. v-icon cloud_download
  50. v-list-tile-title Export Rules
  51. v-card-text(:class='$vuetify.dark ? `grey darken-4-l5` : `white`')
  52. .rules
  53. .caption(v-if='group.pageRules.length === 0')
  54. em(:class='$vuetify.dark ? `grey--text` : `blue-grey--text`') This group has no page rules yet.
  55. .rule(v-for='rule of group.pageRules', :key='rule.id')
  56. v-btn.ma-0.rule-deny-btn(
  57. solo
  58. :color='rule.deny ? "red" : "green"'
  59. dark
  60. @click='rule.deny = !rule.deny'
  61. )
  62. v-icon(v-if='rule.deny') block
  63. v-icon(v-else) check_circle
  64. //- Roles
  65. v-select.ml-1(
  66. solo
  67. :items='roles'
  68. v-model='rule.roles'
  69. placeholder='Select Role(s)...'
  70. hide-details
  71. multiple
  72. chips
  73. deletable-chips
  74. small-chips
  75. style='flex: 0 1 440px;'
  76. :menu-props='{ "maxHeight": 500 }'
  77. clearable
  78. dense
  79. )
  80. template(slot='selection', slot-scope='{ item, index }')
  81. v-chip.white--text.ml-0(v-if='index <= 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value }}
  82. v-chip.white--text.ml-0(v-if='index === 2', small, label, :color='rule.deny ? `red lighten-2` : `green lighten-2`').caption + {{ rule.roles.length - 2 }} more
  83. template(slot='item', slot-scope='props')
  84. v-list-tile-action(style='min-width: 30px;')
  85. v-checkbox(
  86. v-model='props.tile.props.value'
  87. hide-details
  88. color='primary'
  89. )
  90. v-icon.mr-2(:color='rule.deny ? `red` : `green`') {{props.item.icon}}
  91. v-list-tile-content
  92. v-list-tile-title.body-2 {{props.item.text}}
  93. v-chip.mr-2.grey--text(label, small, :color='$vuetify.dark ? `grey darken-4` : `grey lighten-4`').caption {{props.item.value}}
  94. //- Match
  95. v-select.ml-1.mr-1(
  96. solo
  97. :items='matches'
  98. v-model='rule.match'
  99. placeholder='Match...'
  100. hide-details
  101. style='flex: 0 1 250px;'
  102. dense
  103. )
  104. template(slot='selection', slot-scope='{ item, index }')
  105. .body-1 {{item.text}}
  106. template(slot='item', slot-scope='data')
  107. v-list-tile-avatar
  108. v-avatar.white--text.radius-4(color='blue', size='30', tile) {{ data.item.icon }}
  109. v-list-tile-content
  110. v-list-tile-title(v-html='data.item.text')
  111. //- Locales
  112. v-select.mr-1(
  113. :background-color='$vuetify.dark ? `grey darken-3-d5` : `blue-grey lighten-5`'
  114. solo
  115. :items='locales'
  116. v-model='rule.locales'
  117. placeholder='Any Locale'
  118. multiple
  119. hide-details
  120. dense
  121. :menu-props='{ "minWidth": 250 }'
  122. style='flex: 0 1 150px;'
  123. )
  124. template(slot='selection', slot-scope='{ item, index }')
  125. v-chip.white--text.ml-0(v-if='rule.locales.length === 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value.toUpperCase() }}
  126. v-chip.white--text.ml-0(v-else-if='index === 0', small, label, :color='rule.deny ? `red` : `green`').caption {{ rule.locales.length }} locales
  127. v-list-tile(slot='prepend-item', @click='rule.locales = []')
  128. v-list-tile-action(style='min-width: 30px;')
  129. v-checkbox(
  130. :input-value='rule.locales.length === 0'
  131. hide-details
  132. color='primary'
  133. readonly
  134. )
  135. v-icon.mr-2(:color='rule.deny ? `red` : `green`') public
  136. v-list-tile-content
  137. v-list-tile-title.body-2 Any Locale
  138. v-divider(slot='prepend-item')
  139. template(slot='item', slot-scope='props')
  140. v-list-tile-action(style='min-width: 30px;')
  141. v-checkbox(
  142. v-model='props.tile.props.value'
  143. hide-details
  144. color='primary'
  145. )
  146. v-icon.mr-2(:color='rule.deny ? `red` : `green`') language
  147. v-list-tile-content
  148. v-list-tile-title.body-2 {{props.item.text}}
  149. v-chip.mr-2.grey--text(label, small, :color='$vuetify.dark ? `grey darken-4` : `grey lighten-4`').caption {{props.item.value.toUpperCase()}}
  150. //- Path
  151. v-text-field(
  152. solo
  153. v-model='rule.path'
  154. label='Path'
  155. :prefix='rule.match !== `END` ? `/` : null'
  156. :placeholder='rule.match === `REGEX` ? `Regular Expression` : `Path`'
  157. :suffix='rule.match === `REGEX` ? `/` : null'
  158. hide-details
  159. :color='$vuetify.dark ? `grey` : `blue-grey`'
  160. )
  161. v-btn(icon, @click='removeRule(rule.id)')
  162. v-icon(:color='$vuetify.dark ? `grey` : `blue-grey`') clear
  163. v-divider.mt-3
  164. v-subheader.pl-0 Rules Order
  165. .body-1.pl-3 Rules are applied in order of path specificity. A more precise path will always override a less defined path.
  166. .body-1.pl-4 For example, #[span.teal--text /geography/countries] will override #[span.teal--text /geography].
  167. .body-1.pl-3.pt-2 When 2 rules have the same specificity, the priority is given from lowest to highest as follows:
  168. .body-1.pl-3.pt-1
  169. ul
  170. li
  171. strong Path Starts With...
  172. em.caption.pl-1 (lowest)
  173. li
  174. strong Path Ends With...
  175. li
  176. strong Path Matches Regex...
  177. li
  178. strong Path Is Exactly...
  179. em.caption.pl-1 (highest)
  180. .body-1.pl-3.pt-2 When 2 rules have the same path specificity AND the same match type, #[strong.red--text DENY] will always override an #[strong.green--text ALLOW] rule.
  181. v-divider.mt-3
  182. v-subheader.pl-0 Regular Expressions
  183. span Expressions that are deemed unsafe or could result in exponential time processing will be rejected upon saving.
  184. </template>
  185. <script>
  186. import _ from 'lodash'
  187. import nanoid from 'nanoid/non-secure/generate'
  188. export default {
  189. props: {
  190. value: {
  191. type: Object
  192. }
  193. },
  194. data() {
  195. return {
  196. roles: [
  197. { text: 'Read Pages', value: 'read:pages', icon: 'insert_drive_file' },
  198. { text: 'Create Pages', value: 'write:pages', icon: 'insert_drive_file' },
  199. { text: 'Edit + Move Pages', value: 'manage:pages', icon: 'insert_drive_file' },
  200. { text: 'Delete Pages', value: 'delete:pages', icon: 'insert_drive_file' },
  201. { text: 'Read / Use Assets', value: 'read:assets', icon: 'camera' },
  202. { text: 'Upload Assets', value: 'write:assets', icon: 'camera' },
  203. { text: 'Edit + Delete Assets', value: 'manage:assets', icon: 'camera' },
  204. { text: 'Read Comments', value: 'read:comments', icon: 'insert_comment' },
  205. { text: 'Create Comments', value: 'write:comments', icon: 'insert_comment' },
  206. { text: 'Edit + Delete Comments', value: 'manage:comments', icon: 'insert_comment' }
  207. ],
  208. matches: [
  209. { text: 'Path Starts With...', value: 'START', icon: '/...' },
  210. { text: 'Path is Exactly...', value: 'EXACT', icon: '=' },
  211. { text: 'Path Ends With...', value: 'END', icon: '.../' },
  212. { text: 'Path Matches Regex...', value: 'REGEX', icon: '$.*' }
  213. ],
  214. locales: [
  215. { text: 'English', value: 'en' }
  216. ]
  217. }
  218. },
  219. computed: {
  220. group: {
  221. get() { return this.value },
  222. set(val) { this.$set('input', val) }
  223. }
  224. },
  225. methods: {
  226. addRule(group) {
  227. this.group.pageRules.push({
  228. id: nanoid('1234567890abcdef', 10),
  229. path: '',
  230. roles: [],
  231. match: 'START',
  232. deny: false,
  233. locales: []
  234. })
  235. },
  236. removeRule(rule) {
  237. this.group.pageRules.splice(_.findIndex(this.group.pageRules, ['id', rule.id]), 1)
  238. },
  239. comingSoon() {
  240. this.$store.commit('showNotification', {
  241. style: 'indigo',
  242. message: `Coming soon...`,
  243. icon: 'directions_boat'
  244. })
  245. }
  246. }
  247. }
  248. </script>
  249. <style lang="scss">
  250. .rules {
  251. background-color: mc('blue-grey', '50');
  252. border-radius: 4px;
  253. padding: 1rem;
  254. position: relative;
  255. @at-root .theme--dark & {
  256. background-color: mc('grey', '800');
  257. }
  258. }
  259. .rule {
  260. display: flex;
  261. background-color: mc('blue-grey', '100');
  262. border-radius: 4px;
  263. padding: .5rem;
  264. &-enter-active, &-leave-active {
  265. transition: all .5s ease;
  266. }
  267. &-enter, &-leave-to {
  268. opacity: 0;
  269. }
  270. @at-root .theme--dark & {
  271. background-color: mc('grey', '700');
  272. }
  273. & + .rule {
  274. margin-top: .5rem;
  275. position: relative;
  276. &::before {
  277. content: '+';
  278. position: absolute;
  279. width: 2rem;
  280. height: 2rem;
  281. border-radius: 50%;
  282. display: flex;
  283. justify-content: center;
  284. align-items: center;
  285. font-weight: 600;
  286. color: mc('blue-grey', '700');
  287. font-size: 1.25rem;
  288. background-color: mc('blue-grey', '50');
  289. left: -2rem;
  290. top: -1.3rem;
  291. @at-root .theme--dark & {
  292. background-color: mc('grey', '800');
  293. color: mc('grey', '600');
  294. }
  295. }
  296. }
  297. .input-group + * {
  298. margin-left: .5rem;
  299. }
  300. &-deny-btn {
  301. height: 48px;
  302. border-radius: 2px 0 0 2px;
  303. min-width: 0;
  304. }
  305. }
  306. </style>