From e4cfd0bec86a5426769700c138ebe072c5f7117b Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 15 Jan 2019 15:00:03 -0500 Subject: [PATCH 01/48] refactors db structure --- packages/cms/db/profile_footnote.js | 1 - packages/cms/db/profile_stat.js | 1 - packages/cms/db/section.js | 1 - packages/cms/db/section_description.js | 1 - packages/cms/db/section_subtitle.js | 1 - packages/cms/src/db/profile.js | 4 +- packages/cms/src/db/profile_footnote.js | 36 ---------------- packages/cms/src/db/profile_stat.js | 48 ---------------------- packages/cms/src/db/section.js | 46 --------------------- packages/cms/src/db/section_description.js | 36 ---------------- packages/cms/src/db/section_subtitle.js | 36 ---------------- packages/cms/src/db/topic.js | 4 +- 12 files changed, 3 insertions(+), 212 deletions(-) delete mode 100644 packages/cms/db/profile_footnote.js delete mode 100644 packages/cms/db/profile_stat.js delete mode 100644 packages/cms/db/section.js delete mode 100644 packages/cms/db/section_description.js delete mode 100644 packages/cms/db/section_subtitle.js delete mode 100644 packages/cms/src/db/profile_footnote.js delete mode 100644 packages/cms/src/db/profile_stat.js delete mode 100644 packages/cms/src/db/section.js delete mode 100644 packages/cms/src/db/section_description.js delete mode 100644 packages/cms/src/db/section_subtitle.js diff --git a/packages/cms/db/profile_footnote.js b/packages/cms/db/profile_footnote.js deleted file mode 100644 index f5c882878..000000000 --- a/packages/cms/db/profile_footnote.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../src/db/profile_footnote"); diff --git a/packages/cms/db/profile_stat.js b/packages/cms/db/profile_stat.js deleted file mode 100644 index 3608e3325..000000000 --- a/packages/cms/db/profile_stat.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../src/db/profile_stat"); diff --git a/packages/cms/db/section.js b/packages/cms/db/section.js deleted file mode 100644 index ec8d450e7..000000000 --- a/packages/cms/db/section.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../src/db/section"); diff --git a/packages/cms/db/section_description.js b/packages/cms/db/section_description.js deleted file mode 100644 index 7faafcd48..000000000 --- a/packages/cms/db/section_description.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../src/db/section_description"); diff --git a/packages/cms/db/section_subtitle.js b/packages/cms/db/section_subtitle.js deleted file mode 100644 index 21daddab1..000000000 --- a/packages/cms/db/section_subtitle.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("../src/db/section_subtitle"); diff --git a/packages/cms/src/db/profile.js b/packages/cms/src/db/profile.js index a874a09b5..d2ee5b869 100644 --- a/packages/cms/src/db/profile.js +++ b/packages/cms/src/db/profile.js @@ -33,9 +33,7 @@ module.exports = function(sequelize, db) { ); p.associate = models => { - p.hasMany(models.section, {foreignKey: "profile_id", sourceKey: "id", as: "sections"}); - p.hasMany(models.profile_stat, {foreignKey: "profile_id", sourceKey: "id", as: "stats"}); - p.hasMany(models.profile_footnote, {foreignKey: "profile_id", sourceKey: "id", as: "footnotes"}); + p.hasMany(models.topic, {foreignKey: "profile_id", sourceKey: "id", as: "topics"}); p.hasMany(models.generator, {foreignKey: "profile_id", sourceKey: "id", as: "generators"}); p.hasMany(models.materializer, {foreignKey: "profile_id", sourceKey: "id", as: "materializers"}); }; diff --git a/packages/cms/src/db/profile_footnote.js b/packages/cms/src/db/profile_footnote.js deleted file mode 100644 index 94521ee1b..000000000 --- a/packages/cms/src/db/profile_footnote.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function(sequelize, db) { - - const p = sequelize.define("profile_footnote", - { - id: { - type: db.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: { - type: db.STRING, - defaultValue: "New Title" - }, - description: { - type: db.TEXT, - defaultValue: "New Footnote" - }, - profile_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "profile", - key: "id" - } - }, - ordering: db.INTEGER - }, - { - freezeTableName: true, - timestamps: false - } - ); - - return p; - -}; diff --git a/packages/cms/src/db/profile_stat.js b/packages/cms/src/db/profile_stat.js deleted file mode 100644 index 58522899f..000000000 --- a/packages/cms/src/db/profile_stat.js +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = function(sequelize, db) { - - const s = sequelize.define("profile_stat", - { - id: { - type: db.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: { - type: db.STRING, - defaultValue: "New Stat" - }, - subtitle: { - type: db.STRING, - defaultValue: "New Subtitle" - }, - value: { - type: db.STRING, - defaultValue: "New Value" - }, - profile_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "profile", - key: "id" - } - }, - tooltip: { - type: db.TEXT, - defaultValue: "New Tooltip" - }, - allowed: { - type: db.STRING, - defaultValue: "always" - }, - ordering: db.INTEGER - }, - { - freezeTableName: true, - timestamps: false - } - ); - - return s; - -}; diff --git a/packages/cms/src/db/section.js b/packages/cms/src/db/section.js deleted file mode 100644 index 481439a63..000000000 --- a/packages/cms/src/db/section.js +++ /dev/null @@ -1,46 +0,0 @@ -module.exports = function(sequelize, db) { - - const s = sequelize.define("section", - { - id: { - type: db.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: { - type: db.STRING, - defaultValue: "New Section" - }, - slug: { - type: db.STRING, - defaultValue: "new-section-slug" - }, - profile_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "profile", - key: "id" - } - }, - ordering: db.INTEGER, - allowed: { - type: db.STRING, - defaultValue: "always" - } - }, - { - freezeTableName: true, - timestamps: false - } - ); - - s.associate = models => { - s.hasMany(models.topic, {foreignKey: "section_id", sourceKey: "id", as: "topics"}); - s.hasMany(models.section_subtitle, {foreignKey: "section_id", sourceKey: "id", as: "subtitles"}); - s.hasMany(models.section_description, {foreignKey: "section_id", sourceKey: "id", as: "descriptions"}); - }; - - return s; - -}; diff --git a/packages/cms/src/db/section_description.js b/packages/cms/src/db/section_description.js deleted file mode 100644 index 72a0426b2..000000000 --- a/packages/cms/src/db/section_description.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function(sequelize, db) { - - const s = sequelize.define("section_description", - { - id: { - type: db.INTEGER, - primaryKey: true, - autoIncrement: true - }, - description: { - type: db.TEXT, - defaultValue: "New Description" - }, - section_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "section", - key: "id" - } - }, - allowed: { - type: db.STRING, - defaultValue: "always" - }, - ordering: db.INTEGER - }, - { - freezeTableName: true, - timestamps: false - } - ); - - return s; - -}; diff --git a/packages/cms/src/db/section_subtitle.js b/packages/cms/src/db/section_subtitle.js deleted file mode 100644 index 98760128d..000000000 --- a/packages/cms/src/db/section_subtitle.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function(sequelize, db) { - - const s = sequelize.define("section_subtitle", - { - id: { - type: db.INTEGER, - primaryKey: true, - autoIncrement: true - }, - subtitle: { - type: db.TEXT, - defaultValue: "New Subtitle" - }, - section_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "section", - key: "id" - } - }, - allowed: { - type: db.STRING, - defaultValue: "always" - }, - ordering: db.INTEGER - }, - { - freezeTableName: true, - timestamps: false - } - ); - - return s; - -}; diff --git a/packages/cms/src/db/topic.js b/packages/cms/src/db/topic.js index 19d36fe78..9b1829d36 100644 --- a/packages/cms/src/db/topic.js +++ b/packages/cms/src/db/topic.js @@ -15,11 +15,11 @@ module.exports = function(sequelize, db) { type: db.STRING, defaultValue: "new-topic-slug" }, - section_id: { + profile_id: { type: db.INTEGER, onDelete: "cascade", references: { - model: "section", + model: "profile", key: "id" } }, From e030c7bf95ebda3f4ac34fba7a7c73b5f13b0fff Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 15 Jan 2019 16:36:56 -0500 Subject: [PATCH 02/48] removes sections from cms and mortar route --- packages/cms/src/api/cmsRoute.js | 85 ++++----------------- packages/cms/src/api/mortarRoute.js | 55 +++++-------- packages/cms/src/profile/ProfileBuilder.jsx | 1 - 3 files changed, 32 insertions(+), 109 deletions(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index e2aa20ae4..868c2e361 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -19,14 +19,9 @@ const isEnabled = (req, res, next) => { const profileReqTreeOnly = { attributes: ["id", "title", "slug", "dimension", "ordering"], - include: [ - { - association: "sections", attributes: ["id", "title", "slug", "ordering", "profile_id"], - include: [ - {association: "topics", attributes: ["id", "title", "slug", "ordering", "section_id", "type"]} - ] - } - ] + include: [{ + association: "topics", attributes: ["id", "title", "slug", "ordering", "profile_id", "type"] + }] }; const storyReqTreeOnly = { @@ -45,9 +40,7 @@ const formatterReqTreeOnly = { const profileReqProfileOnly = { include: [ {association: "generators", attributes: ["id", "name"]}, - {association: "materializers", attributes: ["id", "name", "ordering"]}, - {association: "stats", attributes: ["id", "ordering"]}, - {association: "footnotes", attributes: ["id", "ordering"]} + {association: "materializers", attributes: ["id", "name", "ordering"]} ] }; @@ -59,13 +52,6 @@ const storyReqStoryOnly = { ] }; -const sectionReqSectionOnly = { - include: [ - {association: "subtitles", attributes: ["id", "ordering"]}, - {association: "descriptions", attributes: ["id", "ordering"]} - ] -}; - const topicReqTopicOnly = { include: [ {association: "subtitles", attributes: ["id", "ordering"]}, @@ -92,8 +78,7 @@ const storyTopicReqStoryTopicOnly = { */ const cmsTables = [ "author", "formatter", "generator", "materializer", "profile", - "profile_footnote", "profile_stat", "section", "section_description", - "section_subtitle", "selector", "story", "story_description", "story_footnote", "storytopic", + "selector", "story", "story_description", "story_footnote", "storytopic", "storytopic_description", "storytopic_stat", "storytopic_subtitle", "storytopic_visualization", "topic", "topic_description", "topic_stat", "topic_subtitle", "topic_visualization" ]; @@ -124,10 +109,7 @@ const sortProfileTree = (db, profiles) => { profiles = profiles.map(p => p.toJSON()); profiles = flatSort(db.profile, profiles); profiles.forEach(p => { - p.sections = flatSort(db.section, p.sections); - p.sections.forEach(s => { - s.topics = flatSort(db.topic, s.topics); - }); + p.topics = flatSort(db.topic, p.topics); }); return profiles; }; @@ -144,8 +126,6 @@ const sortStoryTree = (db, stories) => { const sortProfile = (db, profile) => { profile = profile.toJSON(); profile.materializers = flatSort(db.materializer, profile.materializers); - profile.stats = flatSort(db.profile_stat, profile.stats); - profile.footnotes = flatSort(db.profile_footnote, profile.footnotes); return profile; }; @@ -157,13 +137,6 @@ const sortStory = (db, story) => { return story; }; -const sortSection = (db, section) => { - section = section.toJSON(); - section.subtitles = flatSort(db.section_subtitle, section.subtitles); - section.descriptions = flatSort(db.section_description, section.descriptions); - return section; -}; - const sortTopic = (db, topic) => { topic = topic.toJSON(); topic.subtitles = flatSort(db.topic_subtitle, topic.subtitles); @@ -312,13 +285,6 @@ module.exports = function(app) { res.json(sortStory(db, story)).end(); }); - app.get("/api/cms/section/get/:id", async(req, res) => { - const {id} = req.params; - const reqObj = Object.assign({}, sectionReqSectionOnly, {where: {id}}); - const section = await db.section.findOne(reqObj); - res.json(sortSection(db, section)).end(); - }); - app.get("/api/cms/topic/get/:id", async(req, res) => { const {id} = req.params; const reqObj = Object.assign({}, topicReqTopicOnly, {where: {id}}); @@ -353,7 +319,7 @@ module.exports = function(app) { // Top-level tables have their own special gets, so exclude them from the "simple" gets const getList = cmsTables.filter(tableName => - !["profile", "section", "topic", "story", "storytopic"].includes(tableName) + !["profile", "topic", "story", "storytopic"].includes(tableName) ); getList.forEach(ref => { @@ -374,13 +340,11 @@ module.exports = function(app) { app.post("/api/cms/profile/newScaffold", isEnabled, (req, res) => { const profileData = req.body; db.profile.create({slug: profileData.slug, ordering: profileData.ordering, dimension: profileData.dimName}).then(profile => { - db.section.create({ordering: 0, profile_id: profile.id}).then(section => { - db.topic.create({ordering: 0, section_id: section.id}).then(() => { - db.profile.findAll(profileReqTreeOnly).then(profiles => { - profiles = sortProfileTree(db, profiles); - populateSearch(profileData, db); - res.json(profiles).end(); - }); + db.topic.create({ordering: 0, profile_id: profile.id}).then(() => { + db.profile.findAll(profileReqTreeOnly).then(profiles => { + profiles = sortProfileTree(db, profiles); + populateSearch(profileData, db); + res.json(profiles).end(); }); }); }); @@ -401,8 +365,6 @@ module.exports = function(app) { * and "parent" refers to the foreign key that need be referenced in the associated where clause. */ const deleteList = [ - {elements: ["profile_footnote", "profile_stat"], parent: "profile_id"}, - {elements: ["section_description", "section_subtitle"], parent: "section_id"}, {elements: ["author", "story_description", "story_footnote"], parent: "story_id"}, {elements: ["topic_subtitle", "topic_description", "topic_stat", "topic_visualization"], parent: "topic_id"} ]; @@ -487,30 +449,11 @@ module.exports = function(app) { }); }); - app.delete("/api/cms/section/delete", isEnabled, (req, res) => { - db.section.findOne({where: {id: req.query.id}}).then(row => { - db.section.update({ordering: sequelize.literal("ordering -1")}, {where: {profile_id: row.profile_id, ordering: {[Op.gt]: row.ordering}}}).then(() => { - db.section.destroy({where: {id: req.query.id}}).then(() => { - db.section.findAll({ - where: {profile_id: row.profile_id}, - attributes: ["id", "title", "slug", "ordering", "profile_id"], - order: [["ordering", "ASC"]], - include: [ - {association: "topics", attributes: ["id", "title", "slug", "ordering", "section_id"]} - ] - }).then(rows => { - res.json(rows).end(); - }); - }); - }); - }); - }); - app.delete("/api/cms/topic/delete", isEnabled, (req, res) => { db.topic.findOne({where: {id: req.query.id}}).then(row => { - db.topic.update({ordering: sequelize.literal("ordering -1")}, {where: {section_id: row.section_id, ordering: {[Op.gt]: row.ordering}}}).then(() => { + db.topic.update({ordering: sequelize.literal("ordering -1")}, {where: {profile_id: row.profile_id, ordering: {[Op.gt]: row.ordering}}}).then(() => { db.topic.destroy({where: {id: req.query.id}}).then(() => { - db.topic.findAll({where: {section_id: row.section_id}, attributes: ["id", "title", "slug", "ordering", "section_id", "type"], order: [["ordering", "ASC"]]}).then(rows => { + db.topic.findAll({where: {profile_id: row.profile_id}, attributes: ["id", "title", "slug", "ordering", "profile_id", "type"], order: [["ordering", "ASC"]]}).then(rows => { res.json(rows).end(); }); }); diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index cdf449aa5..1ed0fc200 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -19,27 +19,16 @@ const searchMap = { }; const profileReq = { - include: [ - {association: "stats", separate: true}, - {association: "footnotes", separate: true}, - { - association: "sections", separate: true, - include: [ - {association: "subtitles", separate: true}, - {association: "descriptions", separate: true}, - { - association: "topics", separate: true, - include: [ - {association: "subtitles", separate: true}, - {association: "descriptions", separate: true}, - {association: "visualizations", separate: true}, - {association: "stats", separate: true}, - {association: "selectors", separate: true} - ] - } - ] - } - ] + include: [{ + association: "topics", separate: true, + include: [ + {association: "subtitles", separate: true}, + {association: "descriptions", separate: true}, + {association: "visualizations", separate: true}, + {association: "stats", separate: true}, + {association: "selectors", separate: true} + ] + }] }; const topicReq = [ @@ -84,22 +73,14 @@ const sorter = (a, b) => a.ordering - b.ordering; // Using nested ORDER BY in the massive includes is incredibly difficult so do it manually here. Eventually move it up to the query. const sortProfile = profile => { profile = profile.toJSON(); - if (profile.stats) profile.stats.sort(sorter); - if (profile.sections) { - profile.sections.sort(sorter); - profile.sections.map(section => { - if (section.subtitles) section.subtitles.sort(sorter); - if (section.descriptions) section.descriptions.sort(sorter); - if (section.topics) { - section.topics.sort(sorter); - section.topics.map(topic => { - if (topic.subtitles) topic.subtitles.sort(sorter); - if (topic.selectors) topic.selectors.sort(sorter); - if (topic.stats) topic.stats.sort(sorter); - if (topic.descriptions) topic.descriptions.sort(sorter); - if (topic.visualizations) topic.visualizations.sort(sorter); - }); - } + if (profile.topics) { + profile.topics.sort(sorter); + profile.topics.forEach(topic => { + if (topic.subtitles) topic.subtitles.sort(sorter); + if (topic.selectors) topic.selectors.sort(sorter); + if (topic.stats) topic.stats.sort(sorter); + if (topic.descriptions) topic.descriptions.sort(sorter); + if (topic.visualizations) topic.visualizations.sort(sorter); }); } return profile; diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index 3c1b10b16..dd830d009 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -4,7 +4,6 @@ import {connect} from "react-redux"; import {NonIdealState, Tree, Dialog, Intent, Alert} from "@blueprintjs/core"; import NewProfile from "./NewProfile"; import ProfileEditor from "./ProfileEditor"; -import SectionEditor from "./SectionEditor"; import TopicEditor from "./TopicEditor"; import PropTypes from "prop-types"; import Search from "../components/Search/Search"; From c6ca4cb84facf79e7098c501f7c33717ca5ca2b6 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 15 Jan 2019 16:52:59 -0500 Subject: [PATCH 03/48] removes sections from profilebuilder --- packages/cms/src/profile/ProfileBuilder.jsx | 170 ++++---------------- 1 file changed, 35 insertions(+), 135 deletions(-) diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index dd830d009..b906b3c2f 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -74,24 +74,14 @@ class ProfileBuilder extends Component { masterSlug: p.slug, masterDimension: p.dimension, data: p, - childNodes: p.sections.map(s => ({ - id: `section${s.id}`, - hasCaret: true, - label: this.decode(stripHTML(s.title)), - itemType: "section", + childNodes: p.topics.map(t => ({ + id: `topic${t.id}`, + hasCaret: false, + label: this.decode(stripHTML(t.title)), + itemType: "topic", masterSlug: p.slug, masterDimension: p.dimension, - data: s, - childNodes: s.topics.map(t => ({ - id: `topic${t.id}`, - hasCaret: false, - iconName: topicIcons[t.type] || "help", - label: this.decode(stripHTML(t.title)), - itemType: "topic", - masterSlug: p.slug, - masterDimension: p.dimension, - data: t - })) + data: t })) })); if (!openNode) { @@ -114,8 +104,7 @@ class ProfileBuilder extends Component { const sorter = (a, b) => a.data.ordering - b.data.ordering; n = this.locateNode(n.itemType, n.data.id); let parentArray; - if (n.itemType === "topic") parentArray = this.locateNode("section", n.data.section_id).childNodes; - if (n.itemType === "section") parentArray = this.locateNode("profile", n.data.profile_id).childNodes; + if (n.itemType === "topic") parentArray = this.locateNode("profile", n.data.profile_id).childNodes; if (n.itemType === "profile") parentArray = nodes; if (dir === "up") { const old = parentArray.find(node => node.data.ordering === n.data.ordering - 1); @@ -144,19 +133,15 @@ class ProfileBuilder extends Component { n = this.locateNode(n.itemType, n.data.id); let parent; let parentArray; - // For topics and sections, it is sufficient to find the Actual Parent - this parent will have + // For topics, it is sufficient to find the Actual Parent - this parent will have // a masterSlug that the newly added item should share if (n.itemType === "topic") { - parent = this.locateNode("section", n.data.section_id); - parentArray = parent.childNodes; - } - else if (n.itemType === "section") { parent = this.locateNode("profile", n.data.profile_id); parentArray = parent.childNodes; } // However, if the user is adding a new profile, there is no top-level profile parent whose slug trickles down, // therefore we must make a small fake object whose only prop is masterSlug. This is used only so that when we - // build the new Profile Tree Object, we can set the masterSlug of all three new elements (profile, section, topic) + // build the new Profile Tree Object, we can set the masterSlug of both new elements (profile, topic) // to "parent.masterSlug" and have that correctly reflect the stub object. else if (n.itemType === "profile") { parent = {masterSlug: "new-profile-slug"}; @@ -181,7 +166,7 @@ class ProfileBuilder extends Component { } } - // New Topics and Sections need to inherit their masterDimension from their parent. + // New Topics need to inherit their masterDimension from their parent. // This is not necessary for profiles, as profiles are now only added through the // search scaffold (see onCreateProfile) @@ -190,21 +175,11 @@ class ProfileBuilder extends Component { itemType: "topic", data: {} }; - objTopic.data.section_id = n.data.section_id; + objTopic.data.profile_id = n.data.profile_id; objTopic.data.ordering = loc; objTopic.masterSlug = parent.masterSlug; objTopic.masterDimension = parent.masterDimension; - const objSection = { - hasCaret: true, - itemType: "section", - data: {} - }; - objSection.data.profile_id = n.data.profile_id; - objSection.data.ordering = loc; - objSection.masterSlug = parent.masterSlug; - objSection.masterDimension = parent.masterDimension; - const objProfile = { hasCaret: true, itemType: "profile", @@ -218,22 +193,15 @@ class ProfileBuilder extends Component { if (n.itemType === "topic") { obj = objTopic; } - if (n.itemType === "section") { - obj = objSection; - objTopic.data.ordering = 0; - obj.childNodes = [objTopic]; - } if (n.itemType === "profile") { obj = objProfile; - objSection.data.ordering = 0; objTopic.data.ordering = 0; - objSection.childNodes = [objTopic]; - obj.childNodes = [objSection]; + obj.childNodes = [objTopic]; } + if (obj) { const profilePath = "/api/cms/profile/new"; - const sectionPath = "/api/cms/section/new"; const topicPath = "/api/cms/topic/new"; if (n.itemType === "topic") { @@ -242,7 +210,7 @@ class ProfileBuilder extends Component { obj.id = `topic${topic.data.id}`; obj.data = topic.data; obj.label = varSwap(this.decode(stripHTML(obj.data.title)), formatters, variables); - const parent = this.locateNode("section", obj.data.section_id); + const parent = this.locateNode("profile", obj.data.profile_id); parent.childNodes.push(obj); parent.childNodes.sort((a, b) => a.data.ordering - b.data.ordering); this.setState({nodes}, this.handleNodeClick.bind(this, obj)); @@ -252,12 +220,13 @@ class ProfileBuilder extends Component { } }); } - else if (n.itemType === "section") { - axios.post(sectionPath, obj.data).then(section => { - obj.id = `section${section.data.id}`; - obj.data = section.data; - obj.label = varSwap(this.decode(stripHTML(obj.data.title)), formatters, variables); - objTopic.data.section_id = section.data.id; + else if (n.itemType === "profile") { + axios.post(profilePath, obj.data).then(profile => { + obj.id = `profile${profile.data.id}`; + obj.data = profile.data; + // obj.label = varSwap(this.decode(stripHTML(obj.data.title)), formatters, variables); + obj.label = obj.data.slug; + objTopic.data.profile_id = profile.data.id; axios.post(topicPath, objTopic.data).then(topic => { if (topic.status === 200) { objTopic.id = `topic${topic.data.id}`; @@ -269,39 +238,11 @@ class ProfileBuilder extends Component { this.setState({nodes}, this.handleNodeClick.bind(this, obj)); } else { - console.log("section error"); + console.log("profile error"); } }); }); } - - else if (n.itemType === "profile") { - axios.post(profilePath, obj.data).then(profile => { - obj.id = `profile${profile.data.id}`; - obj.data = profile.data; - obj.label = obj.data.slug; - objSection.data.profile_id = profile.data.id; - axios.post(sectionPath, objSection.data).then(section => { - objSection.id = `section${section.data.id}`; - objSection.data = section.data; - objSection.label = varSwap(this.decode(stripHTML(objSection.data.title)), formatters, variables); - objTopic.data.section_id = section.data.id; - axios.post(topicPath, objTopic.data).then(topic => { - if (topic.status === 200) { - objTopic.id = `topic${topic.data.id}`; - objTopic.data = topic.data; - objTopic.label = varSwap(this.decode(stripHTML(objTopic.data.title)), formatters, variables); - nodes.push(obj); - nodes.sort((a, b) => a.data.ordering - b.data.ordering); - this.setState({nodes}, this.handleNodeClick.bind(this, obj)); - } - else { - console.log("profile error"); - } - }); - }); - }); - } } } @@ -318,7 +259,7 @@ class ProfileBuilder extends Component { n = this.locateNode(n.itemType, n.data.id); // todo: instead of the piecemeal refreshes being done for each of these tiers - is it sufficient to run buildNodes again? if (n.itemType === "topic") { - const parent = this.locateNode("section", n.data.section_id); + const parent = this.locateNode("profile", n.data.profile_id); axios.delete("/api/cms/topic/delete", {params: {id: n.data.id}}).then(resp => { const topics = resp.data.map(topicData => ({ id: `topic${topicData.id}`, @@ -333,30 +274,6 @@ class ProfileBuilder extends Component { this.setState({nodes, nodeToDelete}, this.handleNodeClick.bind(this, parent.childNodes[0])); }); } - else if (n.itemType === "section") { - const parent = this.locateNode("profile", n.data.profile_id); - axios.delete("/api/cms/section/delete", {params: {id: n.data.id}}).then(resp => { - const sections = resp.data.map(sectionData => ({ - id: `section${sectionData.id}`, - hasCaret: true, - label: this.decode(stripHTML(sectionData.title)), - itemType: "section", - masterSlug: parent.masterSlug, - data: sectionData, - childNodes: sectionData.topics.map(t => ({ - id: `topic${t.id}`, - hasCaret: false, - iconName: topicIcons[t.type] || "help", - label: this.decode(stripHTML(t.title)), - itemType: "topic", - masterSlug: parent.masterSlug, - data: t - })) - })); - parent.childNodes = sections; - this.setState({nodes, nodeToDelete}, this.handleNodeClick.bind(this, parent.childNodes[0])); - }); - } else if (n.itemType === "profile") { axios.delete("/api/cms/profile/delete", {params: {id: n.data.id}}).then(resp => { const profiles = resp.data; @@ -369,8 +286,7 @@ class ProfileBuilder extends Component { node = this.locateNode(node.itemType, node.data.id); const {nodes, currentNode} = this.state; let parentLength = 0; - if (node.itemType === "topic") parentLength = this.locateNode("section", node.data.section_id).childNodes.length; - if (node.itemType === "section") parentLength = this.locateNode("profile", node.data.profile_id).childNodes.length; + if (node.itemType === "topic") parentLength = this.locateNode("profile", node.data.profile_id).childNodes.length; if (node.itemType === "profile") parentLength = nodes.length; if (!currentNode) { node.isSelected = true; @@ -433,7 +349,7 @@ class ProfileBuilder extends Component { } /** - * Given a node type (profile, section, topic) and an id, crawl down the tree and fetch a reference to the Tree node with that id + * Given a node type (profile, topic) and an id, crawl down the tree and fetch a reference to the Tree node with that id */ locateNode(type, id) { const {nodes} = this.state; @@ -441,18 +357,10 @@ class ProfileBuilder extends Component { if (type === "profile") { node = nodes.find(p => p.data.id === id); } - else if (type === "section") { - nodes.forEach(p => { - const attempt = p.childNodes.find(s => s.data.id === id); - if (attempt) node = attempt; - }); - } else if (type === "topic") { nodes.forEach(p => { - p.childNodes.forEach(s => { - const attempt = s.childNodes.find(t => t.data.id === id); - if (attempt) node = attempt; - }); + const attempt = p.childNodes.find(t => t.data.id === id); + if (attempt) node = attempt; }); } return node; @@ -470,7 +378,7 @@ class ProfileBuilder extends Component { const {formatters} = this.context; const variables = variablesHash[currentSlug] ? deepClone(variablesHash[currentSlug]) : null; const node = this.locateNode.bind(this)(type, id); - // Update the label based on the new value. If this is a section or a topic, this is the only thing needed + // Update the label based on the new value. If this is a topic, this is the only thing needed if (node) { node.data.title = newValue; // todo: determine if this could be merged with formatTreeVariables @@ -481,13 +389,9 @@ class ProfileBuilder extends Component { nodes = nodes.map(p => { p.masterSlug = newValue; p.data.slug = newValue; - p.childNodes = p.childNodes.map(s => { - s.masterSlug = newValue; - s.childNodes = s.childNodes.map(t => { - t.masterSlug = newValue; - return t; - }); - return s; + p.childNodes = p.childNodes.map(t => { + t.masterSlug = newValue; + return t; }); return p; }); @@ -516,13 +420,9 @@ class ProfileBuilder extends Component { const variables = variablesHash[currentSlug] ? deepClone(variablesHash[currentSlug]) : null; const p = this.locateProfileNodeBySlug(currentSlug); p.label = varSwap(p.data.slug, formatters, variables); - p.childNodes = p.childNodes.map(s => { - s.label = varSwap(this.decode(stripHTML(s.data.title)), formatters, variables); - s.childNodes = s.childNodes.map(t => { - t.label = varSwap(this.decode(stripHTML(t.data.title)), formatters, variables); - return t; - }); - return s; + p.childNodes = p.childNodes.map(t => { + t.label = varSwap(this.decode(stripHTML(t.data.title)), formatters, variables); + return t; }); this.setState({nodes}); } @@ -579,7 +479,7 @@ class ProfileBuilder extends Component { ; } - const editorTypes = {profile: ProfileEditor, section: SectionEditor, topic: TopicEditor}; + const editorTypes = {profile: ProfileEditor, topic: TopicEditor}; const Editor = currentNode ? editorTypes[currentNode.itemType] : null; return ( From f5288b8281e60677181bcdf7dae9f0daf23d57cc Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 15 Jan 2019 16:58:40 -0500 Subject: [PATCH 04/48] removes sectioneditor and old profile stats --- packages/cms/src/profile/ProfileEditor.jsx | 47 ----- packages/cms/src/profile/SectionEditor.css | 6 - packages/cms/src/profile/SectionEditor.jsx | 223 --------------------- 3 files changed, 276 deletions(-) delete mode 100644 packages/cms/src/profile/SectionEditor.css delete mode 100644 packages/cms/src/profile/SectionEditor.jsx diff --git a/packages/cms/src/profile/ProfileEditor.jsx b/packages/cms/src/profile/ProfileEditor.jsx index 65a653d08..e1391d0ed 100644 --- a/packages/cms/src/profile/ProfileEditor.jsx +++ b/packages/cms/src/profile/ProfileEditor.jsx @@ -207,53 +207,6 @@ class ProfileEditor extends Component { - {/* splash stats */} -

- Stats - -

-
-
- { minData.stats && minData.stats.map(s => - - )} -
-
- - {/* footnotes */} -

- Footnotes - -

-
- { minData.footnotes && minData.footnotes.map(f => - - )} -
); } diff --git a/packages/cms/src/profile/SectionEditor.css b/packages/cms/src/profile/SectionEditor.css deleted file mode 100644 index 7f20c4c08..000000000 --- a/packages/cms/src/profile/SectionEditor.css +++ /dev/null @@ -1,6 +0,0 @@ -#section-editor { - & .stats { - display: flex; - flex-wrap: wrap; - } -} diff --git a/packages/cms/src/profile/SectionEditor.jsx b/packages/cms/src/profile/SectionEditor.jsx deleted file mode 100644 index daa344beb..000000000 --- a/packages/cms/src/profile/SectionEditor.jsx +++ /dev/null @@ -1,223 +0,0 @@ -import axios from "axios"; -import React, {Component} from "react"; -import {Icon} from "@blueprintjs/core"; -import Loading from "components/Loading"; -import TextCard from "../components/cards/TextCard"; -import PropTypes from "prop-types"; -import "./SectionEditor.css"; - -const propMap = { - section_subtitle: "subtitles", - section_description: "descriptions" -}; - -class SectionEditor extends Component { - - constructor(props) { - super(props); - this.state = { - minData: null, - recompiling: true - }; - } - - componentDidMount() { - this.hitDB.bind(this)(false); - } - - componentDidUpdate(prevProps) { - if (prevProps.id !== this.props.id) { - this.hitDB.bind(this)(false); - } - if (prevProps.preview !== this.props.preview) { - this.hitDB.bind(this)(true); - } - } - - hitDB(force) { - axios.get(`/api/cms/section/get/${this.props.id}`).then(resp => { - this.setState({minData: resp.data, recompiling: true}, this.fetchVariables.bind(this, force)); - }); - } - - changeField(field, e) { - const {minData} = this.state; - minData[field] = e.target.value; - this.setState({minData}); - } - - chooseVariable(e) { - const {minData} = this.state; - minData.allowed = e.target.value; - this.setState({minData}, this.save.bind(this)); - } - - addItem(type) { - const {minData} = this.state; - const payload = {}; - payload.section_id = minData.id; - // todo: move this ordering out to axios (let the server concat it to the end) - payload.ordering = minData[propMap[type]].length; - axios.post(`/api/cms/${type}/new`, payload).then(resp => { - if (resp.status === 200) { - minData[propMap[type]].push({id: resp.data.id, ordering: resp.data.ordering}); - this.setState({minData}); - } - }); - } - - onDelete(type, newArray) { - const {minData} = this.state; - minData[propMap[type]] = newArray; - this.setState({minData}); - } - - onSave(minData) { - this.setState({recompiling: true}); - if (this.props.reportSave) this.props.reportSave("section", minData.id, minData.title); - } - - onMove() { - this.forceUpdate(); - } - - save() { - const {minData} = this.state; - axios.post("/api/cms/section/update", minData).then(resp => { - if (resp.status === 200) { - this.setState({isOpen: false}); - } - }); - } - - fetchVariables(force) { - const slug = this.props.masterSlug; - const id = this.props.preview; - if (this.props.fetchVariables) { - this.props.fetchVariables(slug, id, force, () => this.setState({recompiling: false})); - } - } - - render() { - - const {minData, recompiling} = this.state; - const {children, variables} = this.props; - - if (!minData || !variables) return ; - - const varOptions = [] - .concat(Object.keys(variables) - .filter(key => !key.startsWith("_")) - .sort((a, b) => a.localeCompare(b)) - .map(key => { - const value = variables[key]; - const type = typeof value; - const label = !["string", "number", "boolean"].includes(type) ? ` (${type})` : `: ${`${value}`.slice(0, 20)}${`${value}`.length > 20 ? "..." : ""}`; - return ; - })); - - return ( -
- {/* profile preview & variable status */} -
- {/* search profiles */} - {children} - {/* loading status */} -
- - { recompiling ? "Updating Variables" : "Variables Loaded" } -
-
- - {/* current section options */} -
- {/* change slug */} - - {/* visibility select */} - -
- - {/* section title */} - {/* TODO: move this into section options above */} -

- Section title - {/* */} -

-
- -
- - {/* subtitles */} -

- Subtitles - -

-
- { minData.subtitles && minData.subtitles.map(s => - - )} -
- - {/* descriptions */} -

- Descriptions - -

-
- { minData.descriptions && minData.descriptions.map(d => - - )} -
-
- ); - } -} - -SectionEditor.contextTypes = { - formatters: PropTypes.object -}; - -export default SectionEditor; From 5016f74dd895b0d4780fc7945dd4817507527f08 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 15 Jan 2019 17:00:41 -0500 Subject: [PATCH 05/48] removes section from profile scaffold --- packages/cms/app/pages/Profile.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/cms/app/pages/Profile.jsx b/packages/cms/app/pages/Profile.jsx index 8e8dca388..12c3136e2 100644 --- a/packages/cms/app/pages/Profile.jsx +++ b/packages/cms/app/pages/Profile.jsx @@ -23,16 +23,13 @@ class Profile extends Component { } render() { - const {sections, title} = this.props.profile; + const {topics, title} = this.props.profile; return (

- {sections.map(section => { - const {id, topics} = section; - return
-

- { topics.map(topic => ) } -

; + {topics.map(topic => { + const {slug} = topic; + return ; })}

); From ac3b68429019b41f207c4a7c9f4e1008a70e4b64 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 29 Jan 2019 19:30:44 -0500 Subject: [PATCH 06/48] adds content tables to db --- packages/cms/db/author_content.js | 1 + packages/cms/db/profile_content.js | 1 + packages/cms/db/story_content.js | 1 + packages/cms/db/story_description_content.js | 1 + packages/cms/db/story_footnote_content.js | 1 + packages/cms/db/storytopic_content.js | 1 + .../cms/db/storytopic_description_content.js | 1 + packages/cms/db/storytopic_stat_content.js | 1 + .../cms/db/storytopic_subtitle_content.js | 1 + packages/cms/db/topic_content.js | 1 + packages/cms/db/topic_description_content.js | 1 + packages/cms/db/topic_stat_content.js | 1 + packages/cms/db/topic_subtitle_content.js | 1 + packages/cms/src/db/author.js | 26 ++-------- packages/cms/src/db/author_content.js | 50 ++++++++++++++++++ packages/cms/src/db/profile.js | 15 +----- packages/cms/src/db/profile_content.js | 42 +++++++++++++++ packages/cms/src/db/story.js | 14 +---- packages/cms/src/db/story_content.js | 51 +++++++++++++++++++ packages/cms/src/db/story_description.js | 10 ++-- .../cms/src/db/story_description_content.js | 34 +++++++++++++ packages/cms/src/db/story_footnote.js | 12 ++--- packages/cms/src/db/story_footnote_content.js | 38 ++++++++++++++ packages/cms/src/db/storytopic.js | 7 +-- packages/cms/src/db/storytopic_content.js | 34 +++++++++++++ packages/cms/src/db/storytopic_description.js | 8 +-- .../src/db/storytopic_description_content.js | 34 +++++++++++++ packages/cms/src/db/storytopic_stat.js | 20 ++------ .../cms/src/db/storytopic_stat_content.js | 46 +++++++++++++++++ packages/cms/src/db/storytopic_subtitle.js | 8 +-- .../cms/src/db/storytopic_subtitle_content.js | 34 +++++++++++++ packages/cms/src/db/topic.js | 5 +- packages/cms/src/db/topic_content.js | 34 +++++++++++++ packages/cms/src/db/topic_description.js | 10 ++-- .../cms/src/db/topic_description_content.js | 34 +++++++++++++ packages/cms/src/db/topic_stat.js | 20 ++------ packages/cms/src/db/topic_stat_content.js | 46 +++++++++++++++++ packages/cms/src/db/topic_subtitle.js | 10 ++-- packages/cms/src/db/topic_subtitle_content.js | 34 +++++++++++++ 39 files changed, 570 insertions(+), 119 deletions(-) create mode 100644 packages/cms/db/author_content.js create mode 100644 packages/cms/db/profile_content.js create mode 100644 packages/cms/db/story_content.js create mode 100644 packages/cms/db/story_description_content.js create mode 100644 packages/cms/db/story_footnote_content.js create mode 100644 packages/cms/db/storytopic_content.js create mode 100644 packages/cms/db/storytopic_description_content.js create mode 100644 packages/cms/db/storytopic_stat_content.js create mode 100644 packages/cms/db/storytopic_subtitle_content.js create mode 100644 packages/cms/db/topic_content.js create mode 100644 packages/cms/db/topic_description_content.js create mode 100644 packages/cms/db/topic_stat_content.js create mode 100644 packages/cms/db/topic_subtitle_content.js create mode 100644 packages/cms/src/db/author_content.js create mode 100644 packages/cms/src/db/profile_content.js create mode 100644 packages/cms/src/db/story_content.js create mode 100644 packages/cms/src/db/story_description_content.js create mode 100644 packages/cms/src/db/story_footnote_content.js create mode 100644 packages/cms/src/db/storytopic_content.js create mode 100644 packages/cms/src/db/storytopic_description_content.js create mode 100644 packages/cms/src/db/storytopic_stat_content.js create mode 100644 packages/cms/src/db/storytopic_subtitle_content.js create mode 100644 packages/cms/src/db/topic_content.js create mode 100644 packages/cms/src/db/topic_description_content.js create mode 100644 packages/cms/src/db/topic_stat_content.js create mode 100644 packages/cms/src/db/topic_subtitle_content.js diff --git a/packages/cms/db/author_content.js b/packages/cms/db/author_content.js new file mode 100644 index 000000000..c592e2ce6 --- /dev/null +++ b/packages/cms/db/author_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/author_content"); diff --git a/packages/cms/db/profile_content.js b/packages/cms/db/profile_content.js new file mode 100644 index 000000000..39e5e0263 --- /dev/null +++ b/packages/cms/db/profile_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/profile_content"); diff --git a/packages/cms/db/story_content.js b/packages/cms/db/story_content.js new file mode 100644 index 000000000..29552554c --- /dev/null +++ b/packages/cms/db/story_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/story_content"); diff --git a/packages/cms/db/story_description_content.js b/packages/cms/db/story_description_content.js new file mode 100644 index 000000000..3aa5688f6 --- /dev/null +++ b/packages/cms/db/story_description_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/story_description_content"); diff --git a/packages/cms/db/story_footnote_content.js b/packages/cms/db/story_footnote_content.js new file mode 100644 index 000000000..cf09b8d79 --- /dev/null +++ b/packages/cms/db/story_footnote_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/story_footnote_content"); diff --git a/packages/cms/db/storytopic_content.js b/packages/cms/db/storytopic_content.js new file mode 100644 index 000000000..dbfbd3e73 --- /dev/null +++ b/packages/cms/db/storytopic_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/storytopic_content"); diff --git a/packages/cms/db/storytopic_description_content.js b/packages/cms/db/storytopic_description_content.js new file mode 100644 index 000000000..536cb0032 --- /dev/null +++ b/packages/cms/db/storytopic_description_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/storytopic_description_content"); diff --git a/packages/cms/db/storytopic_stat_content.js b/packages/cms/db/storytopic_stat_content.js new file mode 100644 index 000000000..0caa1c9fe --- /dev/null +++ b/packages/cms/db/storytopic_stat_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/storytopic_stat_content"); diff --git a/packages/cms/db/storytopic_subtitle_content.js b/packages/cms/db/storytopic_subtitle_content.js new file mode 100644 index 000000000..7582fa3fb --- /dev/null +++ b/packages/cms/db/storytopic_subtitle_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/storytopic_subtitle_content"); diff --git a/packages/cms/db/topic_content.js b/packages/cms/db/topic_content.js new file mode 100644 index 000000000..4b75f2215 --- /dev/null +++ b/packages/cms/db/topic_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/topic_content"); diff --git a/packages/cms/db/topic_description_content.js b/packages/cms/db/topic_description_content.js new file mode 100644 index 000000000..cdf81622d --- /dev/null +++ b/packages/cms/db/topic_description_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/topic_description_content"); diff --git a/packages/cms/db/topic_stat_content.js b/packages/cms/db/topic_stat_content.js new file mode 100644 index 000000000..f18fc3095 --- /dev/null +++ b/packages/cms/db/topic_stat_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/topic_stat_content"); diff --git a/packages/cms/db/topic_subtitle_content.js b/packages/cms/db/topic_subtitle_content.js new file mode 100644 index 000000000..9e38b1589 --- /dev/null +++ b/packages/cms/db/topic_subtitle_content.js @@ -0,0 +1 @@ +module.exports = require("../src/db/topic_subtitle_content"); diff --git a/packages/cms/src/db/author.js b/packages/cms/src/db/author.js index 1a151de49..6ac1ad318 100644 --- a/packages/cms/src/db/author.js +++ b/packages/cms/src/db/author.js @@ -7,22 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - name: { - type: db.STRING, - defaultValue: "New Author" - }, - title: { - type: db.STRING, - defaultValue: "New Title" - }, - image: { - type: db.STRING, - defaultValue: "New Image" - }, - twitter: { - type: db.STRING, - defaultValue: "New Twitter" - }, story_id: { type: db.INTEGER, onDelete: "cascade", @@ -31,11 +15,7 @@ module.exports = function(sequelize, db) { key: "id" } }, - ordering: db.INTEGER, - bio: { - type: db.TEXT, - defaultValue: "New Bio" - } + ordering: db.INTEGER }, { freezeTableName: true, @@ -43,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + a.associate = models => { + a.hasMany(models.author_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return a; }; diff --git a/packages/cms/src/db/author_content.js b/packages/cms/src/db/author_content.js new file mode 100644 index 000000000..26854a60b --- /dev/null +++ b/packages/cms/src/db/author_content.js @@ -0,0 +1,50 @@ +module.exports = function(sequelize, db) { + + const a = sequelize.define("author_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + name: { + type: db.STRING, + defaultValue: "New Author" + }, + title: { + type: db.STRING, + defaultValue: "New Title" + }, + image: { + type: db.STRING, + defaultValue: "New Image" + }, + twitter: { + type: db.STRING, + defaultValue: "New Twitter" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "author", + key: "id" + } + }, + bio: { + type: db.TEXT, + defaultValue: "New Bio" + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return a; + +}; diff --git a/packages/cms/src/db/profile.js b/packages/cms/src/db/profile.js index d2ee5b869..4486fa0cd 100644 --- a/packages/cms/src/db/profile.js +++ b/packages/cms/src/db/profile.js @@ -7,24 +7,12 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Profile" - }, - subtitle: { - type: db.TEXT, - defaultValue: "New Subtitle" - }, slug: { type: db.STRING, defaultValue: "new-profile-slug" }, ordering: db.INTEGER, - dimension: db.STRING, - label: { - type: db.STRING, - defaultValue: "New Profile Label" - } + dimension: db.STRING }, { freezeTableName: true, @@ -33,6 +21,7 @@ module.exports = function(sequelize, db) { ); p.associate = models => { + p.hasMany(models.profile_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); p.hasMany(models.topic, {foreignKey: "profile_id", sourceKey: "id", as: "topics"}); p.hasMany(models.generator, {foreignKey: "profile_id", sourceKey: "id", as: "generators"}); p.hasMany(models.materializer, {foreignKey: "profile_id", sourceKey: "id", as: "materializers"}); diff --git a/packages/cms/src/db/profile_content.js b/packages/cms/src/db/profile_content.js new file mode 100644 index 000000000..af96316d5 --- /dev/null +++ b/packages/cms/src/db/profile_content.js @@ -0,0 +1,42 @@ +module.exports = function(sequelize, db) { + + const p = sequelize.define("profile_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Profile" + }, + subtitle: { + type: db.TEXT, + defaultValue: "New Subtitle" + }, + label: { + type: db.STRING, + defaultValue: "New Profile Label" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "profile", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return p; + +}; diff --git a/packages/cms/src/db/story.js b/packages/cms/src/db/story.js index 30f505281..e74e080ea 100644 --- a/packages/cms/src/db/story.js +++ b/packages/cms/src/db/story.js @@ -7,18 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Story" - }, - subtitle: { - type: db.TEXT, - defaultValue: "New Subtitle" - }, - image: { - type: db.STRING, - defaultValue: "New Image" - }, ordering: db.INTEGER, slug: { type: db.STRING, @@ -36,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - // s.belongsToMany(models.authors, {through: "stories_authors", foreignKey: "story_id", otherKey: "author_id", as: "authors"}); + s.hasMany(models.story_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); s.hasMany(models.author, {foreignKey: "story_id", sourceKey: "id", as: "authors"}); s.hasMany(models.story_footnote, {foreignKey: "story_id", sourceKey: "id", as: "footnotes"}); s.hasMany(models.story_description, {foreignKey: "story_id", sourceKey: "id", as: "descriptions"}); diff --git a/packages/cms/src/db/story_content.js b/packages/cms/src/db/story_content.js new file mode 100644 index 000000000..84bbce613 --- /dev/null +++ b/packages/cms/src/db/story_content.js @@ -0,0 +1,51 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("story_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Story" + }, + subtitle: { + type: db.TEXT, + defaultValue: "New Subtitle" + }, + image: { + type: db.STRING, + defaultValue: "New Image" + }, + ordering: db.INTEGER, + slug: { + type: db.STRING, + defaultValue: "new-story-slug" + }, + date: { + type: db.DATE, + defaultValue: "2018-01-01 00:00:00" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "story", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/story_description.js b/packages/cms/src/db/story_description.js index 2596aef2d..31dfbeccc 100644 --- a/packages/cms/src/db/story_description.js +++ b/packages/cms/src/db/story_description.js @@ -6,11 +6,7 @@ module.exports = function(sequelize, db) { type: db.INTEGER, primaryKey: true, autoIncrement: true - }, - description: { - type: db.TEXT, - defaultValue: "New Description" - }, + }, story_id: { type: db.INTEGER, onDelete: "cascade", @@ -27,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.story_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/story_description_content.js b/packages/cms/src/db/story_description_content.js new file mode 100644 index 000000000..df80afcb8 --- /dev/null +++ b/packages/cms/src/db/story_description_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("story_description_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + description: { + type: db.TEXT, + defaultValue: "New Description" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "story_description", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/story_footnote.js b/packages/cms/src/db/story_footnote.js index d631a3bed..d1faacfa4 100644 --- a/packages/cms/src/db/story_footnote.js +++ b/packages/cms/src/db/story_footnote.js @@ -7,14 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Title" - }, - description: { - type: db.TEXT, - defaultValue: "New Footnote" - }, story_id: { type: db.INTEGER, onDelete: "cascade", @@ -31,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.story_footnote_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/story_footnote_content.js b/packages/cms/src/db/story_footnote_content.js new file mode 100644 index 000000000..77ae2da6a --- /dev/null +++ b/packages/cms/src/db/story_footnote_content.js @@ -0,0 +1,38 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("story_footnote_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Title" + }, + description: { + type: db.TEXT, + defaultValue: "New Footnote" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "story_footnote", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/storytopic.js b/packages/cms/src/db/storytopic.js index 503b6b187..87a7f8a80 100644 --- a/packages/cms/src/db/storytopic.js +++ b/packages/cms/src/db/storytopic.js @@ -6,11 +6,7 @@ module.exports = function(sequelize, db) { type: db.INTEGER, primaryKey: true, autoIncrement: true - }, - title: { - type: db.STRING, - defaultValue: "New StoryTopic" - }, + }, slug: { type: db.STRING, defaultValue: "new-storytopic-slug" @@ -36,6 +32,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { + s.hasMany(models.storytopic_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); s.hasMany(models.storytopic_description, {foreignKey: "storytopic_id", sourceKey: "id", as: "descriptions"}); s.hasMany(models.storytopic_stat, {foreignKey: "storytopic_id", sourceKey: "id", as: "stats"}); s.hasMany(models.storytopic_subtitle, {foreignKey: "storytopic_id", sourceKey: "id", as: "subtitles"}); diff --git a/packages/cms/src/db/storytopic_content.js b/packages/cms/src/db/storytopic_content.js new file mode 100644 index 000000000..28ff52390 --- /dev/null +++ b/packages/cms/src/db/storytopic_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("storytopic_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New StoryTopic" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "storytopic", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/storytopic_description.js b/packages/cms/src/db/storytopic_description.js index 700a358bc..98bf23eb6 100644 --- a/packages/cms/src/db/storytopic_description.js +++ b/packages/cms/src/db/storytopic_description.js @@ -7,10 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - description: { - type: db.TEXT, - defaultValue: "New Description" - }, storytopic_id: { type: db.INTEGER, onDelete: "cascade", @@ -27,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.storytopic_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/storytopic_description_content.js b/packages/cms/src/db/storytopic_description_content.js new file mode 100644 index 000000000..97551e305 --- /dev/null +++ b/packages/cms/src/db/storytopic_description_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("storytopic_description_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + description: { + type: db.TEXT, + defaultValue: "New Description" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "storytopic_description", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/storytopic_stat.js b/packages/cms/src/db/storytopic_stat.js index ac8280a8b..f10c83604 100644 --- a/packages/cms/src/db/storytopic_stat.js +++ b/packages/cms/src/db/storytopic_stat.js @@ -7,18 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Stat" - }, - subtitle: { - type: db.STRING, - defaultValue: "New Subtitle" - }, - value: { - type: db.STRING, - defaultValue: "New Value" - }, storytopic_id: { type: db.INTEGER, onDelete: "cascade", @@ -27,10 +15,6 @@ module.exports = function(sequelize, db) { key: "id" } }, - tooltip: { - type: db.STRING, - defaultValue: "New Tooltip" - }, ordering: db.INTEGER }, { @@ -39,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.storytopic_stat_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/storytopic_stat_content.js b/packages/cms/src/db/storytopic_stat_content.js new file mode 100644 index 000000000..5ce816d96 --- /dev/null +++ b/packages/cms/src/db/storytopic_stat_content.js @@ -0,0 +1,46 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("storytopic_stat_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Stat" + }, + subtitle: { + type: db.STRING, + defaultValue: "New Subtitle" + }, + value: { + type: db.STRING, + defaultValue: "New Value" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "storytopic_stat", + key: "id" + } + }, + tooltip: { + type: db.STRING, + defaultValue: "New Tooltip" + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/storytopic_subtitle.js b/packages/cms/src/db/storytopic_subtitle.js index eb8f945ce..e28a93e60 100644 --- a/packages/cms/src/db/storytopic_subtitle.js +++ b/packages/cms/src/db/storytopic_subtitle.js @@ -7,10 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - subtitle: { - type: db.TEXT, - defaultValue: "New Subtitle" - }, storytopic_id: { type: db.INTEGER, onDelete: "cascade", @@ -27,6 +23,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.storytopic_subtitle_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/storytopic_subtitle_content.js b/packages/cms/src/db/storytopic_subtitle_content.js new file mode 100644 index 000000000..23ff58034 --- /dev/null +++ b/packages/cms/src/db/storytopic_subtitle_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("storytopic_subtitle_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + subtitle: { + type: db.TEXT, + defaultValue: "New Subtitle" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "storytopic_subtitle", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/topic.js b/packages/cms/src/db/topic.js index 9b1829d36..e63a46911 100644 --- a/packages/cms/src/db/topic.js +++ b/packages/cms/src/db/topic.js @@ -7,10 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Topic" - }, slug: { type: db.STRING, defaultValue: "new-topic-slug" @@ -40,6 +36,7 @@ module.exports = function(sequelize, db) { ); t.associate = models => { + t.hasMany(models.topic_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); t.hasMany(models.topic_visualization, {foreignKey: "topic_id", sourceKey: "id", as: "visualizations"}); t.hasMany(models.topic_stat, {foreignKey: "topic_id", sourceKey: "id", as: "stats"}); t.hasMany(models.topic_subtitle, {foreignKey: "topic_id", sourceKey: "id", as: "subtitles"}); diff --git a/packages/cms/src/db/topic_content.js b/packages/cms/src/db/topic_content.js new file mode 100644 index 000000000..587c29afc --- /dev/null +++ b/packages/cms/src/db/topic_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const t = sequelize.define("topic_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Topic" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "topic", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return t; + +}; diff --git a/packages/cms/src/db/topic_description.js b/packages/cms/src/db/topic_description.js index 13952dce7..c6d2f4c44 100644 --- a/packages/cms/src/db/topic_description.js +++ b/packages/cms/src/db/topic_description.js @@ -6,11 +6,7 @@ module.exports = function(sequelize, db) { type: db.INTEGER, primaryKey: true, autoIncrement: true - }, - description: { - type: db.TEXT, - defaultValue: "New Description" - }, + }, topic_id: { type: db.INTEGER, onDelete: "cascade", @@ -31,6 +27,10 @@ module.exports = function(sequelize, db) { } ); + t.associate = models => { + t.hasMany(models.topic_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return t; }; diff --git a/packages/cms/src/db/topic_description_content.js b/packages/cms/src/db/topic_description_content.js new file mode 100644 index 000000000..987dbe116 --- /dev/null +++ b/packages/cms/src/db/topic_description_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const t = sequelize.define("topic_description_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + description: { + type: db.TEXT, + defaultValue: "New Description" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "topic_description", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return t; + +}; diff --git a/packages/cms/src/db/topic_stat.js b/packages/cms/src/db/topic_stat.js index 83c99f1e5..abad287e4 100644 --- a/packages/cms/src/db/topic_stat.js +++ b/packages/cms/src/db/topic_stat.js @@ -7,18 +7,6 @@ module.exports = function(sequelize, db) { primaryKey: true, autoIncrement: true }, - title: { - type: db.STRING, - defaultValue: "New Stat" - }, - subtitle: { - type: db.STRING, - defaultValue: "New Subtitle" - }, - value: { - type: db.STRING, - defaultValue: "New Value" - }, topic_id: { type: db.INTEGER, onDelete: "cascade", @@ -27,10 +15,6 @@ module.exports = function(sequelize, db) { key: "id" } }, - tooltip: { - type: db.TEXT, - defaultValue: "New Tooltip" - }, allowed: { type: db.STRING, defaultValue: "always" @@ -43,6 +27,10 @@ module.exports = function(sequelize, db) { } ); + s.associate = models => { + s.hasMany(models.topic_stat_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return s; }; diff --git a/packages/cms/src/db/topic_stat_content.js b/packages/cms/src/db/topic_stat_content.js new file mode 100644 index 000000000..ab0ed35fa --- /dev/null +++ b/packages/cms/src/db/topic_stat_content.js @@ -0,0 +1,46 @@ +module.exports = function(sequelize, db) { + + const s = sequelize.define("topic_stat_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + title: { + type: db.STRING, + defaultValue: "New Stat" + }, + subtitle: { + type: db.STRING, + defaultValue: "New Subtitle" + }, + value: { + type: db.STRING, + defaultValue: "New Value" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "topic_stat", + key: "id" + } + }, + tooltip: { + type: db.TEXT, + defaultValue: "New Tooltip" + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return s; + +}; diff --git a/packages/cms/src/db/topic_subtitle.js b/packages/cms/src/db/topic_subtitle.js index 476deeded..f764243ca 100644 --- a/packages/cms/src/db/topic_subtitle.js +++ b/packages/cms/src/db/topic_subtitle.js @@ -6,11 +6,7 @@ module.exports = function(sequelize, db) { type: db.INTEGER, primaryKey: true, autoIncrement: true - }, - subtitle: { - type: db.TEXT, - defaultValue: "New Subtitle" - }, + }, topic_id: { type: db.INTEGER, onDelete: "cascade", @@ -31,6 +27,10 @@ module.exports = function(sequelize, db) { } ); + t.associate = models => { + t.hasMany(models.topic_subtitle_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + }; + return t; }; diff --git a/packages/cms/src/db/topic_subtitle_content.js b/packages/cms/src/db/topic_subtitle_content.js new file mode 100644 index 000000000..accf7b1be --- /dev/null +++ b/packages/cms/src/db/topic_subtitle_content.js @@ -0,0 +1,34 @@ +module.exports = function(sequelize, db) { + + const t = sequelize.define("topic_subtitle_content", + { + id: { + type: db.INTEGER, + primaryKey: true + }, + lang: { + type: db.STRING, + primaryKey: true + }, + subtitle: { + type: db.TEXT, + defaultValue: "New Subtitle" + }, + parent_id: { + type: db.INTEGER, + onDelete: "cascade", + references: { + model: "topic_subtitle", + key: "id" + } + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + return t; + +}; From bbe1a280c5e442066f4df10025da2abb7992bf29 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Tue, 29 Jan 2019 19:39:31 -0500 Subject: [PATCH 07/48] moves unnecessary props from story_content --- packages/cms/src/db/story_content.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/cms/src/db/story_content.js b/packages/cms/src/db/story_content.js index 84bbce613..81c1a8eaa 100644 --- a/packages/cms/src/db/story_content.js +++ b/packages/cms/src/db/story_content.js @@ -21,15 +21,6 @@ module.exports = function(sequelize, db) { image: { type: db.STRING, defaultValue: "New Image" - }, - ordering: db.INTEGER, - slug: { - type: db.STRING, - defaultValue: "new-story-slug" - }, - date: { - type: db.DATE, - defaultValue: "2018-01-01 00:00:00" }, parent_id: { type: db.INTEGER, From 2aca743a01f883189583f4c5d05f14690a009a24 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 30 Jan 2019 14:15:48 -0500 Subject: [PATCH 08/48] updates cms/mortar routes to respect content --- packages/cms/src/api/cmsRoute.js | 51 ++++++++++++++++------ packages/cms/src/api/mortarRoute.js | 65 ++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index 868c2e361..3f9c12c09 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -18,17 +18,17 @@ const isEnabled = (req, res, next) => { }; const profileReqTreeOnly = { - attributes: ["id", "title", "slug", "dimension", "ordering"], + attributes: ["id", "slug", "dimension", "ordering"], include: [{ - association: "topics", attributes: ["id", "title", "slug", "ordering", "profile_id", "type"] + association: "topics", attributes: ["id", "slug", "ordering", "profile_id", "type"] }] }; const storyReqTreeOnly = { - attributes: ["id", "title", "ordering"], + attributes: ["id", "slug", "ordering"], include: [ { - association: "storytopics", attributes: ["id", "title", "slug", "ordering", "story_id", "type"] + association: "storytopics", attributes: ["id", "slug", "ordering", "story_id", "type"] } ] }; @@ -83,6 +83,18 @@ const cmsTables = [ "topic", "topic_description", "topic_stat", "topic_subtitle", "topic_visualization" ]; +/** + * Some tables are translated to different languages using a corresponding "content" table, like "profile_content". + * As such, some of the following functions need to take compound actions, e.g., insert a metadata record into + * profile, THEN insert the "real" data into "profile_content." This list (subset of cmsTables) represents those + * tables that need corresponding _content updates. + */ + +const contentTables = [ + "author", "profile", "story", "story_description", "story_footnote", "storytopic", "storytopic_description", + "storytopic_stat", "storytopic_subtitle", "topic", "topic_description", "topic_stat", "topic_subtitle" +]; + const sorter = (a, b) => a.ordering - b.ordering; /** @@ -324,7 +336,7 @@ module.exports = function(app) { getList.forEach(ref => { app.get(`/api/cms/${ref}/get/:id`, (req, res) => { - db[ref].findOne({where: {id: req.params.id}}).then(u => res.json(u).end()); + db[ref].findOne({where: {id: req.params.id}, include: {association: "content"}}).then(u => res.json(u).end()); }); }); @@ -333,18 +345,29 @@ module.exports = function(app) { const newList = cmsTables; newList.forEach(ref => { app.post(`/api/cms/${ref}/new`, isEnabled, (req, res) => { - db[ref].create(req.body).then(u => res.json(u)); + // First, create the metadata object in the top-level table + db[ref].create(req.body).then(newObj => { + // For a certain subset of translated tables, we need to also insert a new, corresponding english content row. + if (contentTables.includes(ref)) { + const payload = Object.assign({}, req.body, {id: newObj.id, lang: "en"}); + db[`${ref}_content`].create(payload).then(u => res.json(u).end()); + } + }); }); }); app.post("/api/cms/profile/newScaffold", isEnabled, (req, res) => { const profileData = req.body; db.profile.create({slug: profileData.slug, ordering: profileData.ordering, dimension: profileData.dimName}).then(profile => { - db.topic.create({ordering: 0, profile_id: profile.id}).then(() => { - db.profile.findAll(profileReqTreeOnly).then(profiles => { - profiles = sortProfileTree(db, profiles); - populateSearch(profileData, db); - res.json(profiles).end(); + db.profile_content.create({id: profile.id, lang: "en"}).then(() => { + db.topic.create({ordering: 0, profile_id: profile.id}).then(topic => { + db.topic_content.create({id: topic.id, lang: "en"}).then(() => { + db.profile.findAll(profileReqTreeOnly).then(profiles => { + profiles = sortProfileTree(db, profiles); + populateSearch(profileData, db); + res.json(profiles).end(); + }); + }); }); }); }); @@ -355,7 +378,11 @@ module.exports = function(app) { const updateList = cmsTables; updateList.forEach(ref => { app.post(`/api/cms/${ref}/update`, isEnabled, (req, res) => { - db[ref].update(req.body, {where: {id: req.body.id}}).then(u => res.json(u)); + db[ref].update(req.body, {where: {id: req.body.id}}).then(() => { + req.body.content.forEach(content => { + db[`${ref}_content`].upsert(content, {where: {id: req.body.id, lang: content.lang}}).then(u => res.json(u).end()); + }); + }); }); }); diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index 1ed0fc200..a89365aa9 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -19,37 +19,64 @@ const searchMap = { }; const profileReq = { - include: [{ - association: "topics", separate: true, - include: [ - {association: "subtitles", separate: true}, - {association: "descriptions", separate: true}, - {association: "visualizations", separate: true}, - {association: "stats", separate: true}, - {association: "selectors", separate: true} - ] - }] + include: [ + {association: "content", separate: true}, + {association: "topics", separate: true, + include: [ + {association: "content", separate: true}, + {association: "subtitles", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "descriptions", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "visualizations", separate: true}, + {association: "stats", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "selectors", separate: true} + ] + }] }; const topicReq = [ - {association: "subtitles", separate: true}, - {association: "descriptions", separate: true}, + {association: "subtitles", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "descriptions", separate: true, + include: [{association: "content", separate: true}] + }, {association: "visualizations", separate: true}, - {association: "stats", separate: true}, + {association: "stats", separate: true, + include: [{association: "content", separate: true}] + }, {association: "selectors", separate: true} ]; const storyReq = { include: [ - {association: "authors", separate: true}, - {association: "descriptions", separate: true}, - {association: "footnotes", separate: true}, + {association: "authors", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "descriptions", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "footnotes", separate: true, + include: [{association: "content", separate: true}] + }, { association: "storytopics", separate: true, include: [ - {association: "descriptions", separate: true}, - {association: "stats", separate: true}, - {association: "subtitles", separate: true}, + {association: "content", separate: true}, + {association: "descriptions", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "stats", separate: true, + include: [{association: "content", separate: true}] + }, + {association: "subtitles", separate: true, + include: [{association: "content", separate: true}] + }, {association: "visualizations", separate: true} ] } From 2e81be7ba906c19ae47c735710c912d21aa751ef Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 30 Jan 2019 14:20:35 -0500 Subject: [PATCH 09/48] removes parent_id from content tables, using regular id instead --- packages/cms/src/db/author_content.js | 15 ++++++--------- packages/cms/src/db/profile_content.js | 15 ++++++--------- packages/cms/src/db/story_content.js | 15 ++++++--------- packages/cms/src/db/story_description_content.js | 15 ++++++--------- packages/cms/src/db/story_footnote_content.js | 15 ++++++--------- packages/cms/src/db/storytopic_content.js | 15 ++++++--------- .../cms/src/db/storytopic_description_content.js | 15 ++++++--------- packages/cms/src/db/storytopic_stat_content.js | 15 ++++++--------- .../cms/src/db/storytopic_subtitle_content.js | 15 ++++++--------- packages/cms/src/db/topic_content.js | 15 ++++++--------- packages/cms/src/db/topic_description_content.js | 15 ++++++--------- packages/cms/src/db/topic_stat_content.js | 15 ++++++--------- packages/cms/src/db/topic_subtitle_content.js | 15 ++++++--------- 13 files changed, 78 insertions(+), 117 deletions(-) diff --git a/packages/cms/src/db/author_content.js b/packages/cms/src/db/author_content.js index 26854a60b..cd31084a3 100644 --- a/packages/cms/src/db/author_content.js +++ b/packages/cms/src/db/author_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "author", + key: "id" + } }, lang: { type: db.STRING, @@ -26,14 +31,6 @@ module.exports = function(sequelize, db) { type: db.STRING, defaultValue: "New Twitter" }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "author", - key: "id" - } - }, bio: { type: db.TEXT, defaultValue: "New Bio" diff --git a/packages/cms/src/db/profile_content.js b/packages/cms/src/db/profile_content.js index af96316d5..7ee027aaa 100644 --- a/packages/cms/src/db/profile_content.js +++ b/packages/cms/src/db/profile_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "profile", + key: "id" + } }, lang: { type: db.STRING, @@ -21,14 +26,6 @@ module.exports = function(sequelize, db) { label: { type: db.STRING, defaultValue: "New Profile Label" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "profile", - key: "id" - } } }, { diff --git a/packages/cms/src/db/story_content.js b/packages/cms/src/db/story_content.js index 81c1a8eaa..6f8de4ccd 100644 --- a/packages/cms/src/db/story_content.js +++ b/packages/cms/src/db/story_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "story", + key: "id" + } }, lang: { type: db.STRING, @@ -21,14 +26,6 @@ module.exports = function(sequelize, db) { image: { type: db.STRING, defaultValue: "New Image" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "story", - key: "id" - } } }, { diff --git a/packages/cms/src/db/story_description_content.js b/packages/cms/src/db/story_description_content.js index df80afcb8..7490b7532 100644 --- a/packages/cms/src/db/story_description_content.js +++ b/packages/cms/src/db/story_description_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "story_description", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { description: { type: db.TEXT, defaultValue: "New Description" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "story_description", - key: "id" - } } }, { diff --git a/packages/cms/src/db/story_footnote_content.js b/packages/cms/src/db/story_footnote_content.js index 77ae2da6a..45adedb84 100644 --- a/packages/cms/src/db/story_footnote_content.js +++ b/packages/cms/src/db/story_footnote_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "story_footnote", + key: "id" + } }, lang: { type: db.STRING, @@ -17,14 +22,6 @@ module.exports = function(sequelize, db) { description: { type: db.TEXT, defaultValue: "New Footnote" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "story_footnote", - key: "id" - } } }, { diff --git a/packages/cms/src/db/storytopic_content.js b/packages/cms/src/db/storytopic_content.js index 28ff52390..0b7ffcf60 100644 --- a/packages/cms/src/db/storytopic_content.js +++ b/packages/cms/src/db/storytopic_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "storytopic", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { title: { type: db.STRING, defaultValue: "New StoryTopic" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "storytopic", - key: "id" - } } }, { diff --git a/packages/cms/src/db/storytopic_description_content.js b/packages/cms/src/db/storytopic_description_content.js index 97551e305..36f115564 100644 --- a/packages/cms/src/db/storytopic_description_content.js +++ b/packages/cms/src/db/storytopic_description_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "storytopic_description", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { description: { type: db.TEXT, defaultValue: "New Description" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "storytopic_description", - key: "id" - } } }, { diff --git a/packages/cms/src/db/storytopic_stat_content.js b/packages/cms/src/db/storytopic_stat_content.js index 5ce816d96..0af027c35 100644 --- a/packages/cms/src/db/storytopic_stat_content.js +++ b/packages/cms/src/db/storytopic_stat_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "storytopic_stat", + key: "id" + } }, lang: { type: db.STRING, @@ -22,14 +27,6 @@ module.exports = function(sequelize, db) { type: db.STRING, defaultValue: "New Value" }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "storytopic_stat", - key: "id" - } - }, tooltip: { type: db.STRING, defaultValue: "New Tooltip" diff --git a/packages/cms/src/db/storytopic_subtitle_content.js b/packages/cms/src/db/storytopic_subtitle_content.js index 23ff58034..e3a6399e7 100644 --- a/packages/cms/src/db/storytopic_subtitle_content.js +++ b/packages/cms/src/db/storytopic_subtitle_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "storytopic_subtitle", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { subtitle: { type: db.TEXT, defaultValue: "New Subtitle" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "storytopic_subtitle", - key: "id" - } } }, { diff --git a/packages/cms/src/db/topic_content.js b/packages/cms/src/db/topic_content.js index 587c29afc..b792a969d 100644 --- a/packages/cms/src/db/topic_content.js +++ b/packages/cms/src/db/topic_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "topic", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { title: { type: db.STRING, defaultValue: "New Topic" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "topic", - key: "id" - } } }, { diff --git a/packages/cms/src/db/topic_description_content.js b/packages/cms/src/db/topic_description_content.js index 987dbe116..95aa8e820 100644 --- a/packages/cms/src/db/topic_description_content.js +++ b/packages/cms/src/db/topic_description_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "topic_description", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { description: { type: db.TEXT, defaultValue: "New Description" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "topic_description", - key: "id" - } } }, { diff --git a/packages/cms/src/db/topic_stat_content.js b/packages/cms/src/db/topic_stat_content.js index ab0ed35fa..577e7f986 100644 --- a/packages/cms/src/db/topic_stat_content.js +++ b/packages/cms/src/db/topic_stat_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "topic_stat", + key: "id" + } }, lang: { type: db.STRING, @@ -22,14 +27,6 @@ module.exports = function(sequelize, db) { type: db.STRING, defaultValue: "New Value" }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "topic_stat", - key: "id" - } - }, tooltip: { type: db.TEXT, defaultValue: "New Tooltip" diff --git a/packages/cms/src/db/topic_subtitle_content.js b/packages/cms/src/db/topic_subtitle_content.js index accf7b1be..b3313db22 100644 --- a/packages/cms/src/db/topic_subtitle_content.js +++ b/packages/cms/src/db/topic_subtitle_content.js @@ -4,7 +4,12 @@ module.exports = function(sequelize, db) { { id: { type: db.INTEGER, - primaryKey: true + primaryKey: true, + onDelete: "cascade", + references: { + model: "topic_subtitle", + key: "id" + } }, lang: { type: db.STRING, @@ -13,14 +18,6 @@ module.exports = function(sequelize, db) { subtitle: { type: db.TEXT, defaultValue: "New Subtitle" - }, - parent_id: { - type: db.INTEGER, - onDelete: "cascade", - references: { - model: "topic_subtitle", - key: "id" - } } }, { From a1a7b1edc2267485c8a47c539208ecb17c0d7e42 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 30 Jan 2019 16:20:22 -0500 Subject: [PATCH 10/48] fixes associations and broken topic title content --- packages/cms/src/api/cmsRoute.js | 5 +- packages/cms/src/db/author.js | 2 +- packages/cms/src/db/profile.js | 2 +- packages/cms/src/db/story.js | 2 +- packages/cms/src/db/story_description.js | 2 +- packages/cms/src/db/story_footnote.js | 2 +- packages/cms/src/db/storytopic.js | 2 +- packages/cms/src/db/storytopic_description.js | 2 +- packages/cms/src/db/storytopic_stat.js | 2 +- packages/cms/src/db/storytopic_subtitle.js | 2 +- packages/cms/src/db/topic.js | 2 +- packages/cms/src/db/topic_description.js | 2 +- packages/cms/src/db/topic_stat.js | 2 +- packages/cms/src/db/topic_subtitle.js | 2 +- packages/cms/src/profile/ProfileBuilder.jsx | 56 ++++++++++++------- 15 files changed, 52 insertions(+), 35 deletions(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index 3f9c12c09..2d4c01545 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -20,7 +20,10 @@ const isEnabled = (req, res, next) => { const profileReqTreeOnly = { attributes: ["id", "slug", "dimension", "ordering"], include: [{ - association: "topics", attributes: ["id", "slug", "ordering", "profile_id", "type"] + association: "topics", attributes: ["id", "slug", "ordering", "profile_id", "type"], + include: [ + {association: "content", attributes: ["id", "lang", "title"]} + ] }] }; diff --git a/packages/cms/src/db/author.js b/packages/cms/src/db/author.js index 6ac1ad318..0b3edfddd 100644 --- a/packages/cms/src/db/author.js +++ b/packages/cms/src/db/author.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); a.associate = models => { - a.hasMany(models.author_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + a.hasMany(models.author_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return a; diff --git a/packages/cms/src/db/profile.js b/packages/cms/src/db/profile.js index 4486fa0cd..899bfb1f7 100644 --- a/packages/cms/src/db/profile.js +++ b/packages/cms/src/db/profile.js @@ -21,7 +21,7 @@ module.exports = function(sequelize, db) { ); p.associate = models => { - p.hasMany(models.profile_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + p.hasMany(models.profile_content, {foreignKey: "id", sourceKey: "id", as: "content"}); p.hasMany(models.topic, {foreignKey: "profile_id", sourceKey: "id", as: "topics"}); p.hasMany(models.generator, {foreignKey: "profile_id", sourceKey: "id", as: "generators"}); p.hasMany(models.materializer, {foreignKey: "profile_id", sourceKey: "id", as: "materializers"}); diff --git a/packages/cms/src/db/story.js b/packages/cms/src/db/story.js index e74e080ea..90b79a557 100644 --- a/packages/cms/src/db/story.js +++ b/packages/cms/src/db/story.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.story_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.story_content, {foreignKey: "id", sourceKey: "id", as: "content"}); s.hasMany(models.author, {foreignKey: "story_id", sourceKey: "id", as: "authors"}); s.hasMany(models.story_footnote, {foreignKey: "story_id", sourceKey: "id", as: "footnotes"}); s.hasMany(models.story_description, {foreignKey: "story_id", sourceKey: "id", as: "descriptions"}); diff --git a/packages/cms/src/db/story_description.js b/packages/cms/src/db/story_description.js index 31dfbeccc..e61045770 100644 --- a/packages/cms/src/db/story_description.js +++ b/packages/cms/src/db/story_description.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.story_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.story_description_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/story_footnote.js b/packages/cms/src/db/story_footnote.js index d1faacfa4..6f9b7f45d 100644 --- a/packages/cms/src/db/story_footnote.js +++ b/packages/cms/src/db/story_footnote.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.story_footnote_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.story_footnote_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/storytopic.js b/packages/cms/src/db/storytopic.js index 87a7f8a80..29d5090de 100644 --- a/packages/cms/src/db/storytopic.js +++ b/packages/cms/src/db/storytopic.js @@ -32,7 +32,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.storytopic_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.storytopic_content, {foreignKey: "id", sourceKey: "id", as: "content"}); s.hasMany(models.storytopic_description, {foreignKey: "storytopic_id", sourceKey: "id", as: "descriptions"}); s.hasMany(models.storytopic_stat, {foreignKey: "storytopic_id", sourceKey: "id", as: "stats"}); s.hasMany(models.storytopic_subtitle, {foreignKey: "storytopic_id", sourceKey: "id", as: "subtitles"}); diff --git a/packages/cms/src/db/storytopic_description.js b/packages/cms/src/db/storytopic_description.js index 98bf23eb6..aaa599d89 100644 --- a/packages/cms/src/db/storytopic_description.js +++ b/packages/cms/src/db/storytopic_description.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.storytopic_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.storytopic_description_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/storytopic_stat.js b/packages/cms/src/db/storytopic_stat.js index f10c83604..69e26f349 100644 --- a/packages/cms/src/db/storytopic_stat.js +++ b/packages/cms/src/db/storytopic_stat.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.storytopic_stat_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.storytopic_stat_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/storytopic_subtitle.js b/packages/cms/src/db/storytopic_subtitle.js index e28a93e60..c86809cfe 100644 --- a/packages/cms/src/db/storytopic_subtitle.js +++ b/packages/cms/src/db/storytopic_subtitle.js @@ -24,7 +24,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.storytopic_subtitle_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.storytopic_subtitle_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/topic.js b/packages/cms/src/db/topic.js index e63a46911..22975d5ad 100644 --- a/packages/cms/src/db/topic.js +++ b/packages/cms/src/db/topic.js @@ -36,7 +36,7 @@ module.exports = function(sequelize, db) { ); t.associate = models => { - t.hasMany(models.topic_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + t.hasMany(models.topic_content, {foreignKey: "id", sourceKey: "id", as: "content"}); t.hasMany(models.topic_visualization, {foreignKey: "topic_id", sourceKey: "id", as: "visualizations"}); t.hasMany(models.topic_stat, {foreignKey: "topic_id", sourceKey: "id", as: "stats"}); t.hasMany(models.topic_subtitle, {foreignKey: "topic_id", sourceKey: "id", as: "subtitles"}); diff --git a/packages/cms/src/db/topic_description.js b/packages/cms/src/db/topic_description.js index c6d2f4c44..4ad0f7de2 100644 --- a/packages/cms/src/db/topic_description.js +++ b/packages/cms/src/db/topic_description.js @@ -28,7 +28,7 @@ module.exports = function(sequelize, db) { ); t.associate = models => { - t.hasMany(models.topic_description_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + t.hasMany(models.topic_description_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return t; diff --git a/packages/cms/src/db/topic_stat.js b/packages/cms/src/db/topic_stat.js index abad287e4..387402af9 100644 --- a/packages/cms/src/db/topic_stat.js +++ b/packages/cms/src/db/topic_stat.js @@ -28,7 +28,7 @@ module.exports = function(sequelize, db) { ); s.associate = models => { - s.hasMany(models.topic_stat_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + s.hasMany(models.topic_stat_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return s; diff --git a/packages/cms/src/db/topic_subtitle.js b/packages/cms/src/db/topic_subtitle.js index f764243ca..3ca71d58b 100644 --- a/packages/cms/src/db/topic_subtitle.js +++ b/packages/cms/src/db/topic_subtitle.js @@ -28,7 +28,7 @@ module.exports = function(sequelize, db) { ); t.associate = models => { - t.hasMany(models.topic_subtitle_content, {foreignKey: "parent_id", sourceKey: "id", as: "content"}); + t.hasMany(models.topic_subtitle_content, {foreignKey: "id", sourceKey: "id", as: "content"}); }; return t; diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index b906b3c2f..7f0eaf23b 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -74,15 +74,19 @@ class ProfileBuilder extends Component { masterSlug: p.slug, masterDimension: p.dimension, data: p, - childNodes: p.topics.map(t => ({ - id: `topic${t.id}`, - hasCaret: false, - label: this.decode(stripHTML(t.title)), - itemType: "topic", - masterSlug: p.slug, - masterDimension: p.dimension, - data: t - })) + childNodes: p.topics.map(t => { + const enCon = t.content.find(c => c.lang === "en"); + const title = enCon ? enCon.title : t.slug; + return { + id: `topic${t.id}`, + hasCaret: false, + label: this.decode(stripHTML(title)), + itemType: "topic", + masterSlug: p.slug, + masterDimension: p.dimension, + data: t + }; + }) })); if (!openNode) { this.setState({nodes}); @@ -209,7 +213,9 @@ class ProfileBuilder extends Component { if (topic.status === 200) { obj.id = `topic${topic.data.id}`; obj.data = topic.data; - obj.label = varSwap(this.decode(stripHTML(obj.data.title)), formatters, variables); + const enCon = topic.data.content.find(c => c.lang === "en"); + const title = enCon ? enCon.title : topic.slug; + obj.label = varSwap(this.decode(stripHTML(title)), formatters, variables); const parent = this.locateNode("profile", obj.data.profile_id); parent.childNodes.push(obj); parent.childNodes.sort((a, b) => a.data.ordering - b.data.ordering); @@ -231,7 +237,9 @@ class ProfileBuilder extends Component { if (topic.status === 200) { objTopic.id = `topic${topic.data.id}`; objTopic.data = topic.data; - objTopic.label = varSwap(this.decode(stripHTML(objTopic.data.title)), formatters, variables); + const enCon = topic.data.content.find(c => c.lang === "en"); + const title = enCon ? enCon.title : topic.data.slug; + objTopic.label = varSwap(this.decode(stripHTML(title)), formatters, variables); const parent = this.locateNode("profile", obj.data.profile_id); parent.childNodes.push(obj); parent.childNodes.sort((a, b) => a.data.ordering - b.data.ordering); @@ -261,15 +269,19 @@ class ProfileBuilder extends Component { if (n.itemType === "topic") { const parent = this.locateNode("profile", n.data.profile_id); axios.delete("/api/cms/topic/delete", {params: {id: n.data.id}}).then(resp => { - const topics = resp.data.map(topicData => ({ - id: `topic${topicData.id}`, - hasCaret: false, - iconName: topicIcons[topicData.type] || "help", - label: this.decode(stripHTML(topicData.title)), - itemType: "topic", - masterSlug: parent.masterSlug, - data: topicData - })); + const topics = resp.data.map(topicData => { + const enCon = topicData.content.find(c => c.lang === "en"); + const title = enCon ? enCon.title : topicData.slug; + return { + id: `topic${topicData.id}`, + hasCaret: false, + iconName: topicIcons[topicData.type] || "help", + label: this.decode(stripHTML(title)), + itemType: "topic", + masterSlug: parent.masterSlug, + data: topicData + }; + }); parent.childNodes = topics; this.setState({nodes, nodeToDelete}, this.handleNodeClick.bind(this, parent.childNodes[0])); }); @@ -421,7 +433,9 @@ class ProfileBuilder extends Component { const p = this.locateProfileNodeBySlug(currentSlug); p.label = varSwap(p.data.slug, formatters, variables); p.childNodes = p.childNodes.map(t => { - t.label = varSwap(this.decode(stripHTML(t.data.title)), formatters, variables); + const enCon = t.data.content.find(c => c.lang === "en"); + const title = enCon ? enCon.title : t.slug; + t.label = varSwap(this.decode(stripHTML(title)), formatters, variables); return t; }); this.setState({nodes}); From cff3519ea1875d324b3f4c93596b5c0b8cc177a1 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 30 Jan 2019 21:02:04 -0500 Subject: [PATCH 11/48] enables textcard to accept locales and write to the proper db --- packages/cms/src/api/cmsRoute.js | 24 +++++++--- .../cms/src/components/cards/TextCard.jsx | 45 ++++++++++++++----- .../components/editors/PlainTextEditor.jsx | 4 +- .../cms/src/components/editors/TextEditor.jsx | 17 +++---- packages/cms/src/profile/ProfileEditor.jsx | 10 ++--- 5 files changed, 67 insertions(+), 33 deletions(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index 2d4c01545..32e8b88f0 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -42,6 +42,7 @@ const formatterReqTreeOnly = { const profileReqProfileOnly = { include: [ + {association: "content"}, {association: "generators", attributes: ["id", "name"]}, {association: "materializers", attributes: ["id", "name", "ordering"]} ] @@ -339,7 +340,12 @@ module.exports = function(app) { getList.forEach(ref => { app.get(`/api/cms/${ref}/get/:id`, (req, res) => { - db[ref].findOne({where: {id: req.params.id}, include: {association: "content"}}).then(u => res.json(u).end()); + if (contentTables.includes(ref)) { + db[ref].findOne({where: {id: req.params.id}, include: {association: "content"}}).then(u => res.json(u).end()); + } + else { + db[ref].findOne({where: {id: req.params.id}}).then(u => res.json(u).end()); + } }); }); @@ -355,6 +361,9 @@ module.exports = function(app) { const payload = Object.assign({}, req.body, {id: newObj.id, lang: "en"}); db[`${ref}_content`].create(payload).then(u => res.json(u).end()); } + else { + res.json(newObj).end(); + } }); }); }); @@ -381,10 +390,15 @@ module.exports = function(app) { const updateList = cmsTables; updateList.forEach(ref => { app.post(`/api/cms/${ref}/update`, isEnabled, (req, res) => { - db[ref].update(req.body, {where: {id: req.body.id}}).then(() => { - req.body.content.forEach(content => { - db[`${ref}_content`].upsert(content, {where: {id: req.body.id, lang: content.lang}}).then(u => res.json(u).end()); - }); + db[ref].update(req.body, {where: {id: req.body.id}}).then(o => { + if (contentTables.includes(ref)) { + req.body.content.forEach(content => { + db[`${ref}_content`].upsert(content, {where: {id: req.body.id, lang: content.lang}}).then(u => res.json(u).end()); + }); + } + else { + res.json(o).end(); + } }); }); }); diff --git a/packages/cms/src/components/cards/TextCard.jsx b/packages/cms/src/components/cards/TextCard.jsx index 2fa125dd7..fd2797015 100644 --- a/packages/cms/src/components/cards/TextCard.jsx +++ b/packages/cms/src/components/cards/TextCard.jsx @@ -37,34 +37,55 @@ class TextCard extends Component { } hitDB() { - const {item, type} = this.props; + const {item, type, locale} = this.props; const {id} = item; axios.get(`/api/cms/${type}/get/${id}`).then(resp => { - this.setState({minData: resp.data}, this.formatDisplay.bind(this)); + // If this has been opened with a non english locale, and there is NO row for that locale, + // create a placeholder row that can be edited in the text boxes (and later saved) + const minData = resp.data; + if (!minData.content.find(c => c.lang === locale)) { + const english = minData.content.find(c => c.lang === "en"); + const newLangObj = {id: minData.id, lang: locale}; + Object.keys(english).forEach(k => { + if (k !== "id" && k !== "lang") newLangObj[k] = english[k]; + }); + minData.content.push(newLangObj); + } + this.setState({minData}, this.formatDisplay.bind(this)); }); } formatDisplay() { - const {variables, selectors} = this.props; + const {variables, selectors, locale} = this.props; const {formatters} = this.context; const {minData} = this.state; // Setting "selectors" here is pretty hacky. The varSwap needs selectors in order // to run, and it expects them INSIDE the object. Find a better way to do this without // polluting the object itself minData.selectors = selectors; - const displayData = varSwapRecursive(minData, formatters, variables); - this.setState({displayData}); + // Swap vars, and extract the actual (multilingual) content + const content = varSwapRecursive(minData, formatters, variables).content; + const english = content.find(c => c.lang === "en"); + const currLang = content.find(c => c.lang === locale); + // Map over each of the english keys, and fetch its equivalent locale version (or default to english) + const displayData = {}; + Object.keys(english).forEach(k => { + displayData[k] = currLang[k] ? currLang[k] : english[k]; + }); + this.setState({displayData}); } save() { - const {type, fields, plainfields} = this.props; + const {type, fields, plainfields, locale} = this.props; const {minData} = this.state; const payload = {id: minData.id}; // For some reason, an empty quill editor reports its contents as


. Do not save // this to the database - save an empty string instead. - fields.forEach(field => payload[field] = minData[field] === "


" ? "" : minData[field]); - if (plainfields) plainfields.forEach(field => payload[field] = minData[field] === "


" ? "" : minData[field]); + const thisLocale = minData.content.find(c => c.lang === locale); + fields.forEach(field => thisLocale[field] = thisLocale[field] === "


" ? "" : thisLocale[field]); + if (plainfields) plainfields.forEach(field => thisLocale[field] = thisLocale[field] === "


" ? "" : thisLocale[field]); payload.allowed = minData.allowed; + payload.content = [thisLocale]; axios.post(`/api/cms/${type}/update`, payload).then(resp => { if (resp.status === 200) { this.setState({isOpen: false}, this.formatDisplay.bind(this)); @@ -119,7 +140,7 @@ class TextCard extends Component { render() { const {displayData, minData, isOpen, alertObj} = this.state; - const {variables, fields, plainfields, type, parentArray, item} = this.props; + const {variables, fields, plainfields, type, parentArray, item, locale} = this.props; const {ordering} = item; if (!minData || !displayData) return ; @@ -128,7 +149,7 @@ class TextCard extends Component { if (["profile_stat", "topic_stat"].includes(type)) cardClass = "stat-card"; const displaySort = ["title", "value", "subtitle", "description"]; const displays = Object.keys(displayData) - .filter(k => typeof displayData[k] === "string" && !["id", "image", "profile_id", "allowed", "date", "ordering", "slug", "label", "type"].includes(k)) + .filter(k => typeof displayData[k] === "string" && !["id", "lang", "image", "profile_id", "allowed", "date", "ordering", "slug", "label", "type"].includes(k)) .sort((a, b) => displaySort.indexOf(a) - displaySort.indexOf(b)); return ( @@ -176,8 +197,8 @@ class TextCard extends Component { inline="true" >
- - displaySort.indexOf(a) - displaySort.indexOf(b))} /> + + displaySort.indexOf(a) - displaySort.indexOf(b))} />
c.lang === locale); + thisLocale[field] = e.target.value; this.setState({data}); } diff --git a/packages/cms/src/components/editors/TextEditor.jsx b/packages/cms/src/components/editors/TextEditor.jsx index 9676a5c6d..6926f5f8e 100644 --- a/packages/cms/src/components/editors/TextEditor.jsx +++ b/packages/cms/src/components/editors/TextEditor.jsx @@ -24,16 +24,11 @@ class TextEditor extends Component { this.setState({data, fields}); } - changeField(field, e) { - const {data} = this.state; - data[field] = e.target.value; - this.setState({data}); - } - handleEditor(field, t) { const {data} = this.state; - // if (t === "


") t = ""; - data[field] = t; + const {locale} = this.props; + const thisLocale = data.content.find(c => c.lang === locale); + thisLocale[field] = t; this.setState({data}); } @@ -60,15 +55,17 @@ class TextEditor extends Component { render() { const {data, fields} = this.state; - const {variables} = this.props; + const {variables, locale} = this.props; const {formatters} = this.context; if (!data || !fields || !variables || !formatters) return null; + const thisLocale = data.content.find(c => c.lang === locale); + const quills = fields.map(f =>
- +
); diff --git a/packages/cms/src/profile/ProfileEditor.jsx b/packages/cms/src/profile/ProfileEditor.jsx index e1391d0ed..fb12d926b 100644 --- a/packages/cms/src/profile/ProfileEditor.jsx +++ b/packages/cms/src/profile/ProfileEditor.jsx @@ -12,9 +12,7 @@ import "./ProfileEditor.css"; const propMap = { generator: "generators", - materializer: "materializers", - profile_stat: "stats", - profile_footnote: "footnotes" + materializer: "materializers" }; class ProfileEditor extends Component { @@ -24,7 +22,8 @@ class ProfileEditor extends Component { this.state = { minData: null, variables: null, - recompiling: true + recompiling: true, + locale: "en" }; } @@ -112,7 +111,7 @@ class ProfileEditor extends Component { render() { - const {minData, recompiling} = this.state; + const {minData, recompiling, locale} = this.state; const {children, variables, preview} = this.props; if (!minData || !variables) return ; @@ -199,6 +198,7 @@ class ProfileEditor extends Component { >
Date: Thu, 31 Jan 2019 14:49:53 -0500 Subject: [PATCH 12/48] adds visual split and locale dropdown to builder --- packages/cms/src/Builder.jsx | 24 ++++++- packages/cms/src/api/cmsRoute.js | 3 + packages/cms/src/components/cards/Flag.jsx | 31 +++++++++ .../cms/src/components/cards/TextCard.css | 7 ++ .../cms/src/components/cards/TextCard.jsx | 11 ++-- packages/cms/src/profile/ProfileBuilder.jsx | 2 + packages/cms/src/profile/ProfileEditor.jsx | 14 ++-- packages/cms/src/profile/TopicEditor.jsx | 65 ++++++++++++++++++- 8 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 packages/cms/src/components/cards/Flag.jsx diff --git a/packages/cms/src/Builder.jsx b/packages/cms/src/Builder.jsx index 0219b2a9b..45a288203 100644 --- a/packages/cms/src/Builder.jsx +++ b/packages/cms/src/Builder.jsx @@ -20,6 +20,7 @@ class Builder extends Component { currentTab: "profiles", // formatters, theme: "cms-light", + locale: "pt", formatters: (props.formatters || []).reduce((acc, d) => { const f = Function("n", "libs", "formatters", d.logic); @@ -52,8 +53,12 @@ class Builder extends Component { this.setState({theme: e.target.value}); } + handleLocaleSelect(e) { + this.setState({locale: e.target.value}); + } + render() { - const {currentTab, theme} = this.state; + const {currentTab, theme, locale} = this.state; const {isEnabled} = this.props; const navLinks = ["profiles", "stories", "formatters"]; @@ -69,6 +74,19 @@ class Builder extends Component { {navLink} )} +
+ + + {/* story name */} {/* TODO: move this to header */}

@@ -98,14 +122,23 @@ class StoryEditor extends Component {
+
-

Date

@@ -129,6 +162,22 @@ class StoryEditor extends Component { { minData.descriptions && minData.descriptions.map(d => + )} + +
+
+ { minData.descriptions && minData.descriptions.map(d => + + )} +
+
+
+ { minData.footnotes && minData.footnotes.map(d => + + )} +
+
+
+ { minData.authors && minData.authors.map(d => + ; @@ -91,6 +97,18 @@ class StoryTopicEditor extends Component { return (
+ {/* current story options */} +
+ {/* change slug */} + +
+ {/* layout select */}
+ {/* visualizations */}

Visualizations )} - }

{/* subtitles */} @@ -219,8 +224,7 @@ class TopicEditor extends Component { /> )}
-
-
+ {locale &&
{ minData.subtitles && minData.subtitles.map(s => )} -
+
} {/* subtitles */}

@@ -283,8 +287,7 @@ class TopicEditor extends Component { /> )} -
-
+ {locale &&
{ minData.stats && minData.stats.map(s => )} -
+
} {/* descriptions */}

@@ -325,7 +328,7 @@ class TopicEditor extends Component { )}
-
+ {locale &&
{ minData.descriptions && minData.descriptions.map(d => )} -
+
} {/* visualizations */}

diff --git a/packages/cms/src/story/StoryEditor.jsx b/packages/cms/src/story/StoryEditor.jsx index 70571d4ce..7cc4ad5ff 100644 --- a/packages/cms/src/story/StoryEditor.jsx +++ b/packages/cms/src/story/StoryEditor.jsx @@ -129,7 +129,7 @@ class StoryEditor extends Component { onSave={this.onSave.bind(this)} variables={{}} /> - + /> }

Date @@ -172,8 +172,7 @@ class StoryEditor extends Component { /> )} -
-
+ {locale &&
{ minData.descriptions && minData.descriptions.map(d => )} -
+
} {/* footnotes */}

@@ -210,8 +209,7 @@ class StoryEditor extends Component { /> )} -
-
+ {locale &&
{ minData.footnotes && minData.footnotes.map(d => )} -
+
} {/* descriptions */}

@@ -249,8 +247,7 @@ class StoryEditor extends Component { /> )} -
-
+ {locale &&
{ minData.authors && minData.authors.map(d => )} -
+
} ); } diff --git a/packages/cms/src/story/StoryTopicEditor.jsx b/packages/cms/src/story/StoryTopicEditor.jsx index f77c5b7ca..97898efd1 100644 --- a/packages/cms/src/story/StoryTopicEditor.jsx +++ b/packages/cms/src/story/StoryTopicEditor.jsx @@ -136,7 +136,7 @@ class StoryTopicEditor extends Component { onSave={this.onSave.bind(this)} variables={{}} /> - + />} {/* subtitles */} @@ -169,8 +169,7 @@ class StoryTopicEditor extends Component { /> )} -
-
+ {locale &&
{ minData.subtitles && minData.subtitles.map(s => )} -
+
} {/* stats */}

@@ -208,8 +207,7 @@ class StoryTopicEditor extends Component { /> )} -
-
+ {locale &&
{ minData.stats && minData.stats.map(s => )} -
+
} {/* descriptions */}

@@ -247,8 +245,7 @@ class StoryTopicEditor extends Component { /> )} -
-
+ { locale &&
{ minData.descriptions && minData.descriptions.map(d => )} -
+
} + {/* visualizations */}

Visualizations From 55755f36303a6aeedbe14558883c08e6ed498779 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Fri, 1 Feb 2019 15:49:50 -0500 Subject: [PATCH 16/48] checks that an update has content before updating language table --- packages/cms/src/api/cmsRoute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index d8010f3af..955e88d78 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -395,7 +395,7 @@ module.exports = function(app) { updateList.forEach(ref => { app.post(`/api/cms/${ref}/update`, isEnabled, (req, res) => { db[ref].update(req.body, {where: {id: req.body.id}}).then(o => { - if (contentTables.includes(ref)) { + if (contentTables.includes(ref) && req.body.content) { req.body.content.forEach(content => { db[`${ref}_content`].upsert(content, {where: {id: req.body.id, lang: content.lang}}).then(u => res.json(u).end()); }); From 532113ae644f7a63fe44911251e37b2bdb9e8206 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Fri, 1 Feb 2019 18:36:25 -0500 Subject: [PATCH 17/48] fixes ordering bug by returning correct payload for create route --- packages/cms/src/api/cmsRoute.js | 2 +- packages/cms/src/components/MoveButtons.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cms/src/api/cmsRoute.js b/packages/cms/src/api/cmsRoute.js index 955e88d78..89a9a93ef 100644 --- a/packages/cms/src/api/cmsRoute.js +++ b/packages/cms/src/api/cmsRoute.js @@ -363,7 +363,7 @@ module.exports = function(app) { // For a certain subset of translated tables, we need to also insert a new, corresponding english content row. if (contentTables.includes(ref)) { const payload = Object.assign({}, req.body, {id: newObj.id, lang: "en"}); - db[`${ref}_content`].create(payload).then(u => res.json(u).end()); + db[`${ref}_content`].create(payload).then(() => res.json(newObj).end()); } else { res.json(newObj).end(); diff --git a/packages/cms/src/components/MoveButtons.jsx b/packages/cms/src/components/MoveButtons.jsx index cea94b98a..2f4ed283f 100644 --- a/packages/cms/src/components/MoveButtons.jsx +++ b/packages/cms/src/components/MoveButtons.jsx @@ -17,7 +17,7 @@ class MoveButtons extends Component { } componentDidUpdate(prevProps) { - if (prevProps.item !== this.props.item || prevProps.array !== this.props.array) { + if (JSON.stringify(prevProps.item) !== JSON.stringify(this.props.item) || JSON.stringify(prevProps.array) !== JSON.stringify(this.props.array)) { this.setState({item: this.props.item, array: this.props.array}); } } From 7c112a4165defc8d31b7a655da80a082cacdb1a3 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Mon, 4 Feb 2019 16:50:26 -0500 Subject: [PATCH 18/48] updates mortar front-end to respect locale requests --- packages/cms/src/api/mortarRoute.js | 65 +++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index a89365aa9..b4eee798b 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -55,6 +55,7 @@ const topicReq = [ const storyReq = { include: [ + {association: "content", separate: true}, {association: "authors", separate: true, include: [{association: "content", separate: true}] }, @@ -99,7 +100,6 @@ const sorter = (a, b) => a.ordering - b.ordering; // Using nested ORDER BY in the massive includes is incredibly difficult so do it manually here. Eventually move it up to the query. const sortProfile = profile => { - profile = profile.toJSON(); if (profile.topics) { profile.topics.sort(sorter); profile.topics.forEach(topic => { @@ -114,7 +114,6 @@ const sortProfile = profile => { }; const sortStory = story => { - story = story.toJSON(); ["descriptions", "footnotes", "authors", "storytopics"].forEach(type => story[type].sort(sorter)); story.storytopics.forEach(storytopic => { ["descriptions", "stats", "subtitles", "visualizations"].forEach(type => storytopic[type].sort(sorter)); @@ -122,14 +121,56 @@ const sortStory = story => { return story; }; +/** + * Lang-specific content is stored in secondary tables, and are part of profiles as an + * array called "content," which contains objects of region-specific translated keys. + * We don't want the front end to have to even know about this sub-table or sub-array. + * Therefore, bubble up the appropriate content to the top-level of the object + */ + +const bubbleUp = (obj, locale) => { + const enCon = obj.content.find(c => c.lang === "en"); + const thisCon = obj.content.find(c => c.lang === locale); + Object.keys(enCon).forEach(k => { + if (k !== "id" && k !== "lang") { + thisCon && thisCon[k] ? obj[k] = thisCon[k] : obj[k] = enCon[k]; + } + }); + delete obj.content; + return obj; +}; + +const extractLocaleContent = (obj, locale, mode) => { + obj = obj.toJSON(); + obj = bubbleUp(obj, locale); + if (mode === "story") { + ["footnotes", "descriptions", "authors"].forEach(type => { + if (obj[type]) obj[type] = obj[type].map(o => bubbleUp(o, locale)); + }); + } + const children = mode === "story" ? "storytopics" : "topics"; + if (obj[children]) { + obj[children] = obj[children].map(child => { + child = bubbleUp(child, locale); + ["subtitles", "descriptions", "stats"].forEach(type => { + if (child[type]) child[type] = child[type].map(o => bubbleUp(o, locale)); + }); + return child; + }); + } + return obj; +}; + + module.exports = function(app) { const {cache, db} = app.settings; app.get("/api/internalprofile/:slug", (req, res) => { const {slug} = req.params; + const locale = req.query.locale ? req.query.locale : "en"; const reqObj = Object.assign({}, profileReq, {where: {slug}}); - db.profile.findOne(reqObj).then(profile => res.json(sortProfile(profile)).end()); + db.profile.findOne(reqObj).then(profile => res.json(sortProfile(extractLocaleContent(profile, locale, "profile"))).end()); }); app.get("/api/variables/:slug/:id", (req, res) => { @@ -224,7 +265,9 @@ module.exports = function(app) { app.get("/api/profile/:slug/:pid", async(req, res) => { req.setTimeout(1000 * 60 * 30); // 30 minute timeout for non-cached cube queries const {slug, pid} = req.params; + const {locale} = req.query; const origin = `http${ req.connection.encrypted ? "s" : "" }://${ req.headers.host }`; + const localeString = locale ? `?locale=${locale}` : ""; const attribute = await db.search.findOne({where: {[sequelize.Op.or]: {id: pid, slug: pid}}}); const {id} = attribute; @@ -235,7 +278,7 @@ module.exports = function(app) { * We must pass that info as one of the arguments of the returned Promise. */ - Promise.all([axios.get(`${origin}/api/variables/${slug}/${id}`), db.formatter.findAll()]) + Promise.all([axios.get(`${origin}/api/variables/${slug}/${id}${localeString}`), db.formatter.findAll()]) // Given the completely built returnVariables and all the formatters (formatters are global) // Get the ACTUAL profile itself and all its dependencies and prepare it to be formatted and regex replaced @@ -246,7 +289,7 @@ module.exports = function(app) { delete variables._matStatus; const formatters = resp[1]; const formatterFunctions = formatters4eval(formatters); - const request = axios.get(`${origin}/api/internalprofile/${slug}`); + const request = axios.get(`${origin}/api/internalprofile/${slug}${localeString}`); return Promise.all([variables, formatterFunctions, request]); }) // Given a returnObject with completely built returnVariables, a hash array of formatter functions, and the profile itself @@ -311,10 +354,11 @@ module.exports = function(app) { // Endpoint for getting a story app.get("/api/story/:id", (req, res) => { const {id} = req.params; + const locale = req.query.locale ? req.query.locale : "en"; // Using a Sequelize OR when the two OR columns are of different types causes a Sequelize error, necessitating this workaround. const reqObj = !isNaN(id) ? Object.assign({}, storyReq, {where: {id}}) : Object.assign({}, storyReq, {where: {slug: id}}); db.story.findOne(reqObj).then(story => { - story = sortStory(story); + story = sortStory(extractLocaleContent(story, locale, "story")); // varSwapRecursive takes any column named "logic" and transpiles it to es5 for IE. // Do a naive varswap (with no formatters and no variables) just to access the transpile for vizes. story = varSwapRecursive(story, {}, {}); @@ -324,7 +368,14 @@ module.exports = function(app) { // Endpoint for getting all stories app.get("/api/story", (req, res) => { - db.story.findAll({include: [{association: "authors", attributes: ["name", "image"]}]}).then(stories => { + const locale = req.query.locale ? req.query.locale : "en"; + db.story.findAll({include: [ + {association: "content"}, + {association: "authors", include: [ + {association: "content", attributes: ["name", "image", "lang"]} + ]} + ]}).then(stories => { + stories = stories.map(story => extractLocaleContent(story, locale, "story")); res.json(stories.sort(sorter)).end(); }); }); From f40914cb4332836e66556377e748e3fc5c920b17 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Mon, 4 Feb 2019 17:15:31 -0500 Subject: [PATCH 19/48] adds locale to mortareval --- packages/cms/src/api/mortarRoute.js | 7 +++++-- packages/cms/src/utils/mortarEval.js | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index b4eee798b..939ada42c 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -175,6 +175,7 @@ module.exports = function(app) { app.get("/api/variables/:slug/:id", (req, res) => { // app.get("/api/variables/:slug/:id", jsonCache, (req, res) => { + const locale = req.query.locale ? req.query.locale : "en"; req.setTimeout(1000 * 60 * 30); // 30 minute timeout for non-cached cube queries const {slug, id} = req.params; @@ -226,7 +227,7 @@ module.exports = function(app) { const requiredGenerators = generators.filter(g => g.api === requests[i]); // Build the return object using a reducer, one generator at a time returnVariables = requiredGenerators.reduce((acc, g) => { - const evalResults = mortarEval("resp", r.data, g.logic, formatterFunctions); + const evalResults = mortarEval("resp", r.data, g.logic, formatterFunctions, locale); const {vars} = evalResults; // genStatus is used to track the status of each individual generator genStatus[g.id] = evalResults.error ? {error: evalResults.error} : evalResults.vars; @@ -316,6 +317,8 @@ module.exports = function(app) { app.get("/api/topic/:slug/:pid/:topicId", async(req, res) => { req.setTimeout(1000 * 60 * 30); // 30 minute timeout for non-cached cube queries const {slug, pid, topicId} = req.params; + const {locale} = req.query; + const localeString = locale ? `?locale=${locale}` : ""; const origin = `http${ req.connection.encrypted ? "s" : "" }://${ req.headers.host }`; const attribute = await db.search.findOne({where: {[sequelize.Op.or]: {id: pid, slug: pid}}}); @@ -323,7 +326,7 @@ module.exports = function(app) { // As with profiles above, we need formatters, variables, and the topic itself in order to // create a "postProcessed" topic that can be returned to the requester. - const getVariables = axios.get(`${origin}/api/variables/${slug}/${id}`); + const getVariables = axios.get(`${origin}/api/variables/${slug}/${id}${localeString}`); const getFormatters = db.formatter.findAll(); const where = {}; diff --git a/packages/cms/src/utils/mortarEval.js b/packages/cms/src/utils/mortarEval.js index 8660ebed4..a12d81f3c 100644 --- a/packages/cms/src/utils/mortarEval.js +++ b/packages/cms/src/utils/mortarEval.js @@ -1,14 +1,14 @@ const libs = require("./libs"); -module.exports = (varInnerName, varOuterValue, logic, formatterFunctions) => { +module.exports = (varInnerName, varOuterValue, logic, formatterFunctions, locale = "en") => { let vars = {}; // Because logic is arbitrary javascript, it may be malformed. We need to wrap the // entire execution in a try/catch. try { if (varOuterValue) { eval(` - let f = (${varInnerName}, libs, formatters) => {${logic}}; - vars = f(varOuterValue, libs, formatterFunctions); + let f = (${varInnerName}, libs, formatters, locale) => {${logic}}; + vars = f(varOuterValue, libs, formatterFunctions, locale); `); // A successfully run eval will return the vars generated return {vars, error: null}; From efd3e35f5d672fedbf076cd476bf6d0324baf3f4 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 6 Feb 2019 15:39:38 -0500 Subject: [PATCH 20/48] splits out variables into locale-specfic blocks --- packages/cms/src/api/mortarRoute.js | 2 +- .../src/components/cards/GeneratorCard.jsx | 5 ++- .../cms/src/components/cards/TextCard.css | 2 + packages/cms/src/profile/ProfileBuilder.jsx | 37 ++++++++++++---- packages/cms/src/profile/ProfileEditor.jsx | 43 +++++++++++++++++-- packages/cms/src/profile/TopicEditor.jsx | 28 ++++++------ 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index 939ada42c..9e926a61f 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -248,7 +248,7 @@ module.exports = function(app) { materializers.sort((a, b) => a.ordering - b.ordering); const matStatus = {}; returnVariables = materializers.reduce((acc, m) => { - const evalResults = mortarEval("variables", acc, m.logic, formatterFunctions); + const evalResults = mortarEval("variables", acc, m.logic, formatterFunctions, locale); const {vars} = evalResults; matStatus[m.id] = evalResults.error ? {error: evalResults.error} : evalResults.vars; return {...acc, ...vars}; diff --git a/packages/cms/src/components/cards/GeneratorCard.jsx b/packages/cms/src/components/cards/GeneratorCard.jsx index 5977eee20..7e9562b1f 100644 --- a/packages/cms/src/components/cards/GeneratorCard.jsx +++ b/packages/cms/src/components/cards/GeneratorCard.jsx @@ -6,6 +6,7 @@ import Loading from "components/Loading"; import FooterButtons from "../FooterButtons"; import MoveButtons from "../MoveButtons"; import deepClone from "../../utils/deepClone"; +import Flag from "./Flag"; import "./GeneratorCard.css"; import ConsoleVariable from "../ConsoleVariable"; @@ -109,7 +110,7 @@ class GeneratorCard extends Component { } render() { - const {type, variables, item, parentArray, preview} = this.props; + const {type, variables, item, parentArray, preview, locale} = this.props; const {displayData, minData, isOpen, alertObj} = this.state; let description = ""; @@ -140,6 +141,8 @@ class GeneratorCard extends Component { {alertObj.message} + + {/* title & edit toggle button */}
diff --git a/packages/cms/src/components/cards/TextCard.css b/packages/cms/src/components/cards/TextCard.css index dd074a3e2..000a49881 100644 --- a/packages/cms/src/components/cards/TextCard.css +++ b/packages/cms/src/components/cards/TextCard.css @@ -25,6 +25,8 @@ /* flag icon */ & .cms-flag { position: absolute; + bottom: 5px; + right: 5px; } } diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index 919182c45..c7039a4fe 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -50,7 +50,9 @@ class ProfileBuilder extends Component { getChildContext() { const {formatters} = this.context; const {variablesHash, currentSlug} = this.state; - return {formatters, variables: variablesHash[currentSlug]}; + const variables = variablesHash[currentSlug] ? variablesHash[currentSlug].en : {}; + // TODO: how to send down proper lang via context? + return {formatters, variables}; } /** @@ -133,7 +135,7 @@ class ProfileBuilder extends Component { const {variablesHash, currentSlug} = this.state; const {stripHTML} = this.context.formatters; const {formatters} = this.context; - const variables = variablesHash[currentSlug] ? deepClone(variablesHash[currentSlug]) : null; + const variables = variablesHash[currentSlug] && variablesHash[currentSlug].en ? deepClone(variablesHash[currentSlug].en) : null; n = this.locateNode(n.itemType, n.data.id); let parent; let parentArray; @@ -388,7 +390,7 @@ class ProfileBuilder extends Component { const {variablesHash, currentSlug} = this.state; const {stripHTML} = this.context.formatters; const {formatters} = this.context; - const variables = variablesHash[currentSlug] ? deepClone(variablesHash[currentSlug]) : null; + const variables = variablesHash[currentSlug] && variablesHash[currentSlug].en ? deepClone(variablesHash[currentSlug].en) : null; const node = this.locateNode.bind(this)(type, id); // Update the label based on the new value. If this is a topic, this is the only thing needed if (node) { @@ -429,7 +431,7 @@ class ProfileBuilder extends Component { const {variablesHash, currentSlug, nodes} = this.state; const {stripHTML} = this.context.formatters; const {formatters} = this.context; - const variables = variablesHash[currentSlug] ? deepClone(variablesHash[currentSlug]) : null; + const variables = variablesHash[currentSlug] && variablesHash[currentSlug].en ? deepClone(variablesHash[currentSlug].en) : null; const p = this.locateProfileNodeBySlug(currentSlug); p.label = varSwap(p.data.slug, formatters, variables); p.childNodes = p.childNodes.map(t => { @@ -451,19 +453,38 @@ class ProfileBuilder extends Component { */ fetchVariables(slug, id, force, callback) { const {variablesHash} = this.state; + const {locale} = this.props; const maybeCallback = () => { if (callback) callback(); this.formatTreeVariables.bind(this)(); }; if (force || !variablesHash[slug]) { if (id) { - axios.get(`/api/variables/${slug}/${id}`).then(resp => { - variablesHash[slug] = resp.data; - this.setState({variablesHash}, maybeCallback); + axios.get(`/api/variables/${slug}/${id}`).then(en => { + const enVars = en.data; + if (!variablesHash[slug]) { + variablesHash[slug] = {en: enVars}; + } + else { + variablesHash[slug] = Object.assign(variablesHash[slug], {en: enVars}); + } + if (locale) { + axios.get(`/api/variables/${slug}/${id}?locale=${locale}`).then(loc => { + const locVars = loc.data; + const locObj = {}; + locObj[locale] = locVars; + variablesHash[slug] = Object.assign(variablesHash[slug], locObj); + this.setState({variablesHash}, maybeCallback); + }); + } + else { + this.setState({variablesHash}, maybeCallback); + } }); } else { - variablesHash[slug] = {_genStatus: {}, _matStatus: {}}; + variablesHash[slug].en = {_genStatus: {}, _matStatus: {}}; + if (locale) variablesHash[slug][locale] = {_genStatus: {}, _matStatus: {}}; this.setState({variablesHash}, maybeCallback); } } diff --git a/packages/cms/src/profile/ProfileEditor.jsx b/packages/cms/src/profile/ProfileEditor.jsx index 0f6160b77..36eb7700f 100644 --- a/packages/cms/src/profile/ProfileEditor.jsx +++ b/packages/cms/src/profile/ProfileEditor.jsx @@ -159,15 +159,32 @@ class ProfileEditor extends Component { .map(g => ) } + {locale &&
+ { minData.generators && minData.generators + .sort((a, b) => a.name.localeCompare(b.name)) + .map(g => ) + } +
} + {/* materializers */}

Materializers @@ -182,16 +199,34 @@ class ProfileEditor extends Component { )} + {locale &&
+ { minData.materializers && minData.materializers + .map(m => + + )} +
} + {/* Top-level Profile */}

Profile @@ -206,14 +241,14 @@ class ProfileEditor extends Component { item={minData} fields={["title", "subtitle"]} type="profile" - variables={variables} + variables={variables.en} /> {locale && } diff --git a/packages/cms/src/profile/TopicEditor.jsx b/packages/cms/src/profile/TopicEditor.jsx index 520e0f50d..a8a119281 100644 --- a/packages/cms/src/profile/TopicEditor.jsx +++ b/packages/cms/src/profile/TopicEditor.jsx @@ -92,7 +92,9 @@ class TopicEditor extends Component { } onSave(minData) { - if (this.props.reportSave) this.props.reportSave("topic", minData.id, minData.title); + const enCon = minData.content.find(c => c.lang === "en"); + const title = enCon.title || "Title Error"; + if (this.props.reportSave) this.props.reportSave("topic", minData.id, title); } onMove() { @@ -121,11 +123,11 @@ class TopicEditor extends Component { if (!minData || !variables) return ; const varOptions = [] - .concat(Object.keys(variables) + .concat(Object.keys(variables.en) .filter(key => !key.startsWith("_")) .sort((a, b) => a.localeCompare(b)) .map(key => { - const value = variables[key]; + const value = variables.en[key]; const type = typeof value; const label = !["string", "number", "boolean"].includes(type) ? ` (${type})` : `: ${`${value}`.slice(0, 20)}${`${value}`.length > 20 ? "..." : ""}`; return ; @@ -189,7 +191,7 @@ class TopicEditor extends Component { fields={["title"]} onSave={this.onSave.bind(this)} type="topic" - variables={variables} + variables={variables.en} /> {locale && } @@ -217,7 +219,7 @@ class TopicEditor extends Component { fields={["subtitle"]} type="topic_subtitle" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables.en} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.subtitles} onMove={this.onMove.bind(this)} @@ -233,7 +235,7 @@ class TopicEditor extends Component { fields={["subtitle"]} type="topic_subtitle" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables[locale]} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.subtitles} onMove={this.onMove.bind(this)} @@ -256,7 +258,7 @@ class TopicEditor extends Component { type="selector" onSave={() => this.forceUpdate()} onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables.en} parentArray={minData.selectors} onMove={this.onMove.bind(this)} /> @@ -280,7 +282,7 @@ class TopicEditor extends Component { fields={["title", "subtitle", "value", "tooltip"]} type="topic_stat" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables.en} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.stats} onMove={this.onMove.bind(this)} @@ -296,7 +298,7 @@ class TopicEditor extends Component { fields={["title", "subtitle", "value", "tooltip"]} type="topic_stat" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables[locale]} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.stats} onMove={this.onMove.bind(this)} @@ -320,7 +322,7 @@ class TopicEditor extends Component { fields={["description"]} type="topic_description" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables.en} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.descriptions} onMove={this.onMove.bind(this)} @@ -337,7 +339,7 @@ class TopicEditor extends Component { fields={["description"]} type="topic_description" onDelete={this.onDelete.bind(this)} - variables={variables} + variables={variables[locale]} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.descriptions} onMove={this.onMove.bind(this)} @@ -360,7 +362,7 @@ class TopicEditor extends Component { preview={preview} onDelete={this.onDelete.bind(this)} type="topic_visualization" - variables={variables} + variables={variables.en} selectors={minData.selectors.map(s => Object.assign({}, s))} parentArray={minData.visualizations} onMove={this.onMove.bind(this)} From eeb60abde9a0324dcfa1d596edcbea8472621ac0 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 6 Feb 2019 16:01:49 -0500 Subject: [PATCH 21/48] fixes topic endpoint to respect locale --- packages/cms/src/api/mortarRoute.js | 29 ++++++++++++------- .../src/components/cards/GeneratorCard.jsx | 10 +++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index 9e926a61f..44e5a654b 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -40,6 +40,7 @@ const profileReq = { }; const topicReq = [ + {association: "content", separate: true}, {association: "subtitles", separate: true, include: [{association: "content", separate: true}] }, @@ -148,14 +149,21 @@ const extractLocaleContent = (obj, locale, mode) => { if (obj[type]) obj[type] = obj[type].map(o => bubbleUp(o, locale)); }); } - const children = mode === "story" ? "storytopics" : "topics"; - if (obj[children]) { - obj[children] = obj[children].map(child => { - child = bubbleUp(child, locale); - ["subtitles", "descriptions", "stats"].forEach(type => { - if (child[type]) child[type] = child[type].map(o => bubbleUp(o, locale)); + if (mode === "profile" || mode === "story") { + const children = mode === "story" ? "storytopics" : "topics"; + if (obj[children]) { + obj[children] = obj[children].map(child => { + child = bubbleUp(child, locale); + ["subtitles", "descriptions", "stats"].forEach(type => { + if (child[type]) child[type] = child[type].map(o => bubbleUp(o, locale)); + }); + return child; }); - return child; + } + } + if (mode === "topic") { + ["subtitles", "descriptions", "stats"].forEach(type => { + if (obj[type]) obj[type] = obj[type].map(o => bubbleUp(o, locale)); }); } return obj; @@ -341,7 +349,8 @@ module.exports = function(app) { delete variables._matStatus; const formatters = resp[1]; const formatterFunctions = formatters4eval(formatters); - const topic = varSwapRecursive(resp[2].toJSON(), formatterFunctions, variables, req.query); + let topic = extractLocaleContent(resp[2], locale, "topic"); + topic = varSwapRecursive(topic, formatterFunctions, variables, req.query); if (topic.subtitles) topic.subtitles.sort(sorter); if (topic.selectors) topic.selectors.sort(sorter); if (topic.stats) topic.stats.sort(sorter); @@ -349,8 +358,8 @@ module.exports = function(app) { if (topic.visualizations) topic.visualizations.sort(sorter); res.json({variables, ...topic}).end(); }) - .catch(() => { - res.json({error: "Unable to find topic."}).end(); + .catch(e => { + res.json({error: `Topic error: ${e.message}`}).end(); }); }); diff --git a/packages/cms/src/components/cards/GeneratorCard.jsx b/packages/cms/src/components/cards/GeneratorCard.jsx index 7e9562b1f..d8e17d990 100644 --- a/packages/cms/src/components/cards/GeneratorCard.jsx +++ b/packages/cms/src/components/cards/GeneratorCard.jsx @@ -146,10 +146,14 @@ class GeneratorCard extends Component { {/* title & edit toggle button */}
- {minData.name} - + }
From 71aba51a2ed7364d3a71759ed41788fd36ae31b1 Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 6 Feb 2019 16:53:56 -0500 Subject: [PATCH 22/48] updates vizes to respect locale, performs urlswap by locale --- packages/cms/app/pages/Profile.jsx | 9 ++++++--- packages/cms/src/api/mortarRoute.js | 2 +- packages/cms/src/components/Viz/index.jsx | 8 ++++++-- .../cms/src/components/cards/GeneratorCard.jsx | 2 +- .../src/components/cards/VisualizationCard.jsx | 8 ++++---- .../src/components/editors/GeneratorEditor.jsx | 6 +++--- packages/cms/src/profile/ProfileBuilder.jsx | 13 ------------- packages/cms/src/profile/TopicEditor.jsx | 17 +++++++++++++++++ packages/cms/src/utils/FUNC.js | 5 +++-- packages/cms/src/utils/d3plusPropify.js | 4 ++-- 10 files changed, 43 insertions(+), 31 deletions(-) diff --git a/packages/cms/app/pages/Profile.jsx b/packages/cms/app/pages/Profile.jsx index 12c3136e2..aee73b11e 100644 --- a/packages/cms/app/pages/Profile.jsx +++ b/packages/cms/app/pages/Profile.jsx @@ -8,7 +8,7 @@ import libs from "../../src/utils/libs"; class Profile extends Component { getChildContext() { - const {formatters, profile, router} = this.props; + const {formatters, locale, profile, router} = this.props; const {variables} = profile; return { formatters: formatters.reduce((acc, d) => { @@ -18,7 +18,8 @@ class Profile extends Component { return acc; }, {}), router, - variables + variables, + locale }; } @@ -39,16 +40,18 @@ class Profile extends Component { Profile.childContextTypes = { formatters: PropTypes.object, + locale: PropTypes.string, router: PropTypes.object, variables: PropTypes.object }; Profile.need = [ - fetchData("profile", "/api/profile//"), + fetchData("profile", "/api/profile//?locale="), fetchData("formatters", "/api/formatters") ]; export default connect(state => ({ formatters: state.data.formatters, + locale: state.i18n.locale, profile: state.data.profile }))(Profile); diff --git a/packages/cms/src/api/mortarRoute.js b/packages/cms/src/api/mortarRoute.js index 44e5a654b..2ff3a5bb7 100644 --- a/packages/cms/src/api/mortarRoute.js +++ b/packages/cms/src/api/mortarRoute.js @@ -207,7 +207,7 @@ module.exports = function(app) { // Generators use as a placeholder. Replace instances of with the provided id from the URL // The .catch here is to handle malformed API urls, returning an empty object const fetches = requests.map(r => { - let url = urlSwap(r, {...req.params, ...cache, ...attr}); + let url = urlSwap(r, {...req.params, ...cache, ...attr, locale}); if (url.indexOf("http") !== 0) { const origin = `http${ req.connection.encrypted ? "s" : "" }://${ req.headers.host }`; url = `${origin}${url.indexOf("/") === 0 ? "" : "/"}${url}`; diff --git a/packages/cms/src/components/Viz/index.jsx b/packages/cms/src/components/Viz/index.jsx index bbc9695bd..c8128dfc1 100644 --- a/packages/cms/src/components/Viz/index.jsx +++ b/packages/cms/src/components/Viz/index.jsx @@ -16,12 +16,15 @@ class Viz extends Component { } render() { - const {formatters, variables} = this.context; + const {formatters} = this.context; + + const variables = this.props.variables || this.context.variables; + const locale = this.props.locale || this.context.locale; const {config, configOverride, className, options, slug} = this.props; // clone config object to allow manipulation - const vizProps = propify(config.logic, formatters, variables); + const vizProps = propify(config.logic, formatters, variables, locale); // If the result of propify has an "error" property, then the provided javascript was malformed and propify // caught an error. Instead of attempting to render the viz, simply show the error to the user. @@ -56,6 +59,7 @@ class Viz extends Component { Viz.contextTypes = { formatters: PropTypes.object, + locale: PropTypes.string, updateSource: PropTypes.func, variables: PropTypes.object }; diff --git a/packages/cms/src/components/cards/GeneratorCard.jsx b/packages/cms/src/components/cards/GeneratorCard.jsx index d8e17d990..a858c67e0 100644 --- a/packages/cms/src/components/cards/GeneratorCard.jsx +++ b/packages/cms/src/components/cards/GeneratorCard.jsx @@ -218,7 +218,7 @@ class GeneratorCard extends Component { >
- +
; const {formatters} = this.context; - const {selectors, type, variables, parentArray, item, preview} = this.props; + const {selectors, type, variables, parentArray, item, preview, locale} = this.props; minData.selectors = selectors; const {logic} = varSwapRecursive(minData, formatters, variables); @@ -135,11 +135,11 @@ class VisualizationCard extends Component { {/* title & edit toggle button */} -
+ {locale === "en" &&
-
+

} {/* reorder buttons */} { parentArray && @@ -166,7 +166,7 @@ class VisualizationCard extends Component { onSave={this.save.bind(this)} /> - { logic ? :

No configuration defined.

} + { logic ? :

No configuration defined.

} ); } diff --git a/packages/cms/src/components/editors/GeneratorEditor.jsx b/packages/cms/src/components/editors/GeneratorEditor.jsx index 936244375..38d45d3b6 100644 --- a/packages/cms/src/components/editors/GeneratorEditor.jsx +++ b/packages/cms/src/components/editors/GeneratorEditor.jsx @@ -85,12 +85,12 @@ class GeneratorEditor extends Component { previewPayload(forceSimple) { const {data} = this.state; const {api} = data; - const {preview, variables} = this.props; + const {preview, variables, locale} = this.props; if (api) { // The API will have an in it that needs to be replaced with the current preview. // Use urlSwap to swap ANY instances of variables between brackets (e.g. ) - // With its corresponding value. - const url = urlSwap(api, Object.assign({}, variables, {id: preview})); + // With its corresponding value. Same goes for locale + const url = urlSwap(api, Object.assign({}, variables, {id: preview, locale})); axios.get(url).then(resp => { const payload = resp.data; let {simple} = this.state; diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index c7039a4fe..958ee01ad 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -47,14 +47,6 @@ class ProfileBuilder extends Component { }); } - getChildContext() { - const {formatters} = this.context; - const {variablesHash, currentSlug} = this.state; - const variables = variablesHash[currentSlug] ? variablesHash[currentSlug].en : {}; - // TODO: how to send down proper lang via context? - return {formatters, variables}; - } - /** * Decode HTML elements such as &. Taken from: * https://stackoverflow.com/questions/3700326/decode-amp-back-to-in-javascript @@ -579,11 +571,6 @@ class ProfileBuilder extends Component { } } -ProfileBuilder.childContextTypes = { - formatters: PropTypes.object, - variables: PropTypes.object -}; - ProfileBuilder.contextTypes = { formatters: PropTypes.object }; diff --git a/packages/cms/src/profile/TopicEditor.jsx b/packages/cms/src/profile/TopicEditor.jsx index a8a119281..42e5dc08a 100644 --- a/packages/cms/src/profile/TopicEditor.jsx +++ b/packages/cms/src/profile/TopicEditor.jsx @@ -359,6 +359,7 @@ class TopicEditor extends Component { )} + {locale &&
+ { minData.visualizations && minData.visualizations.map(v => + Object.assign({}, s))} + parentArray={minData.visualizations} + onMove={this.onMove.bind(this)} + /> + )} +
} ); } diff --git a/packages/cms/src/utils/FUNC.js b/packages/cms/src/utils/FUNC.js index a1a06578b..c5bc67525 100644 --- a/packages/cms/src/utils/FUNC.js +++ b/packages/cms/src/utils/FUNC.js @@ -54,11 +54,12 @@ function func2obj(func) { } -function parse(config, formatters = {}) { +function parse(config, formatters = {}, locale = "en") { const globals = { formatters, - libs + libs, + locale }; function parseFunction(obj) { diff --git a/packages/cms/src/utils/d3plusPropify.js b/packages/cms/src/utils/d3plusPropify.js index 7873fd7d3..6b16c3500 100644 --- a/packages/cms/src/utils/d3plusPropify.js +++ b/packages/cms/src/utils/d3plusPropify.js @@ -1,14 +1,14 @@ import {assign} from "d3plus-common"; import {parse} from "./FUNC"; -export default (logic, formatters = {}, variables = {}) => { +export default (logic, formatters = {}, variables = {}, locale = "en") => { let config; // The logic provided might be malformed. Wrap it in a try/catch to be sure we don't // crash / RSOD whatever page is making use of propify. try { - config = parse({vars: ["variables"], logic}, formatters)(variables); + config = parse({vars: ["variables"], logic}, formatters, locale)(variables); } // If the javascript fails, return a special error object for the front-end to use. catch (e) { From a04f9783c0ebc1f1e62b759742798350a2ccbb3b Mon Sep 17 00:00:00 2001 From: jhmullen Date: Wed, 6 Feb 2019 17:43:53 -0500 Subject: [PATCH 23/48] adds localeDefault to outer Builder --- packages/cms/src/Builder.jsx | 16 ++++++++-------- packages/cms/src/components/cards/Flag.jsx | 8 +------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/cms/src/Builder.jsx b/packages/cms/src/Builder.jsx index fc5a2a39c..8d12981ff 100644 --- a/packages/cms/src/Builder.jsx +++ b/packages/cms/src/Builder.jsx @@ -6,7 +6,6 @@ import StoryBuilder from "./story/StoryBuilder"; import FormatterEditor from "./formatter/FormatterEditor"; import {fetchData} from "@datawheel/canon-core"; import {connect} from "react-redux"; -// import formatters from "./utils/formatters"; import "./cms.css"; import "./themes/cms-dark.css"; @@ -18,10 +17,10 @@ class Builder extends Component { super(props); this.state = { currentTab: "profiles", - // formatters, theme: "cms-light", locales: false, locale: false, + localeDefault: false, formatters: (props.formatters || []).reduce((acc, d) => { const f = Function("n", "libs", "formatters", d.logic); @@ -39,10 +38,11 @@ class Builder extends Component { // env.CANON_LANGUAGES = false; // Retrieve the langs from canon vars, use it to build the second language select dropdown. + const localeDefault = env.CANON_LANGUAGE_DEFAULT || "en"; if (env.CANON_LANGUAGES && env.CANON_LANGUAGES.includes(",")) { - const locales = env.CANON_LANGUAGES.split(",").filter(l => l !== "en"); + const locales = env.CANON_LANGUAGES.split(",").filter(l => l !== localeDefault); const locale = locales[0]; - this.setState({locales, locale}); + this.setState({locales, locale, localeDefault}); } } @@ -67,7 +67,7 @@ class Builder extends Component { } render() { - const {currentTab, theme, locale, locales} = this.state; + const {currentTab, theme, locale, locales, localeDefault} = this.state; const {isEnabled} = this.props; const navLinks = ["profiles", "stories", "formatters"]; @@ -84,7 +84,7 @@ class Builder extends Component { {navLink} )} - {locales && locale &&

@@ -158,7 +158,7 @@ class SelectorCard extends Component { icon="false" inline="true" > -
+
@@ -206,7 +206,7 @@ class TextCard extends Component { title="Text Editor" inline="true" > -
+
displaySort.indexOf(a) - displaySort.indexOf(b))} />
diff --git a/packages/cms/src/components/cards/VisualizationCard.jsx b/packages/cms/src/components/cards/VisualizationCard.jsx index 313f488de..c2dc6af27 100644 --- a/packages/cms/src/components/cards/VisualizationCard.jsx +++ b/packages/cms/src/components/cards/VisualizationCard.jsx @@ -110,7 +110,7 @@ class VisualizationCard extends Component { cancelButtonText="Cancel" confirmButtonText={alertObj.confirm} className="cms-confirm-alert" - iconName="pt-icon-warning-sign" + iconName="bp3-icon-warning-sign" intent={Intent.DANGER} isOpen={alertObj} onConfirm={alertObj.callback} @@ -124,7 +124,7 @@ class VisualizationCard extends Component { cancelButtonText="Cancel" confirmButtonText={alertObj.confirm} className="cms-confirm-alert" - iconName="pt-icon-warning-sign" + iconName="bp3-icon-warning-sign" intent={Intent.DANGER} isOpen={alertObj} onConfirm={alertObj.callback} @@ -137,7 +137,7 @@ class VisualizationCard extends Component { {/* title & edit toggle button */} {locale === localeDefault &&
} @@ -158,7 +158,7 @@ class VisualizationCard extends Component { title="Variable Editor" inline="true" > -
+
You have access to the variable resp, which represents the response to the above API call.

, - materializer:

You have access to all variables previously created by generators

, - profile_visualization:

You have access to all variables previously created by generators and materializers.

, - topic_visualization:

You have access to all variables previously created by generators and materializers.

, - formatter:

You have access to the variable n, which represents the string to be formatted.

+ generator:

You have access to the variable resp, which represents the response to the above API call.

, + materializer:

You have access to all variables previously created by generators

, + profile_visualization:

You have access to all variables previously created by generators and materializers.

, + topic_visualization:

You have access to all variables previously created by generators and materializers.

, + formatter:

You have access to the variable n, which represents the string to be formatted.

}; const postMessage = { - generator:

Be sure to return an object with the variables you want stored as keys.

, - materalizer:

Be sure to return an object with the variables you want stored as keys.

, - profile_visualization:

Be sure to return a valid config object for a visualization

, - topic_visualization:

Be sure to return a valid config object for a visualization

, - formatter:

Be sure to return a string that represents your formatted content.

+ generator:

Be sure to return an object with the variables you want stored as keys.

, + materalizer:

Be sure to return an object with the variables you want stored as keys.

, + profile_visualization:

Be sure to return a valid config object for a visualization

, + topic_visualization:

Be sure to return a valid config object for a visualization

, + formatter:

Be sure to return a string that represents your formatted content.

}; const varOptions = [] @@ -214,7 +214,7 @@ class GeneratorEditor extends Component { cancelButtonText="Cancel" confirmButtonText={alertObj.confirm} className="cms-confirm-alert" - iconName="pt-icon-warning-sign" + iconName="bp3-icon-warning-sign" intent={Intent.DANGER} isOpen={alertObj} onConfirm={alertObj.callback} @@ -229,11 +229,11 @@ class GeneratorEditor extends Component {
- +
- +
} @@ -241,9 +241,9 @@ class GeneratorEditor extends Component { { type === "generator" &&
-
- -
@@ -257,9 +257,9 @@ class GeneratorEditor extends Component { {/* visibility */}
{ (type === "profile_visualization" || type === "topic_visualization") && -
+
diff --git a/packages/cms/src/components/editors/PlainTextEditor.jsx b/packages/cms/src/components/editors/PlainTextEditor.jsx index b655284e3..c5d3d0b6e 100644 --- a/packages/cms/src/components/editors/PlainTextEditor.jsx +++ b/packages/cms/src/components/editors/PlainTextEditor.jsx @@ -33,7 +33,7 @@ class PlainTextEditor extends Component { const inputs = fields.map(f =>
- +
); diff --git a/packages/cms/src/components/editors/SelectorEditor.jsx b/packages/cms/src/components/editors/SelectorEditor.jsx index 9a7e81b34..ca252ad21 100644 --- a/packages/cms/src/components/editors/SelectorEditor.jsx +++ b/packages/cms/src/components/editors/SelectorEditor.jsx @@ -187,11 +187,11 @@ class SelectorEditor extends Component {
@@ -213,17 +213,17 @@ class SelectorEditor extends Component {
  • {/* option / allowed */} -
  • + delete
  • @@ -257,18 +257,18 @@ class SelectorEditor extends Component {
    {/* custom default */} {showCustom && -
    +
    Custom default -
    +
    diff --git a/packages/cms/src/components/editors/SimpleGeneratorEditor.jsx b/packages/cms/src/components/editors/SimpleGeneratorEditor.jsx index 22470e349..c8f5c0f75 100644 --- a/packages/cms/src/components/editors/SimpleGeneratorEditor.jsx +++ b/packages/cms/src/components/editors/SimpleGeneratorEditor.jsx @@ -147,7 +147,7 @@ export default class SimpleGeneratorEditor extends Component { cancelButtonText="Cancel" confirmButtonText="Rebuild" className="confirm-alert" - iconName="pt-icon-warning-sign" + iconName="bp3-icon-warning-sign" intent={Intent.DANGER} isOpen={rebuildAlertOpen} onConfirm={this.rebuild.bind(this)} @@ -157,7 +157,7 @@ export default class SimpleGeneratorEditor extends Component {
    Generated variables
    diff --git a/packages/cms/src/components/editors/SimpleVisualizationEditor.jsx b/packages/cms/src/components/editors/SimpleVisualizationEditor.jsx index 5b34802b1..eaafa5962 100644 --- a/packages/cms/src/components/editors/SimpleVisualizationEditor.jsx +++ b/packages/cms/src/components/editors/SimpleVisualizationEditor.jsx @@ -150,7 +150,7 @@ export default class SimpleVisualizationEditor extends Component { cancelButtonText="Cancel" confirmButtonText="Rebuild" className="confirm-alert" - iconName="pt-icon-warning-sign" + iconName="bp3-icon-warning-sign" intent={Intent.DANGER} isOpen={rebuildAlertOpen} onConfirm={this.rebuild.bind(this)} @@ -162,10 +162,10 @@ export default class SimpleVisualizationEditor extends Component { {/* data URL */}
    -
    - +
    + {object.data && - } @@ -174,7 +174,7 @@ export default class SimpleVisualizationEditor extends Component {
    Type -
    +
    - :
    + :
    {varOptions} @@ -95,13 +95,13 @@ class TextEditor extends Component { } {/* -
    +
    - + */} {quills} diff --git a/packages/cms/src/components/topics/Card.jsx b/packages/cms/src/components/topics/Card.jsx index f472c858d..71eff8643 100644 --- a/packages/cms/src/components/topics/Card.jsx +++ b/packages/cms/src/components/topics/Card.jsx @@ -16,7 +16,7 @@ export default class Card extends Component { const statGroups = nest().key(d => d.title).entries(stats); - return
    + return
    { title &&

    diff --git a/packages/cms/src/components/topics/components/Selector.jsx b/packages/cms/src/components/topics/components/Selector.jsx index 93b692afb..534e680e6 100644 --- a/packages/cms/src/components/topics/components/Selector.jsx +++ b/packages/cms/src/components/topics/components/Selector.jsx @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PropTypes from "prop-types"; -import {Select} from "@blueprintjs/labs"; +import {Select} from "@blueprintjs/select"; import {MenuItem, Icon} from "@blueprintjs/core"; class Selector extends Component { @@ -51,13 +51,13 @@ class Selector extends Component { return
    { title && } -
    +
    { type === "multi" ?
    - { comparisons.map(d =>
    + { comparisons.map(d =>
    { variables[d] } -
    ) }
    { comparisons.length !== options.length @@ -67,7 +67,7 @@ class Selector extends Component { onItemSelect={this.addComparison.bind(this)} items={options.map(d => d.option)} itemRenderer={this.renderItem.bind(this)}> - diff --git a/packages/cms/src/components/topics/topic.css b/packages/cms/src/components/topics/topic.css index 3600d941c..8cfb704b4 100644 --- a/packages/cms/src/components/topics/topic.css +++ b/packages/cms/src/components/topics/topic.css @@ -61,7 +61,7 @@ } } - & .pt-non-ideal-state { + & .bp3-non-ideal-state { left: 0; position: absolute; top: 0; diff --git a/packages/cms/src/formatter/FormatterEditor.jsx b/packages/cms/src/formatter/FormatterEditor.jsx index e0148babc..dfdc189b7 100644 --- a/packages/cms/src/formatter/FormatterEditor.jsx +++ b/packages/cms/src/formatter/FormatterEditor.jsx @@ -52,10 +52,10 @@ class FormatterEditor extends Component {

    Formatters

    -

    Javascript Formatters for Canon text components.

    +

    Javascript Formatters for Canon text components.

    { formatters && formatters .sort((a, b) => a.name.localeCompare(b.name)) diff --git a/packages/cms/src/profile/NewProfile.jsx b/packages/cms/src/profile/NewProfile.jsx index cb0c3aa9a..c8378c589 100644 --- a/packages/cms/src/profile/NewProfile.jsx +++ b/packages/cms/src/profile/NewProfile.jsx @@ -1,6 +1,6 @@ import React, {Component} from "react"; import {MenuItem} from "@blueprintjs/core"; -import {MultiSelect} from "@blueprintjs/labs"; +import {MultiSelect} from "@blueprintjs/select"; class NewProfile extends Component { @@ -77,14 +77,14 @@ class NewProfile extends Component { .map(m => ) : []; return ( -
    +
    - +
    Dimension: -
    +
    {measureOptions} diff --git a/packages/cms/src/profile/ProfileBuilder.css b/packages/cms/src/profile/ProfileBuilder.css index f0676c512..54d784cda 100644 --- a/packages/cms/src/profile/ProfileBuilder.css +++ b/packages/cms/src/profile/ProfileBuilder.css @@ -1,9 +1,9 @@ .cms-sidebar { - & .pt-tree-node { + & .bp3-tree-node { &:hover { cursor: pointer; } - & .pt-tree-node-content { + & .bp3-tree-node-content { line-height: 1.25; padding-top: 0.375rem; padding-bottom: 0.5rem; @@ -14,33 +14,33 @@ background: none; } - & .pt-tree-node-caret { + & .bp3-tree-node-caret { padding: 0.25rem 0.5rem; cursor: auto; } - & .pt-icon-cog { + & .bp3-icon-cog { padding: 0.25rem 0; } - & .pt-tree-node-caret, - & .pt-icon-cog { + & .bp3-tree-node-caret, + & .bp3-icon-cog { line-height: 0.5rem !important; min-width: 0; /* override blueprint */ font-size: 0.875rem; } - & .pt-tree-node-label { + & .bp3-tree-node-label { white-space: normal; } - & .pt-tree-node-secondary-label { + & .bp3-tree-node-secondary-label { line-height: 14px; } } } - & .pt-tree-node-content-1 { + & .bp3-tree-node-content-1 { padding-left: 1.25rem; } - & .pt-tree-node-content-2 { + & .bp3-tree-node-content-2 { padding-left: 1.5em; } } @@ -54,15 +54,15 @@ & .input-block, & .area-block { width: 100%; display: flex; - & .pt-label { + & .bp3-label { flex: 1; &:first-child { margin-right: 20px; } - & .pt-input { + & .bp3-input { width: 100%; } - & .pt-select { + & .bp3-select { width: 100%; } & .quill { diff --git a/packages/cms/src/profile/ProfileBuilder.jsx b/packages/cms/src/profile/ProfileBuilder.jsx index 32aa60962..295cc8323 100644 --- a/packages/cms/src/profile/ProfileBuilder.jsx +++ b/packages/cms/src/profile/ProfileBuilder.jsx @@ -508,7 +508,7 @@ class ProfileBuilder extends Component { let profileSearch = ""; if (currentNode && currentSlug) { profileSearch = -
    +
    {preview ? `Current data ID: ${preview}` : "Preview profile"} {d.name}} @@ -529,7 +529,7 @@ class ProfileBuilder extends Component { {/* new entity */} {/* change slug */} -