feat: visualize pages (dendograms)
This commit is contained in:
		| @@ -152,12 +152,13 @@ const router = new VueRouter({ | ||||
|     { path: '/locale', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-locale.vue') }, | ||||
|     { path: '/navigation', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-navigation.vue') }, | ||||
|     { path: '/pages', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages.vue') }, | ||||
|     { path: '/pages/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-edit.vue') }, | ||||
|     { path: '/pages/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-edit.vue') }, | ||||
|     { path: '/pages/visualize', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-visualize.vue') }, | ||||
|     { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-theme.vue') }, | ||||
|     { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups.vue') }, | ||||
|     { path: '/groups/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups-edit.vue') }, | ||||
|     { path: '/groups/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups-edit.vue') }, | ||||
|     { path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users.vue') }, | ||||
|     { path: '/users/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users-edit.vue') }, | ||||
|     { path: '/users/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users-edit.vue') }, | ||||
|     { path: '/analytics', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-analytics.vue') }, | ||||
|     { path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-auth.vue') }, | ||||
|     { path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-rendering.vue') }, | ||||
|   | ||||
							
								
								
									
										358
									
								
								client/components/admin/admin-pages-visualize.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								client/components/admin/admin-pages-visualize.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,358 @@ | ||||
| <template lang='pug'> | ||||
|   v-container(fluid, grid-list-lg) | ||||
|     v-layout(row wrap) | ||||
|       v-flex(xs12) | ||||
|         .admin-header | ||||
|           img.animated.fadeInUp(src='/svg/icon-venn-diagram.svg', alt='Visualize Pages', style='width: 80px;') | ||||
|           .admin-header-title | ||||
|             .headline.blue--text.text--darken-2.animated.fadeInLeft Visualize Pages | ||||
|             .subtitle-1.grey--text.animated.fadeInLeft.wait-p2s Dendrogram representation of your pages | ||||
|           v-spacer | ||||
|           v-select.mx-5.animated.fadeInDown.wait-p1s( | ||||
|             v-if='locales.length > 0' | ||||
|             v-model='currentLocale' | ||||
|             :items='locales' | ||||
|             style='flex: 0 1 120px;' | ||||
|             solo | ||||
|             dense | ||||
|             hide-details | ||||
|             item-value='code' | ||||
|             item-text='name' | ||||
|           ) | ||||
|           v-btn-toggle.animated.fadeInDown(v-model='graphMode', color='primary', dense, rounded) | ||||
|             v-btn.px-5(value='htree') | ||||
|               v-icon(left, :color='graphMode === `htree` ? `primary` : `grey darken-3`') mdi-sitemap | ||||
|               span.text-none Hierarchical Tree | ||||
|             v-btn.px-5(value='hradial') | ||||
|               v-icon(left, :color='graphMode === `hradial` ? `primary` : `grey darken-3`') mdi-chart-donut-variant | ||||
|               span.text-none Hierarchical Radial | ||||
|             v-btn.px-5(value='rradial') | ||||
|               v-icon(left, :color='graphMode === `rradial` ? `primary` : `grey darken-3`') mdi-blur-radial | ||||
|               span.text-none Relational Radial | ||||
|               v-chip.ml-3(x-small) Beta | ||||
|         .admin-pages-visualize-svg.pa-10(ref='svgContainer') | ||||
|         v-alert(v-if='pages.length < 1', outlined, type='warning', style='max-width: 650px; margin: 0 auto;') Looks like there's no data yet to graph! | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash' | ||||
| import * as d3 from 'd3' | ||||
| import gql from 'graphql-tag' | ||||
|  | ||||
| /* global siteConfig, siteLangs */ | ||||
|  | ||||
| export default { | ||||
|   data() { | ||||
|     return { | ||||
|       graphMode: 'htree', | ||||
|       width: 800, | ||||
|       radius: 400, | ||||
|       pages: [], | ||||
|       locales: siteLangs, | ||||
|       currentLocale: siteConfig.lang | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     pages () { | ||||
|       this.redraw() | ||||
|     }, | ||||
|     graphMode () { | ||||
|       this.redraw() | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     bilink (root) { | ||||
|       const map = new Map(root.leaves().map(d => [d.data.path, d])) | ||||
|       for (const d of root.leaves()) { | ||||
|         d.incoming = [] | ||||
|         d.outgoing = [] | ||||
|         d.data.links.forEach(i => { | ||||
|           const relNode = map.get(i) | ||||
|           if (relNode) { | ||||
|             d.outgoing.push([d, relNode]) | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
|       for (const d of root.leaves()) { | ||||
|         for (const o of d.outgoing) { | ||||
|           if (o[1]) { | ||||
|             o[1].incoming.push(o) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return root | ||||
|     }, | ||||
|     hierarchy (data, rootOnly = false) { | ||||
|       let result = [] | ||||
|       let level = { result } | ||||
|       const map = new Map(data.map(d => [d.path, d])) | ||||
|       data.forEach(d => { | ||||
|         const pathParts = d.path.split('/') | ||||
|         pathParts.reduce((r, part, i) => { | ||||
|           const curPath = _.take(pathParts, i + 1).join('/') | ||||
|           if (!r[part]) { | ||||
|             r[part] = { result: [] } | ||||
|             const page = map.get(curPath) | ||||
|             r.result.push(page ? { | ||||
|               ...d, | ||||
|               children: r[part].result | ||||
|             } : { | ||||
|               title: part, | ||||
|               links: [], | ||||
|               path: curPath, | ||||
|               children: r[part].result | ||||
|             }) | ||||
|           } | ||||
|  | ||||
|           return r[part] | ||||
|         }, level) | ||||
|       }) | ||||
|  | ||||
|       return rootOnly ? _.head(result) || { children: [] } : { | ||||
|         children: result | ||||
|       } | ||||
|     }, | ||||
|     drawRelations () { | ||||
|       const data = this.hierarchy(this.pages) | ||||
|  | ||||
|       const line = d3.lineRadial() | ||||
|         .curve(d3.curveBundle.beta(0.85)) | ||||
|         .radius(d => d.y) | ||||
|         .angle(d => d.x) | ||||
|  | ||||
|       const tree = d3.cluster() | ||||
|         .size([2 * Math.PI, this.radius - 100]) | ||||
|  | ||||
|       const root = tree(this.bilink(d3.hierarchy(data) | ||||
|         .sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.title, b.data.title)))) | ||||
|  | ||||
|       const svg = d3.create('svg') | ||||
|         .attr('viewBox', [-this.width / 2, -this.width / 2, this.width, this.width]) | ||||
|  | ||||
|       svg.append('g') | ||||
|         .attr('font-family', 'sans-serif') | ||||
|         .attr('font-size', 10) | ||||
|         .selectAll('g') | ||||
|         .data(root.leaves()) | ||||
|         .join('g') | ||||
|         .attr('transform', d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`) | ||||
|         .append('text') | ||||
|         .attr('dy', '0.31em') | ||||
|         .attr('x', d => d.x < Math.PI ? 6 : -6) | ||||
|         .attr('text-anchor', d => d.x < Math.PI ? 'start' : 'end') | ||||
|         .attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null) | ||||
|         .attr('fill', this.$vuetify.theme.dark ? 'white' : '') | ||||
|         .text(d => d.data.title) | ||||
|         .each(function(d) { d.text = this }) | ||||
|         .on('mouseover', overed) | ||||
|         .on('mouseout', outed) | ||||
|         .call(text => text.append('title').text(d => `${d.data.path} | ||||
|           ${d.outgoing.length} outgoing | ||||
|           ${d.incoming.length} incoming`)) | ||||
|  | ||||
|       const link = svg.append('g') | ||||
|         .attr('stroke', '#CCC') | ||||
|         .attr('fill', 'none') | ||||
|         .selectAll('path') | ||||
|         .data(root.leaves().flatMap(leaf => leaf.outgoing)) | ||||
|         .join('path') | ||||
|         .style('mix-blend-mode', 'multiply') | ||||
|         .attr('d', ([i, o]) => line(i.path(o))) | ||||
|         .each(function(d) { d.path = this }) | ||||
|  | ||||
|       function overed(d) { | ||||
|         link.style('mix-blend-mode', null) | ||||
|         d3.select(this).attr('font-weight', 'bold') | ||||
|         d3.selectAll(d.incoming.map(d => d.path)).attr('stroke', '#2196F3').raise() | ||||
|         d3.selectAll(d.incoming.map(([d]) => d.text)).attr('fill', '#2196F3').attr('font-weight', 'bold') | ||||
|         d3.selectAll(d.outgoing.map(d => d.path)).attr('stroke', '#E91E63').raise() | ||||
|         d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr('fill', '#E91E63').attr('font-weight', 'bold') | ||||
|       } | ||||
|  | ||||
|       function outed(d) { | ||||
|         link.style('mix-blend-mode', 'multiply') | ||||
|         d3.select(this).attr('font-weight', null) | ||||
|         d3.selectAll(d.incoming.map(d => d.path)).attr('stroke', null) | ||||
|         d3.selectAll(d.incoming.map(([d]) => d.text)).attr('fill', null).attr('font-weight', null) | ||||
|         d3.selectAll(d.outgoing.map(d => d.path)).attr('stroke', null) | ||||
|         d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr('fill', null).attr('font-weight', null) | ||||
|       } | ||||
|  | ||||
|       this.$refs.svgContainer.appendChild(svg.node()) | ||||
|     }, | ||||
|     drawTree () { | ||||
|       const data = this.hierarchy(this.pages, true) | ||||
|  | ||||
|       const treeRoot = d3.hierarchy(data) | ||||
|       treeRoot.dx = 10 | ||||
|       treeRoot.dy = this.width / (treeRoot.height + 1) | ||||
|       const root = d3.tree().nodeSize([treeRoot.dx, treeRoot.dy])(treeRoot) | ||||
|  | ||||
|       let x0 = Infinity | ||||
|       let x1 = -x0 | ||||
|       root.each(d => { | ||||
|         if (d.x > x1) x1 = d.x | ||||
|         if (d.x < x0) x0 = d.x | ||||
|       }) | ||||
|  | ||||
|       const svg = d3.create('svg') | ||||
|         .attr('viewBox', [0, 0, this.width, x1 - x0 + root.dx * 2]) | ||||
|  | ||||
|       const g = svg.append('g') | ||||
|         .attr('font-family', 'sans-serif') | ||||
|         .attr('font-size', 10) | ||||
|         .attr('transform', `translate(${root.dy / 3},${root.dx - x0})`) | ||||
|  | ||||
|       g.append('g') | ||||
|         .attr('fill', 'none') | ||||
|         .attr('stroke', this.$vuetify.theme.dark ? '#999' : '#555') | ||||
|         .attr('stroke-opacity', 0.4) | ||||
|         .attr('stroke-width', 1.5) | ||||
|         .selectAll('path') | ||||
|         .data(root.links()) | ||||
|         .join('path') | ||||
|         .attr('d', d3.linkHorizontal() | ||||
|           .x(d => d.y) | ||||
|           .y(d => d.x)) | ||||
|  | ||||
|       const node = g.append('g') | ||||
|         .attr('stroke-linejoin', 'round') | ||||
|         .attr('stroke-width', 3) | ||||
|         .selectAll('g') | ||||
|         .data(root.descendants()) | ||||
|         .join('g') | ||||
|         .attr('transform', d => `translate(${d.y},${d.x})`) | ||||
|  | ||||
|       node.append('circle') | ||||
|         .attr('fill', d => d.children ? '#555' : '#999') | ||||
|         .attr('r', 2.5) | ||||
|  | ||||
|       node.append('text') | ||||
|         .attr('dy', '0.31em') | ||||
|         .attr('x', d => d.children ? -6 : 6) | ||||
|         .attr('text-anchor', d => d.children ? 'end' : 'start') | ||||
|         .attr('fill', this.$vuetify.theme.dark ? 'white' : '') | ||||
|         .text(d => d.data.title) | ||||
|         .clone(true).lower() | ||||
|         .attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white') | ||||
|  | ||||
|       this.$refs.svgContainer.appendChild(svg.node()) | ||||
|     }, | ||||
|     drawRadialTree () { | ||||
|       const data = this.hierarchy(this.pages) | ||||
|  | ||||
|       const tree = d3.tree() | ||||
|         .size([2 * Math.PI, this.radius]) | ||||
|         .separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth) | ||||
|  | ||||
|       const root = tree(d3.hierarchy(data) | ||||
|         .sort((a, b) => d3.ascending(a.data.title, b.data.title))) | ||||
|  | ||||
|       const svg = d3.create('svg') | ||||
|         .style('font', '10px sans-serif') | ||||
|  | ||||
|       svg.append('g') | ||||
|         .attr('fill', 'none') | ||||
|         .attr('stroke', this.$vuetify.theme.dark ? 'white' : '#555') | ||||
|         .attr('stroke-opacity', 0.4) | ||||
|         .attr('stroke-width', 1.5) | ||||
|         .selectAll('path') | ||||
|         .data(root.links()) | ||||
|         .join('path') | ||||
|         .attr('d', d3.linkRadial() | ||||
|           .angle(d => d.x) | ||||
|           .radius(d => d.y)) | ||||
|  | ||||
|       const node = svg.append('g') | ||||
|         .attr('stroke-linejoin', 'round') | ||||
|         .attr('stroke-width', 3) | ||||
|         .selectAll('g') | ||||
|         .data(root.descendants().reverse()) | ||||
|         .join('g') | ||||
|         .attr('transform', d => ` | ||||
|           rotate(${d.x * 180 / Math.PI - 90}) | ||||
|           translate(${d.y},0) | ||||
|         `) | ||||
|  | ||||
|       node.append('circle') | ||||
|         .attr('fill', d => d.children ? '#555' : '#999') | ||||
|         .attr('r', 2.5) | ||||
|  | ||||
|       node.append('text') | ||||
|         .attr('dy', '0.31em') | ||||
|         /* eslint-disable no-mixed-operators */ | ||||
|         .attr('x', d => d.x < Math.PI === !d.children ? 6 : -6) | ||||
|         .attr('text-anchor', d => d.x < Math.PI === !d.children ? 'start' : 'end') | ||||
|         .attr('transform', d => d.x >= Math.PI ? 'rotate(180)' : null) | ||||
|         /* eslint-enable no-mixed-operators */ | ||||
|         .attr('fill', this.$vuetify.theme.dark ? 'white' : '') | ||||
|         .text(d => d.data.title) | ||||
|         .clone(true).lower() | ||||
|         .attr('stroke', this.$vuetify.theme.dark ? '#222' : 'white') | ||||
|  | ||||
|       this.$refs.svgContainer.appendChild(svg.node()) | ||||
|  | ||||
|       function autoBox() { | ||||
|         const {x, y, width, height} = this.getBBox() | ||||
|         return [x, y, width, height] | ||||
|       } | ||||
|  | ||||
|       svg.attr('viewBox', autoBox) | ||||
|     }, | ||||
|     redraw () { | ||||
|       while (this.$refs.svgContainer.firstChild) { | ||||
|         this.$refs.svgContainer.firstChild.remove() | ||||
|       } | ||||
|       if (this.pages.length > 0) { | ||||
|         switch (this.graphMode) { | ||||
|           case 'rradial': | ||||
|             this.drawRelations() | ||||
|             break | ||||
|           case 'htree': | ||||
|             this.drawTree() | ||||
|             break | ||||
|           case 'hradial': | ||||
|             this.drawRadialTree() | ||||
|             break | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   apollo: { | ||||
|     pages: { | ||||
|       query: gql` | ||||
|         query ($locale: String!) { | ||||
|           pages { | ||||
|             links(locale: $locale) { | ||||
|               id | ||||
|               path | ||||
|               title | ||||
|               links | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       `, | ||||
|       variables () { | ||||
|         return { | ||||
|           locale: this.currentLocale | ||||
|         } | ||||
|       }, | ||||
|       fetchPolicy: 'network-only', | ||||
|       update: (data) => data.pages.links, | ||||
|       watchLoading (isLoading) { | ||||
|         this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-pages-refresh') | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang='scss'> | ||||
| .admin-pages-visualize-svg { | ||||
|   text-align: center; | ||||
|  | ||||
|   > svg { | ||||
|     height: 100vh; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -13,9 +13,9 @@ | ||||
|           v-btn.animated.fadeInDown.mx-3(color='primary', outlined, large, @click='recyclebin', disabled) | ||||
|             v-icon(left) mdi-delete-outline | ||||
|             span Recycle Bin | ||||
|           v-btn.animated.fadeInDown(color='primary', depressed, large, @click='newpage', disabled) | ||||
|             v-icon(left) mdi-plus | ||||
|             span New Page | ||||
|           v-btn.animated.fadeInDown(color='primary', depressed, large, to='pages/visualize') | ||||
|             v-icon(left) mdi-graph | ||||
|             span Visualize | ||||
|         v-card.wiki-form.mt-3.animated.fadeInUp | ||||
|           v-toolbar(flat, :color='$vuetify.theme.dark ? `grey darken-3-d5` : `grey lighten-5`', height='80') | ||||
|             v-spacer | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
|       v-tabs(color='white', background-color='blue darken-1', dark, centered) | ||||
|         v-tab {{$t('editor:props.info')}} | ||||
|         v-tab {{$t('editor:props.scheduling')}} | ||||
|         v-tab(disabled) {{$t('editor:props.scripts')}} | ||||
|         v-tab {{$t('editor:props.social')}} | ||||
|         v-tab-item | ||||
|           v-card-text.pt-5 | ||||
| @@ -177,6 +178,25 @@ | ||||
|                         @click='$refs.menuPublishEnd.save(publishEndDate)' | ||||
|                         ) {{$t('common:actions.ok')}} | ||||
|  | ||||
|         v-tab-item | ||||
|           v-card-text | ||||
|             .overline.pb-3 {{$t('editor:props.js')}} | ||||
|             v-textarea( | ||||
|               outlined | ||||
|               rows='5' | ||||
|               :hint='$t(`editor:props.jsHint`)' | ||||
|               persistent-hint | ||||
|             ) | ||||
|           v-divider | ||||
|           v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d3` : `lighten-5`') | ||||
|             .overline.pb-3 {{$t('editor:props.css')}} | ||||
|             v-textarea( | ||||
|               outlined | ||||
|               rows='5' | ||||
|               :hint='$t(`editor:props.cssHint`)' | ||||
|               persistent-hint | ||||
|             ) | ||||
|  | ||||
|         v-tab-item | ||||
|           v-card-text | ||||
|             .overline.pb-5 {{$t('editor:props.socialFeatures')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon] | ||||
|   | ||||
| @@ -215,6 +215,7 @@ | ||||
|     "core-js": "3.6.1", | ||||
|     "css-loader": "3.4.0", | ||||
|     "cssnano": "4.1.10", | ||||
|     "d3": "5.15.0", | ||||
|     "duplicate-package-checker-webpack-plugin": "3.0.0", | ||||
|     "epic-spinners": "1.1.0", | ||||
|     "eslint": "6.8.0", | ||||
|   | ||||
| @@ -137,8 +137,12 @@ module.exports = { | ||||
|     async tree (obj, args, context, info) { | ||||
|       let results = [] | ||||
|       let conds = { | ||||
|         localeCode: args.locale, | ||||
|         parent: (args.parent < 1) ? null : args.parent | ||||
|         localeCode: args.locale | ||||
|       } | ||||
|       if (args.parent) { | ||||
|         conds.parent = (args.parent < 1) ? null : args.parent | ||||
|       } else if (args.path) { | ||||
|         // conds.parent = (args.parent < 1) ? null : args.parent | ||||
|       } | ||||
|       switch (args.mode) { | ||||
|         case 'FOLDERS': | ||||
| @@ -162,6 +166,44 @@ module.exports = { | ||||
|         parent: r.parent || 0, | ||||
|         locale: r.localeCode | ||||
|       })) | ||||
|     }, | ||||
|     /** | ||||
|      * FETCH PAGE LINKS | ||||
|      */ | ||||
|     async links (obj, args, context, info) { | ||||
|       let results = [] | ||||
|  | ||||
|       results = await WIKI.models.knex('pages') | ||||
|         .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.localeCode' }) | ||||
|         .fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId') | ||||
|         .where({ | ||||
|           'pages.localeCode': args.locale | ||||
|         }) | ||||
|  | ||||
|       return _.reduce(results, (result, val) => { | ||||
|         // -> Check if user has access to source and linked page | ||||
|         if ( | ||||
|           !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.path, locale: args.locale }) || | ||||
|           !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.link, locale: val.locale }) | ||||
|         ) { | ||||
|           return result | ||||
|         } | ||||
|  | ||||
|         const existingEntry = _.findIndex(result, ['id', val.id]) | ||||
|         if (existingEntry >= 0) { | ||||
|           if (val.link) { | ||||
|             result[existingEntry].links.push(`${val.locale}/${val.link}`) | ||||
|           } | ||||
|         } else { | ||||
|           result.push({ | ||||
|             id: val.id, | ||||
|             title: val.title, | ||||
|             path: `${args.locale}/${val.path}`, | ||||
|             links: val.link ? [`${val.locale}/${val.link}`] : [] | ||||
|           }) | ||||
|         } | ||||
|         return result | ||||
|       }, []) | ||||
|     } | ||||
|   }, | ||||
|   PageMutation: { | ||||
|   | ||||
| @@ -42,10 +42,16 @@ type PageQuery { | ||||
|   tags: [PageTag]! @auth(requires: ["manage:system", "read:pages"]) | ||||
|  | ||||
|   tree( | ||||
|     parent: Int! | ||||
|     path: String | ||||
|     parent: Int | ||||
|     mode: PageTreeMode! | ||||
|     locale: String! | ||||
|     includeParents: Boolean | ||||
|   ): [PageTreeItem] @auth(requires: ["manage:system", "read:pages"]) | ||||
|  | ||||
|   links( | ||||
|     locale: String! | ||||
|   ): [PageLinkItem] @auth(requires: ["manage:system", "read:pages"]) | ||||
| } | ||||
|  | ||||
| # ----------------------------------------------- | ||||
| @@ -209,6 +215,13 @@ type PageTreeItem { | ||||
|   locale: String! | ||||
| } | ||||
|  | ||||
| type PageLinkItem { | ||||
|   id: Int! | ||||
|   path: String! | ||||
|   title: String! | ||||
|   links: [String]! | ||||
| } | ||||
|  | ||||
| enum PageOrderBy { | ||||
|   CREATED | ||||
|   ID | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const crypto = require('crypto') | ||||
| const path = require('path') | ||||
|  | ||||
| const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i | ||||
| const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?)\/?(.*)/i | ||||
| const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i | ||||
|  | ||||
| const contentToExt = { | ||||
|   markdown: 'md', | ||||
| @@ -125,7 +125,7 @@ module.exports = { | ||||
|     const result = localeFolderRegex.exec(meta.path) | ||||
|     if (result[1]) { | ||||
|       meta = { | ||||
|         locale: result[1], | ||||
|         locale: result[1].replace('/', ''), | ||||
|         path: result[2] | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -72,7 +72,8 @@ module.exports = { | ||||
|   }, | ||||
|  | ||||
|   async processPage ({ user, fullPath, relPath, contentType, moduleName }) { | ||||
|     const contentPath = pageHelper.getPagePath(relPath) | ||||
|     const normalizedRelPath = relPath.replace(/\\/g, '/') | ||||
|     const contentPath = pageHelper.getPagePath(normalizedRelPath) | ||||
|     const itemContents = await fs.readFile(path.join(fullPath, relPath), 'utf8') | ||||
|     const pageData = WIKI.models.pages.parseMetadata(itemContents, contentType) | ||||
|     const currentPage = await WIKI.models.pages.getPageFromDb({ | ||||
| @@ -82,7 +83,7 @@ module.exports = { | ||||
|     const newTags = !_.isNil(pageData.tags) ? _.get(pageData, 'tags', '').split(', ') : false | ||||
|     if (currentPage) { | ||||
|       // Already in the DB, can mark as modified | ||||
|       WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as modified: ${relPath}`) | ||||
|       WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as modified: ${normalizedRelPath}`) | ||||
|       await WIKI.models.pages.updatePage({ | ||||
|         id: currentPage.id, | ||||
|         title: _.get(pageData, 'title', currentPage.title), | ||||
| @@ -96,7 +97,7 @@ module.exports = { | ||||
|       }) | ||||
|     } else { | ||||
|       // Not in the DB, can mark as new | ||||
|       WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as new: ${relPath}`) | ||||
|       WIKI.logger.info(`(STORAGE/${moduleName}) Page marked as new: ${normalizedRelPath}`) | ||||
|       const pageEditor = await WIKI.models.editors.getDefaultEditor(contentType) | ||||
|       await WIKI.models.pages.createPage({ | ||||
|         path: contentPath.path, | ||||
|   | ||||
							
								
								
									
										260
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										260
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -4263,6 +4263,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: | ||||
|   dependencies: | ||||
|     delayed-stream "~1.0.0" | ||||
|  | ||||
| commander@2: | ||||
|   version "2.20.3" | ||||
|   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" | ||||
|   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== | ||||
|  | ||||
| commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0, commander@~2.20.0: | ||||
|   version "2.20.0" | ||||
|   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" | ||||
| @@ -4848,6 +4853,254 @@ cyclist@^1.0.1: | ||||
|   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" | ||||
|   integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= | ||||
|  | ||||
| d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: | ||||
|   version "1.2.4" | ||||
|   resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" | ||||
|   integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== | ||||
|  | ||||
| d3-axis@1: | ||||
|   version "1.0.12" | ||||
|   resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9" | ||||
|   integrity sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ== | ||||
|  | ||||
| d3-brush@1: | ||||
|   version "1.1.5" | ||||
|   resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.1.5.tgz#066b8e84d17b192986030446c97c0fba7e1bacdc" | ||||
|   integrity sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A== | ||||
|   dependencies: | ||||
|     d3-dispatch "1" | ||||
|     d3-drag "1" | ||||
|     d3-interpolate "1" | ||||
|     d3-selection "1" | ||||
|     d3-transition "1" | ||||
|  | ||||
| d3-chord@1: | ||||
|   version "1.0.6" | ||||
|   resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f" | ||||
|   integrity sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA== | ||||
|   dependencies: | ||||
|     d3-array "1" | ||||
|     d3-path "1" | ||||
|  | ||||
| d3-collection@1: | ||||
|   version "1.0.7" | ||||
|   resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" | ||||
|   integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== | ||||
|  | ||||
| d3-color@1: | ||||
|   version "1.4.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.0.tgz#89c45a995ed773b13314f06460df26d60ba0ecaf" | ||||
|   integrity sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg== | ||||
|  | ||||
| d3-contour@1: | ||||
|   version "1.3.2" | ||||
|   resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3" | ||||
|   integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg== | ||||
|   dependencies: | ||||
|     d3-array "^1.1.1" | ||||
|  | ||||
| d3-dispatch@1: | ||||
|   version "1.0.6" | ||||
|   resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" | ||||
|   integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== | ||||
|  | ||||
| d3-drag@1: | ||||
|   version "1.2.5" | ||||
|   resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" | ||||
|   integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w== | ||||
|   dependencies: | ||||
|     d3-dispatch "1" | ||||
|     d3-selection "1" | ||||
|  | ||||
| d3-dsv@1: | ||||
|   version "1.2.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" | ||||
|   integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g== | ||||
|   dependencies: | ||||
|     commander "2" | ||||
|     iconv-lite "0.4" | ||||
|     rw "1" | ||||
|  | ||||
| d3-ease@1: | ||||
|   version "1.0.6" | ||||
|   resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.6.tgz#ebdb6da22dfac0a22222f2d4da06f66c416a0ec0" | ||||
|   integrity sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ== | ||||
|  | ||||
| d3-fetch@1: | ||||
|   version "1.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.1.2.tgz#957c8fbc6d4480599ba191b1b2518bf86b3e1be2" | ||||
|   integrity sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA== | ||||
|   dependencies: | ||||
|     d3-dsv "1" | ||||
|  | ||||
| d3-force@1: | ||||
|   version "1.2.1" | ||||
|   resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b" | ||||
|   integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg== | ||||
|   dependencies: | ||||
|     d3-collection "1" | ||||
|     d3-dispatch "1" | ||||
|     d3-quadtree "1" | ||||
|     d3-timer "1" | ||||
|  | ||||
| d3-format@1: | ||||
|   version "1.4.3" | ||||
|   resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.3.tgz#4e8eb4dff3fdcb891a8489ec6e698601c41b96f1" | ||||
|   integrity sha512-mm/nE2Y9HgGyjP+rKIekeITVgBtX97o1nrvHCWX8F/yBYyevUTvu9vb5pUnKwrcSw7o7GuwMOWjS9gFDs4O+uQ== | ||||
|  | ||||
| d3-geo@1: | ||||
|   version "1.11.9" | ||||
|   resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.11.9.tgz#77eaed14ba62fc2c0aef55cd2943849c866f7ae6" | ||||
|   integrity sha512-9edcH6J3s/Aa3KJITWqFJbyB/8q3mMlA9Fi7z6yy+FAYMnRaxmC7jBhUnsINxVWD14GmqX3DK8uk7nV6/Ekt4A== | ||||
|   dependencies: | ||||
|     d3-array "1" | ||||
|  | ||||
| d3-hierarchy@1: | ||||
|   version "1.1.9" | ||||
|   resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" | ||||
|   integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== | ||||
|  | ||||
| d3-interpolate@1: | ||||
|   version "1.4.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" | ||||
|   integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== | ||||
|   dependencies: | ||||
|     d3-color "1" | ||||
|  | ||||
| d3-path@1: | ||||
|   version "1.0.9" | ||||
|   resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" | ||||
|   integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== | ||||
|  | ||||
| d3-polygon@1: | ||||
|   version "1.0.6" | ||||
|   resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e" | ||||
|   integrity sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ== | ||||
|  | ||||
| d3-quadtree@1: | ||||
|   version "1.0.7" | ||||
|   resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135" | ||||
|   integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA== | ||||
|  | ||||
| d3-random@1: | ||||
|   version "1.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291" | ||||
|   integrity sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ== | ||||
|  | ||||
| d3-scale-chromatic@1: | ||||
|   version "1.5.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98" | ||||
|   integrity sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg== | ||||
|   dependencies: | ||||
|     d3-color "1" | ||||
|     d3-interpolate "1" | ||||
|  | ||||
| d3-scale@2: | ||||
|   version "2.2.2" | ||||
|   resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" | ||||
|   integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw== | ||||
|   dependencies: | ||||
|     d3-array "^1.2.0" | ||||
|     d3-collection "1" | ||||
|     d3-format "1" | ||||
|     d3-interpolate "1" | ||||
|     d3-time "1" | ||||
|     d3-time-format "2" | ||||
|  | ||||
| d3-selection@1, d3-selection@^1.1.0: | ||||
|   version "1.4.1" | ||||
|   resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.1.tgz#98eedbbe085fbda5bafa2f9e3f3a2f4d7d622a98" | ||||
|   integrity sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA== | ||||
|  | ||||
| d3-shape@1: | ||||
|   version "1.3.7" | ||||
|   resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" | ||||
|   integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== | ||||
|   dependencies: | ||||
|     d3-path "1" | ||||
|  | ||||
| d3-time-format@2: | ||||
|   version "2.2.3" | ||||
|   resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.2.3.tgz#0c9a12ee28342b2037e5ea1cf0b9eb4dd75f29cb" | ||||
|   integrity sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA== | ||||
|   dependencies: | ||||
|     d3-time "1" | ||||
|  | ||||
| d3-time@1: | ||||
|   version "1.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" | ||||
|   integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== | ||||
|  | ||||
| d3-timer@1: | ||||
|   version "1.0.10" | ||||
|   resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" | ||||
|   integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== | ||||
|  | ||||
| d3-transition@1: | ||||
|   version "1.3.2" | ||||
|   resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398" | ||||
|   integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA== | ||||
|   dependencies: | ||||
|     d3-color "1" | ||||
|     d3-dispatch "1" | ||||
|     d3-ease "1" | ||||
|     d3-interpolate "1" | ||||
|     d3-selection "^1.1.0" | ||||
|     d3-timer "1" | ||||
|  | ||||
| d3-voronoi@1: | ||||
|   version "1.1.4" | ||||
|   resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" | ||||
|   integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg== | ||||
|  | ||||
| d3-zoom@1: | ||||
|   version "1.8.3" | ||||
|   resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a" | ||||
|   integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ== | ||||
|   dependencies: | ||||
|     d3-dispatch "1" | ||||
|     d3-drag "1" | ||||
|     d3-interpolate "1" | ||||
|     d3-selection "1" | ||||
|     d3-transition "1" | ||||
|  | ||||
| d3@5.15.0: | ||||
|   version "5.15.0" | ||||
|   resolved "https://registry.yarnpkg.com/d3/-/d3-5.15.0.tgz#ffd44958e6a3cb8a59a84429c45429b8bca5677a" | ||||
|   integrity sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg== | ||||
|   dependencies: | ||||
|     d3-array "1" | ||||
|     d3-axis "1" | ||||
|     d3-brush "1" | ||||
|     d3-chord "1" | ||||
|     d3-collection "1" | ||||
|     d3-color "1" | ||||
|     d3-contour "1" | ||||
|     d3-dispatch "1" | ||||
|     d3-drag "1" | ||||
|     d3-dsv "1" | ||||
|     d3-ease "1" | ||||
|     d3-fetch "1" | ||||
|     d3-force "1" | ||||
|     d3-format "1" | ||||
|     d3-geo "1" | ||||
|     d3-hierarchy "1" | ||||
|     d3-interpolate "1" | ||||
|     d3-path "1" | ||||
|     d3-polygon "1" | ||||
|     d3-quadtree "1" | ||||
|     d3-random "1" | ||||
|     d3-scale "2" | ||||
|     d3-scale-chromatic "1" | ||||
|     d3-selection "1" | ||||
|     d3-shape "1" | ||||
|     d3-time "1" | ||||
|     d3-time-format "2" | ||||
|     d3-timer "1" | ||||
|     d3-transition "1" | ||||
|     d3-voronoi "1" | ||||
|     d3-zoom "1" | ||||
|  | ||||
| d@1, d@^1.0.1: | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" | ||||
| @@ -7015,7 +7268,7 @@ i18next@19.0.2: | ||||
|   dependencies: | ||||
|     "@babel/runtime" "^7.3.1" | ||||
|  | ||||
| iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: | ||||
| iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: | ||||
|   version "0.4.24" | ||||
|   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" | ||||
|   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== | ||||
| @@ -12513,6 +12766,11 @@ run-queue@^1.0.0, run-queue@^1.0.3: | ||||
|   dependencies: | ||||
|     aproba "^1.1.1" | ||||
|  | ||||
| rw@1: | ||||
|   version "1.3.3" | ||||
|   resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" | ||||
|   integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= | ||||
|  | ||||
| rxjs@^6.4.0: | ||||
|   version "6.5.3" | ||||
|   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user