if (document.getElementById('factbase-content')) {
Vue.createApp({
/**
* Initializes the component before it is mounted.
* Retrieves query parameters from the URL and sets them as component properties.
* Calls the `search` method to perform a search based on the retrieved parameters.
*/
beforeMount() {
let urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('q')) {
this.params.q = urlParams.get('q');
}
if (urlParams.has('media')) {
this.params.media = urlParams.get('media');
}
if (urlParams.has('topic')) {
this.params.topic = urlParams.get('topic');
}
if (urlParams.has('type')) {
this.params.type = urlParams.get('type');
}
if (urlParams.has('sort')) {
this.params.sort = urlParams.get('sort');
}
if (urlParams.has('f')) {
this.params.f = urlParams.get('f');
}
if (urlParams.has('location')) {
this.params.location = urlParams.get('location');
}
if (urlParams.has('place')) {
this.params.place = urlParams.get('place');
}
this.search();
},
/**
* Updates the component when it is mounted.
*
* This function attaches an event listener to the modal element referenced by `this.$refs.modal`.
* When the modal is about to be closed, it finds all the iframe elements with the id `videoPlayer`
* in the document and pauses the Vimeo player associated with each iframe.
*
* @return {void} This function does not return anything.
*/
updated() {
jQuery(this.$refs.modal).on(jQuery.modal.BEFORE_CLOSE, function (event, modal) {
iframes = jQuery(document).find('iframe#videoPlayer');
iframes.each(function (video, item) {
var player = new Vimeo.Player(item);
player.pause()
});
});
},
/**
* Returns an object containing the initial data for the component.
*
* @return {Object} An object with the following properties:
*/
data() {
return {
window: window,
params: this.getInitialParams(),
person: window.person,
transcript: window.transcript,
transcript_counts: window.transcript_counts,
dateRange: {
startDate: null,
endDate: null,
},
modalOpened: null,
isVideoPlayerVisible: [],
loading: false,
results: [],
data: [],
meta: [],
selectedEventTypeIds: [],
selectedLoctionIds: [],
selectedMediaIds: [],
selectedPlaceIds: [],
transcriptMapping: {
'all': '',
'remarks': 'remarks',
'speeches': 'speech',
'press-conferences': 'press_conferences',
'vlogs': 'vlog',
'interviews': 'interview',
'op-ed': 'op_ed',
'op-eds': 'op_ed',
'videos': 'video'
}
}
},
computed: {
highlightedData() {
if (!this.searchQuery) {
return this.data.map(item => ({ ...item, highlightedText: item.text }));
}
const query = this.escapeRegExp(this.searchQuery);
const regex = new RegExp(`(${query})`, 'gi');
return this.data.map(item => {
const highlightedText = item.text.replace(regex, '$1');
return { ...item, highlightedText };
});
},
numberOfResults() {
if(this.meta.records_matched) {
return this.meta.records_matched + ' results';
}
},
mediaTypes() {
const mediaTypeMap = {};
// Step 1: Aggregate media types and count occurrences
this.data.forEach(item => {
const mediaType = item.media_type;
if (!mediaTypeMap[mediaType]) {
mediaTypeMap[mediaType] = {
id: mediaType,
name: mediaType,
count: 0
};
}
mediaTypeMap[mediaType].count++;
});
// Step 2: Convert map to array and sort by name
const mediaTypes = Object.values(mediaTypeMap)
.sort((a, b) => a.name.localeCompare(b.name));
return mediaTypes;
},
selectedMedia() {
return this.mediaTypes.filter(media => this.selectedMediaIds.includes(media.id));
},
eventTypes() {
const eventTypeMap = {};
// Step 1: Aggregate event types and count occurrences
this.data.forEach(item => {
const eventType = item.record_type;
if (!eventType) {
return;
}
if (!eventTypeMap[eventType]) {
eventTypeMap[eventType] = {
id: eventType,
name: eventType,
count: 0
};
}
eventTypeMap[eventType].count++;
});
// Step 2: Convert map to array and sort by name
const eventTypes = Object.values(eventTypeMap)
.sort((a, b) => a.name.localeCompare(b.name));
return eventTypes;
},
selectedEventTypes() {
return this.eventTypes.filter(eventType => this.selectedEventTypeIds.includes(eventType.id));
},
locationTypes() {
const locationMap = {};
// Step 1: Aggregate locations and count entries
this.data.forEach(item => {
const { city, state, state_code, country } = item.location;
const id = [city, state, state_code, country].filter(value => value !== null && value !== undefined).join(',');
if (!locationMap[id]) {
locationMap[id] = {
id: id,
name: [city, state_code, country].filter(value => value !== null && value !== undefined).join(', '),
count: 0
};
}
locationMap[id].count++;
});
// Step 2: Convert map to array and sort by name
const locationTypes = Object.values(locationMap)
.sort((a, b) => a.name.localeCompare(b.name));
return locationTypes;
},
selectedLoctions() {
return this.locationTypes.filter(location => this.selectedLoctionIds.includes(location.id));
},
places() {
const places = {};
this.data.forEach(item => {
const id = item.place
if (!places[id]) {
places[id] = {
id: id,
name: id,
count: 0
};
}
places[id].count++;
});
return Object.values(places).sort((a, b) => a.name.localeCompare(b.name));
},
selectedPlaces() {
return this.places.filter(place => this.selectedPlaceIds.includes(place.id));
}
},
/**
* Initializes the component when it is created.
*
* @param {type} paramName - description of parameter
* @return {type} description of return value
*/
created() {
this.debouncedWatch = window.lodash.debounce((newValue, oldValue) => {
this.search();
}, 500);
},
/**
* Cancels the debounced watch function before the component is unmounted.
*
* @return {void}
*/
beforeUnmount() {
this.debouncedWatch.cancel();
},
/**
* Initializes the component when it is mounted.
*
*/
mounted() {
this.getResults();
// const applyCallback = (start, end) => {
// this.params.start_date = start.format('YYYY-MM-DD');
// this.params.end_date = end.format('YYYY-MM-DD');
// };
// jQuery(this.$refs.dateInput).daterangepicker({
// startDate: moment('1946-06-14T10:54:00-0400'),
// endDate: moment(),
// autoApply: true,
// autoUpdateInput: true,
// ranges: {
// 'Today': [moment(), moment()],
// 'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
// 'Last 7 Days': [moment().subtract(6, 'days'), moment()],
// 'Last 30 Days': [moment().subtract(29, 'days'), moment()],
// 'This Month': [moment().startOf('month'), moment().endOf('month')],
// 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
// 'Year-To-Date': [moment().startOf('year'), moment()],
// 'Last Year': [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')],
// '2010s': [moment('2010-01-01'), moment()],
// '2000s': [moment('2000-01-01'), moment('2010-01-01')],
// '1990s': [moment('1990-01-01'), moment('2000-01-01')],
// '1980s': [moment('1980-01-01'), moment('1990-01-01')],
// 'All Time': [moment('1946-06-14T10:54:00-0400'), moment()]
// }
// }, applyCallback);
},
watch: {
// 'params.start_date': function (newValue, oldValue) {
// this.search();
// },
// 'params.end_date': function (newValue, oldValue) {
// this.search();
// },
'params.type': function (newValue, oldValue) {
this.setF();
this.search();
},
'params.media': function (newValue, oldValue) {
this.setF();
this.search();
},
'params.sort': function (newValue, oldValue) {
this.setF();
this.search();
},
selectedMediaIds(newVal, oldVal) {
this.setF();
this.params.media = newVal.join(',');
this.search();
},
'selectedEventTypeIds': function (newValue, oldValue) {
this.setF();
this.params.type = newValue.join(',');
this.search();
},
'selectedLoctionIds': function (newValue, oldValue) {
this.setF();
this.params.location = newValue.join(',');
this.search();
},
'selectedPlaceIds': function (newValue, oldValue) {
this.setF();
this.params.place = newValue.join(',');
this.search();
},
'params.q': {
handler(newData, old) {
this.debouncedWatch(...newData);
},
deep: true
}
},
methods: {
getInitialParams() {
return {
q: '',
f: '',
media: '',
type: '',
sort: 'desc',
spage: 0,
location: '',
place: '',
topic: ''
};
},
sentiment(item) {
var score;
var label;
if(item.document) {
score = item.document.sentiment?.score;
label = item.document.sentiment?.text;
} else {
score = item.sentiment.score;
label = item.sentiment.text;
}
if (!score) {
return `
`;
} else {
switch (true) {
case (score >= 0.1):
return `
${Math.floor(score * 100) / 100}
${label}
`;
case (score <= -0.1):
return `
${Math.floor(score * 100) / 100}
${label}
`;
case (score < 0.1 && score > -0.1):
return `
${Math.floor(score * 100) / 100}
${label}
`;
default:
return `
${Math.floor(score * 100) / 100}
${label}
${Math.floor(score * 100) / 100}
${label}
`;
}
}
},
resetParams() {
this.selectedMediaIds = [];
this.selectedLoctionIds = [];
this.selectedEventTypeIds = [];
this.selectedPlaceIds = [];
this.params = this.getInitialParams();
},
/**
* Sets the value of the 'f' parameter in the 'params' object based on the value of the 'transcript' property.
* If the 'transcript' property is truthy, the 'browse' property in the 'params' object is set to 1.
* The 'f' parameter is then set to the corresponding value in the 'transcriptMapping' object, or an empty string if no match is found.
* The function then generates a query string from the 'params' object and updates the URL with the new query string.
*
* @return {void} This function does not return a value.
*/
setF() {
// if (this.transcript) {
// this.params.browse = 1;
// }
this.params.f = this.transcriptMapping[this.transcript] || '';
const queryString = Object.entries(this.params)
.filter(([key, value]) => value !== null && value !== '')
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
history.replaceState(null, null, "?" + queryString);
},
/**
* Opens a modal with the specified record ID.
*
* @param {string} recordId - The ID of the record to open the modal for.
* @return {void} This function does not return a value.
*/
openModal(recordId) {
this.modalOpened = recordId;
},
/**
* Asynchronously retrieves results from the API based on the current parameters and person,
* and updates the data array with the results. If there are no more results, the scroll
* event listener is removed. If an error occurs, the results are set to an error message.
*
* @return {Promise} A promise that resolves when the function completes.
*/
async getResults() {
const scrollHandler = async () => {
this.setF();
let bottomOfWindow = (window.innerHeight + Math.round(window.scrollY)) >= document.body.offsetHeight
if (bottomOfWindow && !this.loading) {
this.loading = true
this.params.spage++
let url = 'https://api.factsquared.com/json/factba.se-' + this.person + '-20240623.php?' + this.objectToQueryString(this.params) + '&page='+page;
try {
const res = await fetch(url, {
method: "post",
})
this.results = (await res.json())
if(this.results.data.length === 0) {
this.loading = false
window.removeEventListener('scroll', scrollHandler);
return; // No results, so return from the function
}
this.meta = this.results.meta
this.data = this.results.data
this.data.forEach(item => {
this.data.push({
video_id: item.video_id || (item.video && item.video.video_id),
vimeo_start: item.vimeo_start || (item.video && item.video.vimeo_start),
...item
});
});
this.loading = false
} catch (error) {
this.loading = false
this.results = 'Error! Could not reach the API. ' + error
} finally {
this.loading = false
}
}
}
window.addEventListener('scroll', scrollHandler);
},
/**
* Toggles the visibility of the video player for the corresponding index.
*
* @param {Object} item - The item object.
* @param {number} index - The index of the item.
* @return {void} This function does not return anything.
*/
toggleVideo(item, index) {
this.isVideoPlayerVisible[index] = !this.isVideoPlayerVisible[index];
this.$forceUpdate();
},
/**
* Redirects the user to the specified URL.
*
* @param {string} url - The URL to redirect to.
* @return {void} This function does not return anything.
*/
redirect(url) {
window.location.href = url
},
/**
* Converts an object into a query string.
*
* @param {Object} params - The object to convert.
* @return {string} The query string representation of the object.
*/
objectToQueryString(params) {
const queryString = Object.entries(params)
.filter(([key, value]) => value !== '')
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
return queryString;
},
/**
* Asynchronously searches for data using the specified parameters and updates the component state with the results.
*
* @return {Promise} A Promise that resolves when the search is complete.
*/
async search() {
this.loading = true
// this.setF();
this.params.spage = 1
page = this.spage = 1
let url = 'https://api.factsquared.com/json/factba.se-' + this.person + '-20240623.php?' + this.objectToQueryString(this.params) + '&page='+page;
try {
const res = await fetch(url, {
method: "post",
})
this.results = (await res.json())
this.meta = this.results.meta
this.data = this.results.data
this.data.map((item, index) => ({
video_id: item.video_id || item.video.video_id,
vimeo_start: item?.video?.vimeo_start,
...item
}))
this.loading = false
} catch (error) {
this.loading = false
this.results = 'Error! Could not reach the API. ' + error
} finally {
this.loading = false
}
}
},
template: `
`
}).mount('#factbase-content');
}
if (document.getElementById('factbase-twitter')) {
Vue.createApp({
/**
* Initializes the component before it is mounted.
* Sets the filter property to the value of the global 'filter' variable.
* Calls the 'search' method.
*/
beforeMount() {
this.filter = window.filter;
this.search();
},
/**
* Returns an object containing the initial data for the component.
*
* @return {Object} An object with the following properties:
* - params: An object with the following properties:
* - q: A string representing the search query.
* - sort: A string representing the sort order.
* - spage: A number representing the current page.
* - loading: A boolean value representing the loading state.
* - results: An array representing the search results.
* - filter: A string representing the filter.
* - data: An array representing the component data.
* - meta: An array representing the meta data.
* - queryString: A string representing the query string.
* - filtermap: An object mapping filter names to their corresponding values.
*/
data() {
return {
window: window,
params: {
q: '',
sort: 'desc',
spage: 0,
},
loading: false,
results: [],
filter: '',
data: [],
meta: [],
queryString: '',
filtermap: {
'deleted-tweets': 'deleted',
'flagged-tweets': 'flagged',
}
}
},
/**
* Initializes the component when it is created.
*
* This function sets up a debounced watcher for the `search` method.
* The `debouncedWatch` function is debounced using the `window.lodash.debounce` function,
* which delays invoking the `search` method until after 500 milliseconds have elapsed
* since the last time the debounced function was invoked.
*
* @return {void}
*/
created() {
this.debouncedWatch = window.lodash.debounce((newValue, oldValue) => {
this.search();
}, 500);
},
/**
* Cancels the debounced watch function before the component is unmounted.
*
* @return {void}
*/
beforeUnmount() {
this.debouncedWatch.cancel();
},
/**
* Initializes the component when it is mounted.
*
*/
mounted() {
this.getResults();
},
watch: {
'params.sort': function (newValue, oldValue) {
this.search();
},
'params.q': {
handler(newData, old) {
this.debouncedWatch(...newData);
},
deep: true
}
},
methods: {
/**
* Sets the `modalOpened` property to the specified `recordId`.
*
* @param {string} recordId - The ID of the record to open the modal for.
* @return {void} This function does not return a value.
*/
openModal(recordId) {
this.modalOpened = recordId;
},
/**
* Initializes the scrollHandler function to handle scrolling events.
*
* @return {void} This function does not return a value.
*/
async getResults() {
const scrollHandler = async () => {
let bottomOfWindow = (window.innerHeight + Math.round(window.scrollY)) >= document.body.offsetHeight
if (bottomOfWindow && !this.loading) {
this.loading = true
this.params.spage++
let url = 'https://rcapi.factba.se/json/factba.se-trump-twitter.php?' + this.objectToQueryString(this.params) + '&' + this.filtermap[this.filter] + '=true&page='+page;
try {
const res = await fetch(url, {
method: "post",
})
this.results = (await res.json())
if(this.results.data.length === 0) {
window.removeEventListener('scroll', scrollHandler);
return; // No results, so return from the function
}
this.results.data.forEach(item => {
this.data.push({...item});
});
} catch (error) {
this.loading = false
this.results = 'Error! Could not reach the API. ' + error
} finally {
this.loading = false
}
}
}
window.addEventListener('scroll', scrollHandler);
},
/**
* Converts an object of parameters into a query string.
*
* @param {Object} params - The object containing the parameters.
* @return {string} The query string representation of the parameters.
*/
objectToQueryString(params) {
const queryString = Object.entries(params)
.filter(([key, value]) => value !== '')
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
return queryString;
},
/**
* Converts a date string into a formatted date string.
*
* @param {string} dateString - The input date string.
* @return {string} The formatted date string.
*/
formatDate(dateString) {
const date = new Date(dateString);
const options = {
year: '2-digit',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
timeZoneName: 'short'
};
const formattedDate = date.toLocaleString('en-US', options);
return formattedDate.replace(',', '').replace(/(?<=\d)(st|nd|rd|th)/, '$& ');
},
/**
* Asynchronously searches for data using the specified parameters and updates the component state with the results.
*
* @return {Promise} A Promise that resolves when the search is complete.
*/
async search() {
this.loading = true
this.params.spage = 1
let url = 'https://rcapi.factba.se/json/factba.se-trump-twitter.php?' + this.objectToQueryString(this.params) + '&' + this.filtermap[this.filter] + '=true&page='+page;;
try {
const res = await fetch(url, {
method: "post",
})
this.results = (await res.json())
this.meta = this.results.meta
this.data = this.results.data
this.data.map((item, index) => ({...item}));
this.loading = false
} catch (error) {
this.results = 'Error! Could not reach the API. ' + error
this.loading = false
} finally {
this.loading = false
}
}
},
template: `
`
}).mount('#factbase-twitter');
}
jQuery(document).ready(function ($) {
$('.toggle-whcd-player').on('click', function () {
$('.whcd-player-wrapper').toggleClass('hidden');
const vimeo_player = new Vimeo.Player(transcriptwrapper);
vimeo_player.pause();
});
const iframe = document.querySelector('.whcd-player-wrapper > iframe');
if (iframe) {
const vimeo_player = new Vimeo.Player(iframe);
$('.whcd-player').on('click', function () {
var video_seconds = $(this).attr('data-seconds');
vimeo_player.pause();
vimeo_player.setCurrentTime(video_seconds);
vimeo_player.play();
});
}
$('.toggle-transcript-player').on('click', function () {
$('.transcript-player-wrapper').toggleClass('hidden');
const vimeo_player = new Vimeo.Player(transcriptwrapper);
vimeo_player.pause();
});
const transcriptwrapper = document.querySelector('.transcript-player-wrapper > iframe');
if (transcriptwrapper) {
const vimeo_player = new Vimeo.Player(transcriptwrapper);
$('.transcript-play-video').on('click', function () {
if ($('.transcript-player-wrapper').hasClass('hidden')) {
$('.transcript-player-wrapper').removeClass('hidden');
}
var video_seconds = $(this).attr('data-seconds');
vimeo_player.pause();
vimeo_player.setCurrentTime(video_seconds);
vimeo_player.play();
});
}
if (window.latest_id) {
jQuery('#' + window.latest_id).modal({ fadeDuration: 100, });
}
window.initTooltip();
});
window.initTooltip = () => {
jQuery('.tooltip').tooltipster({
theme: 'tooltipster-borderless',
side: 'right',
functionInit: function (instance, helper) {
var $origin = jQuery(helper.origin),
dataOptions = $origin.attr('data-tooltipster');
if (dataOptions) {
dataOptions = JSON.parse(dataOptions);
jQuery.each(dataOptions, function (name, option) {
instance.option(name, option);
});
}
}
});
};