diff --git a/css/ucb-related-articles.css b/css/ucb-related-articles.css
new file mode 100644
index 0000000..600ce75
--- /dev/null
+++ b/css/ucb-related-articles.css
@@ -0,0 +1,21 @@
+.ucb-related-articles-block{
+ display: none;
+}
+
+.ucb-article-card-img {
+ max-width: 100%;
+ height: auto;
+ }
+
+ .ucb-article-card-data {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .ucb-article-card {
+ margin-bottom: 2em;
+ }
+
+ .ucb-related-article-card-body{
+ font-size: 85%;
+ }
\ No newline at end of file
diff --git a/js/ucb-related-articles.js b/js/ucb-related-articles.js
new file mode 100644
index 0000000..33d2687
--- /dev/null
+++ b/js/ucb-related-articles.js
@@ -0,0 +1,408 @@
+const relatedArticlesBlock = document.querySelector(".ucb-related-articles-block");
+const excludeTagEl = document.querySelector("#ucb-related-articles-exclude-tags")
+const excludeCatEl = document.querySelector("#ucb-related-articles-exclude-categories")
+
+// Get the tax ids for excluded Cats and Tags
+let excludeCatArr = [];
+if (excludeCatEl){
+ excludeCatArr = excludeCatEl.getAttribute('data-excludecategories').split(',').filter(Number)
+}
+
+let excludeTagArr = [];
+if (excludeTagEl){
+ excludeTagArr = excludeTagEl.getAttribute('data-excludetags').split(',').filter(Number)
+}
+
+// Global variable to store articles that are good matches. Danger!
+let articleArrayWithScores = []
+
+// Related Shown?
+const relatedShown = relatedArticlesBlock.getAttribute('data-relatedshown') != "Off" ? true : false;
+
+// This function returns a total of matched categories or tags
+function checkMatches(data, ids){
+ let count = 0;
+ let numberArr = ids.map(Number)
+ data.forEach((article)=>{
+ if(numberArr.includes(article.meta.drupal_internal__target_id)){
+ count++
+ }
+ })
+ return count
+}
+// This function takes in the tag endpoint and current array of related articles, returns the array of related articles once it has a count of 3.
+async function getArticlesWithTags(url, array, articleTags ,numLeft){
+ fetch(url)
+ .then(response => response.json())
+ .then(data=>{
+ let relatedArticlesDiv = document.querySelector('.related-articles-section')
+
+ // console.log("TAG DATA", data)
+ let returnedArticles = data.data
+ let existingIds = [];
+ // create an array of existing ids
+ array.map(article=>{
+ existingIds.push(article.id)
+ })
+
+ // remove any articles already chosen, excluded categories, and the current article
+ let filterData = []
+ returnedArticles.map((article)=>{
+
+ let thisArticleCats = article.relationships.field_ucb_article_categories.data
+ let thisArticleTags = article.relationships.field_ucb_article_tags.data
+ let urlCheck = article.attributes.path.alias;
+ let toInclude = true;
+ //remove excluded category & tagss
+ if(thisArticleTags.length){ // if there are categories
+ thisArticleTags.forEach((tag)=>{
+ let id = tag.meta.drupal_internal__target_id.toString();
+ // console.log(id)
+ if(excludeCatArr.includes(id)){
+ toInclude = false;
+ return
+ }
+ })
+ }
+
+ if(thisArticleCats.length){ // if there are categories
+ thisArticleCats.forEach((category)=>{ // check each category
+ let id = category.meta.drupal_internal__target_id.toString();
+ // console.log('cat id', id)
+ if(excludeCatArr.includes(id)){ // if excluded, do not proceed
+ // console.log('i have an included id')
+ toInclude = false;
+ return
+ }
+ if( urlCheck == window.location.pathname) { // if same article, do not proceed
+ // console.log("im the current article! ignore me!")
+ toInclude = false
+ return;
+ } // proceed
+ // create an object out of
+ // add to running array of possible matches
+
+ })
+ }
+
+ // console.log(article.id, existingIds.includes(article.id))
+ if(existingIds.includes(article.id) || article.attributes.path.alias == window.location.pathname ){
+ toInclude = false
+ // filter on categories
+ }
+ if(toInclude){
+ let articleObj ={}
+ articleObj.id = article.id
+ articleObj.catMatches = checkMatches(article.relationships.field_ucb_article_tags.data, articleTags) // count the number of matches
+ articleObj.article = article
+ filterData.push(articleObj)
+ }
+ else{
+ return
+ }
+ })
+
+ // console.log(returnedArticles)
+
+ let urlObj = {};
+ let idObj = {};
+
+ if (data.included) {
+ let filteredData = data.included.filter((url) => {
+ return url.attributes.uri !== undefined;
+ })
+ // creates the urlObj, key: data id, value: url
+ filteredData.map((pair) => {
+ urlObj[pair.id] = pair.links.focal_image.href;
+ })
+
+ // removes all other included data besides images in our included media
+ let idFilterData = data.included.filter((item) => {
+ return item.type == "media--image";
+ })
+ // using the image-only data, creates the idObj => key: thumbnail id, value : data id
+ idFilterData.map((pair) => {
+ idObj[pair.id] = pair.relationships.thumbnail.data.id;
+ })
+ }
+
+ // Rank based on matches (cats)
+ filterData.sort((a, b) => a.catMatches - b.catMatches).reverse();
+ filterData.length = numLeft // sets to fill in however many articles are left
+
+ filterData.map((article)=>{
+ let articleCard = document.createElement('div')
+ articleCard.classList = "ucb-article-card col-sm-12 col-md-6 col-lg-4"
+ let title = article.article.attributes.title;
+ let link = article.article.attributes.path.alias;
+ // if no thumbnail, show no image
+ if (!article.article.relationships.field_ucb_article_thumbnail.data) {
+ image = "";
+ } else {
+ //Use the idObj as a memo to add the corresponding image url
+ let thumbId = article.article.relationships.field_ucb_article_thumbnail.data.id;
+ image = urlObj[idObj[thumbId]];
+ }
+ let body = ""
+ // if summary, use that
+ if( article.article.attributes.field_ucb_article_summary != null){
+ body = article.article.attributes.field_ucb_article_summary;
+ }
+
+ // if image, use it
+ if (!article.article.relationships.field_ucb_article_thumbnail.data) {
+ imageSrc = "";
+ } else {
+ //Use the idObj as a memo to add the corresponding image url
+ let thumbId = article.article.relationships.field_ucb_article_thumbnail.data.id;
+ imageSrc = urlObj[idObj[thumbId]];
+ }
+
+ if(link && imageSrc) {
+ image = `
`;
+ }
+ let outputHTML = `
+
${image}
+
+ `;
+
+ articleCard.innerHTML = outputHTML
+ relatedArticlesDiv.appendChild(articleCard)
+ })
+
+ })
+}
+
+// If related articles is toggled on create section, run the fetch
+if(relatedShown){
+ // Iterate through the json data of the articles tags and categories, store the values
+ let x=0
+ let n=0
+ // Tag array - iterate through and store taxonomy ID's for fetch
+ const tagJSON = JSON.parse(relatedArticlesBlock.getAttribute('data-tagsjson'));
+ const myTagIDs = []
+
+ while(tagJSON[n]!=undefined){
+ myTagIDs.push(tagJSON[n]["#cache"].tags[0])
+ n++;
+ }
+ const myTags = myTagIDs.map((id)=> id.replace(/\D/g,'')) // remove blanks, get only the tag ID#s
+
+ // console.log("my tags", myTags)
+
+ // Cat array - iterate through and store taxonomy ID's for fetch
+ const catsJSON = JSON.parse(relatedArticlesBlock.getAttribute('data-catsjson'));
+ const myCatsID= [];
+ while(catsJSON[x]!=undefined){
+ myCatsID.push(catsJSON[x]["#cache"].tags[0])
+ x++;
+ }
+
+ const myCats = myCatsID.map((id)=> id.replace(/\D/g,''))// remove blanks, get only the cat ID#s
+
+ // Using tags and categories, construct an API call
+ const rootURL = `/jsonapi/node/ucb_article?include[node--ucb_article]=uid,title,ucb_article_content,created,field_ucb_article_summary,field_ucb_article_categories,field_ucb_article_tags,field_ucb_article_thumbnail&include=field_ucb_article_thumbnail.field_media_image&fields[file--file]=uri,url%20&filter[published][group][conjunction]=AND&filter[publish-check][condition][path]=status&filter[publish-check][condition][value]=1&filter[publish-check][condition][memberOf]=published`;
+
+ const tagQuery = buildTagFilter(myTags)
+ const catQuery = buildCatFilter(myCats)
+
+ // Constructs the tag portion of the API filter
+ function buildTagFilter(array){
+ let string = `${rootURL}`
+
+ array.forEach(value => {
+ let tagFilterString = `&filter[filter-tag${value}][condition][path]=field_ucb_article_tags.meta.drupal_internal__target_id&filter[filter-tag${value}][condition][value]=${value}&filter[filter-tag${value}][condition][memberOf]=tag-include`;
+ string += tagFilterString
+ });
+ // console.log(string)
+ return string
+ // let tagFilterString = ``
+ }
+ // Constructs the category portion of the API filter
+ function buildCatFilter(array){
+ let string = `${rootURL}`
+ array.forEach(value=>{
+ let catFilterString = `&filter[filter-cat${value}][condition][path]=field_ucb_article_categories.meta.drupal_internal__target_id&filter[filter-cat${value}][condition][value]=${value}&filter[filter-cat${value}][condition][memberOf]=cat-include`
+ string += catFilterString
+
+ });
+ return string
+ }
+
+ const URL = `${catQuery}`
+
+ // Fetch
+ async function getArticles(URL){
+ fetch(URL)
+ .then(response=>response.json())
+ .then(data=> {
+ // Below objects are needed to match images with their corresponding articles.
+ // There are two endpoints => data.data (article) and data.included (incl. media), both needed to associate a media library image with its respective article
+ let urlObj = {};
+ let idObj = {};
+ // Remove any blanks from our articles before map
+ if (data.included) {
+ let filteredData = data.included.filter((url) => {
+ return url.attributes.uri !== undefined;
+ })
+
+ // creates the urlObj, key: data id, value: url
+ filteredData.map((pair) => {
+ urlObj[pair.id] = pair.links.focal_image.href;
+ })
+
+ // removes all other included data besides images in our included media
+ let idFilterData = data.included.filter((item) => {
+ return item.type == "media--image";
+ })
+ // using the image-only data, creates the idObj => key: thumbnail id, value : data id
+ idFilterData.map((pair) => {
+ idObj[pair.id] = pair.relationships.thumbnail.data.id;
+ })
+ }
+ let returnedArticles = data.data
+ // console.log("my returned articles from categories", returnedArticles)
+ // Create an array of options to render with additional checks
+ returnedArticles.map((article)=> {
+ let thisArticleCats = article.relationships.field_ucb_article_categories.data
+ let thisArticleTags = article.relationships.field_ucb_article_tags.data
+ let urlCheck = article.attributes.path.alias;
+ let toInclude = true;
+ //remove excluded category & tagss
+ if(thisArticleTags.length){ // if there are categories
+ thisArticleTags.forEach((tag)=>{
+ let id = tag.meta.drupal_internal__target_id.toString();
+ // console.log(id)
+ if(excludeCatArr.includes(id)){
+ toInclude = false;
+ return
+ }
+ })
+ }
+
+ if(thisArticleCats.length){ // if there are categories
+ thisArticleCats.forEach((category)=>{ // check each category
+ let id = category.meta.drupal_internal__target_id.toString();
+ // console.log('cat id', id)
+ if(excludeCatArr.includes(id)){ // if excluded, do not proceed
+ // console.log('i have an included id')
+ toInclude = false;
+ return
+ }
+ if( urlCheck == window.location.pathname) { // if same article, do not proceed
+ // console.log("im the current article! ignore me!")
+ toInclude = false
+ return;
+ } // proceed
+ // create an object out of
+ // add to running array of possible matches
+
+ })
+ }
+ // if it triggered any fail conditions, do not proceed with the article
+ if(toInclude){
+ let articleObj = {}
+ articleObj.id = article.id
+ articleObj.catMatches = checkMatches(article.relationships.field_ucb_article_categories.data, myCats) // count the number of matches
+ articleObj.article = article // contain the existing article
+ articleArrayWithScores.push(articleObj)
+ }
+
+ })
+
+ //Remove current article from those availabile in the block
+ articleArrayWithScores.filter((article)=>{
+ if(article.article.attributes.path.alias == window.location.pathname){
+ articleArrayWithScores.splice(articleArrayWithScores.indexOf(article),1)
+ } else {
+ return article;
+ }
+ })
+ articleArrayWithScores.sort((a, b) => a.catMatches - b.catMatches).reverse(); // sort in order
+
+ //Remove articles without matches from those availabile in the block
+ const finalArr = articleArrayWithScores.filter(article=> article.catMatches > 0)
+
+ // console.log("LASST PASS ARTICLES WITH SCORES", finalArr)
+ // if more than 3 articles, take the top 3
+ if(finalArr.length>3){
+ finalArr.length = 3
+ } else if(finalArr.length<3){
+ let howManyLeft = 3 - finalArr.length
+ // if less than 3, grab the most tags
+ getArticlesWithTags(tagQuery,finalArr, myTags, howManyLeft);
+ // console.log(howManyLeft)
+
+ }
+
+
+
+
+
+
+ // Create the article cards contained within the block, assign classes
+ let relatedArticlesDiv = document.createElement('div')
+ relatedArticlesDiv.classList = "row related-articles-section"
+ relatedArticlesBlock.appendChild(relatedArticlesDiv)
+
+ finalArr.map((article)=>{
+ let articleCard = document.createElement('div')
+ articleCard.classList = "ucb-article-card col-sm-12 col-md-6 col-lg-4"
+ let title = article.article.attributes.title;
+ let link = article.article.attributes.path.alias;
+ // if no thumbnail, show no image
+ if (!article.article.relationships.field_ucb_article_thumbnail.data) {
+ image = "";
+ } else {
+ //Use the idObj as a memo to add the corresponding image url
+ let thumbId = article.article.relationships.field_ucb_article_thumbnail.data.id;
+ image = urlObj[idObj[thumbId]];
+ }
+ let body = ""
+ // if summary, use that
+ if( article.article.attributes.field_ucb_article_summary != null){
+ body = article.article.attributes.field_ucb_article_summary;
+ }
+
+ // if image, use it
+ if (!article.article.relationships.field_ucb_article_thumbnail.data) {
+ imageSrc = "";
+ } else {
+ //Use the idObj as a memo to add the corresponding image url
+ let thumbId = article.article.relationships.field_ucb_article_thumbnail.data.id;
+ imageSrc = urlObj[idObj[thumbId]];
+ }
+
+ if(link && imageSrc) {
+ image = `
`;
+ }
+ let outputHTML = `
+ ${image}
+
+ `;
+ // Append
+ articleCard.innerHTML = outputHTML
+ relatedArticlesDiv.appendChild(articleCard)
+ })
+
+ })
+}
+
+ getArticles(URL) // init
+
+
+
+
+
+ // Reveal related block after creating cards
+ relatedArticlesBlock.style.display = "block"
+
+} else {
+ relatedArticlesBlock.style.display = "none";
+}
diff --git a/templates/content/node--ucb-article.html.twig b/templates/content/node--ucb-article.html.twig
index ebc7f97..0a94da9 100644
--- a/templates/content/node--ucb-article.html.twig
+++ b/templates/content/node--ucb-article.html.twig
@@ -8,6 +8,7 @@
{{ attach_library('ucb2021_base/ucb-page') }}
{{ attach_library('ucb2021_base/ucb-article') }}
+{{ attach_library('ucb2021_base/ucb-related-articles') }}
{%
set classes = [
@@ -18,6 +19,7 @@ view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
]
%}
+
{% set social_share_button_position = drupal_config('ucb2021_base.settings', 'ucb_social_share_position')%}
{% set social_share_class = "" %}
{% if social_share_button_position == "0"%}
@@ -36,9 +38,27 @@ view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
{#Dummy variable to ensure that all content tags are set for caching purposes#}
{% set content_render = content|render %}
-
{% if not content.field_ucb_article_external_url|render %}
+
+{# Related Articles setup #}
+{# {% set showRelated = ''%} #}
+
+{% set myTags %}
+ {{ content.field_ucb_article_tags }}
+{% endset %}
+{% set myCats %}
+ {{ content.field_ucb_article_categories }}
+{% endset %}
+{% set showRelated %}
+ {{content.field_ucb_related_articles_parag.0}}
+{% endset %}
+{# {% if show_Related != "Off" %}
+ {% set showRelated = true %}
+ {% endset %}
+{% endif %} #}
+
+
{{ label }}
{# TO DO - conditional render #}
@@ -83,8 +103,13 @@ view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
{{ content.field_ucb_article_tags }}
{% endif %}
+{# TO DO -- Related Articles Block #}
+
+ {{content.field_ucb_related_articles_parag}}
+
+
{% else %}
{% if not user.hasPermission('update node') %}
{# redirect non-authenticated users to the specified URL #}
diff --git a/templates/field/field--field-ucb-article-categories.html.twig b/templates/field/field--field-ucb-article-categories.html.twig
index 785848f..4f68559 100644
--- a/templates/field/field--field-ucb-article-categories.html.twig
+++ b/templates/field/field--field-ucb-article-categories.html.twig
@@ -6,5 +6,7 @@
#}
{% for item in items %}
+
{{ item|render }}
+
{% endfor %}
\ No newline at end of file
diff --git a/templates/field/field--node--field-ucb-related-articles-parag.html.twig b/templates/field/field--node--field-ucb-related-articles-parag.html.twig
new file mode 100644
index 0000000..82ed21c
--- /dev/null
+++ b/templates/field/field--node--field-ucb-related-articles-parag.html.twig
@@ -0,0 +1,3 @@
+{% for item in items %}
+ {{item}}
+{% endfor %}
\ No newline at end of file
diff --git a/templates/field/field--paragraph--field-ucb-related-cat-exclude.html.twig b/templates/field/field--paragraph--field-ucb-related-cat-exclude.html.twig
new file mode 100644
index 0000000..2cd26d9
--- /dev/null
+++ b/templates/field/field--paragraph--field-ucb-related-cat-exclude.html.twig
@@ -0,0 +1,19 @@
+{% set excludeCategories = '' %}
+{% set myExCategories =
+ items|render|striptags|trim|split(' ')
+%}
+
+
+{% for item in myExCategories %}
+ {% if item %}
+ {% if loop.last %}
+ {% set excludeCategories = excludeCategories ~ (item|trim) %}
+ {% else %}
+ {% set excludeCategories = excludeCategories ~ (item|trim) ~ ',' %}
+ {% endif %}
+ {% endif %}
+{% endfor %}
+
+
+
+
diff --git a/templates/field/field--paragraph--field-ucb-related-tag-exclude.html.twig b/templates/field/field--paragraph--field-ucb-related-tag-exclude.html.twig
new file mode 100644
index 0000000..a74bbd4
--- /dev/null
+++ b/templates/field/field--paragraph--field-ucb-related-tag-exclude.html.twig
@@ -0,0 +1,16 @@
+{% set excludeTags = '' %}
+{% set myExTags =
+ items|render|striptags|trim|split('')
+%}
+
+{% for item in myExTags %}
+ {% if item %}
+ {% if loop.last %}
+ {% set excludeTags = excludeTags ~ (item|trim) %}
+ {% else %}
+ {% set excludeTags = excludeTags ~ (item|trim) ~ ',' %}
+ {% endif %}
+ {% endif %}
+{% endfor %}
+
+
diff --git a/ucb2021_base.libraries.yml b/ucb2021_base.libraries.yml
index b710a10..75cb2cc 100755
--- a/ucb2021_base.libraries.yml
+++ b/ucb2021_base.libraries.yml
@@ -164,7 +164,14 @@ ucb-article-list:
theme:
css/ucb-article-list.css: {}
+ucb-related-articles:
+ version: 1.x
+ js:
+ js/ucb-related-articles.js: {}
+ css:
+ theme:
+ css/ucb-related-articles.css: {}
ucb-site-contact-info-footer:
css:
theme:
- css/ucb-site-contact-info-footer.css: {}
\ No newline at end of file
+ css/ucb-site-contact-info-footer.css: {}