+ {Object.keys(this.props.list).map((key, i) => {
+
+ let thisClass: string = 'cite-row';
+ thisClass += i % 2 === 0 ? ' even' : '';
+ thisClass += this.props.citeArray.indexOf(i+1) !== -1 ? ' cite-selected' : '';
+
+ return(
+
${i+1}. ` +
+ (this.props.list[key] as HTMLLIElement).innerHTML}
+ }
+ onClick={this.props.clickHandler}
+ data-citenum={i+1} />
+ )
+ })}
+
+ )
+ }
+
+}
+
+
+
+ReactDOM.render(
+
,
+ document.getElementById('main-container')
+);
diff --git a/inc/js/components/PubmedWindow.tsx b/inc/js/components/PubmedWindow.tsx
new file mode 100644
index 00000000..75d86074
--- /dev/null
+++ b/inc/js/components/PubmedWindow.tsx
@@ -0,0 +1,209 @@
+import * as React from 'react'
+import * as ReactDOM from 'react-dom'
+import Modal from '../utils/Modal';
+import { PubmedQuery } from '../utils/PubmedAPI';
+
+declare var wm;
+
+
+class PubmedWindow extends React.Component
+<{}, {query: string, results: Object[], page: number}> {
+
+ private modal: Modal = new Modal('Search PubMed for Reference');
+ private wm: any = top.window.tinyMCE.activeEditor.windowManager.windows[top.window.tinyMCE.activeEditor.windowManager.windows.length - 1];
+
+ constructor() {
+ super();
+ this.state = {
+ query: '',
+ results: [],
+ page: 0,
+ };
+ }
+
+ componentDidMount() {
+ this.modal.resize();
+ }
+
+ _handleSubmit(e: Event) {
+ e.preventDefault();
+ PubmedQuery(this.state.query, (data: Object[]|Error) => {
+ if ((data as Error).name == 'Error') {
+ top.tinyMCE.activeEditor.windowManager.alert((data as Error).message);
+ return;
+ }
+ this.setState({
+ query: '',
+ results: (data as Object[]),
+ page: 1,
+ })
+ this.modal.resize();
+ });
+ }
+
+ _changeHandler(e: Event) {
+ this.setState({
+ query: (e.target as HTMLInputElement).value,
+ results: this.state.results,
+ page: this.state.page,
+ });
+ }
+
+ _handlePagination(e: Event) {
+ e.preventDefault();
+
+ let page: number = this.state.page;
+ page = (e.target as HTMLInputElement).id === 'next'
+ ? page + 1
+ : page - 1;
+
+ this.setState({
+ query: this.state.query,
+ results: this.state.results,
+ page
+ });
+ setTimeout(() => {
+ this.modal.resize();
+ }, 200);
+ }
+
+ _insertPMID(e: Event) {
+ this.wm.data['pmid'] = (e.target as HTMLInputElement).dataset['pmid'];
+ this.wm.submit();
+ }
+
+ render() {
+ return (
+
+
+ { this.state.results.length !== 0 &&
+
{
+ if ( i < (this.state.page * 5) && ((this.state.page * 5) - 6) < i ) {
+ return true;
+ }
+ })} />
+ }
+ { this.state.results.length !== 0 &&
+
+ }
+
+ )
+ }
+}
+
+const ResultList = ({
+ results,
+ onClick
+}) => {
+ return(
+
+ {results.map((result, i: number) =>
+
+
+
+
+ {result.authors.filter((el, i) => i < 3).map(el => el.name).join(', ')}
+
+
+ {result.source} | {result.pubdate}
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+const Paginate = ({
+ page,
+ onClick,
+ resultLength,
+}) => {
+ return (
+
+ )
+}
+
+
+function generatePlaceholder(): string {
+
+ let options = [
+ "Ioannidis JP[Author - First] AND meta research",
+ 'Brohi K[Author - First] AND "acute traumatic coagulopathy"',
+ "Dunning[Author] AND Kruger[Author] AND incompetence",
+ "parachute use AND death prevention AND BMJ[Journal]",
+ "obediance AND Milgram S[Author - First]",
+ "tranexamic acid AND trauma NOT arthroscopy AND Lancet[Journal]",
+ 'Watson JD[Author] AND Crick FH[Author] AND "nucleic acid"',
+ 'innovation OR ("machine learning" OR "deep learning") AND healthcare',
+ "injuries NOT orthopedic AND hemorrhage[MeSH]",
+ "resident OR student AND retention",
+ ];
+
+ return options[Math.ceil(Math.random() * 10) - 1];
+
+}
+
+
+ReactDOM.render(
+
,
+ document.getElementById('main-container')
+);
diff --git a/inc/js/components/ReferenceWindow.tsx b/inc/js/components/ReferenceWindow.tsx
new file mode 100644
index 00000000..39f978b1
--- /dev/null
+++ b/inc/js/components/ReferenceWindow.tsx
@@ -0,0 +1,670 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import Modal from '../utils/Modal';
+
+interface Author {
+ firstname: string
+ lastname: string
+ middleinitial: string
+}
+
+interface CommonMeta {
+ title: string
+ source: string
+ pubdate: string
+}
+
+interface JournalMeta extends CommonMeta {
+ volume: string
+ issue: string
+ pages: string
+}
+
+interface WebsiteMeta extends CommonMeta {
+ url: string
+ updated: string
+ accessed: string
+}
+
+interface BookMeta extends CommonMeta {
+ chapter: string
+ edition: string
+ location: string
+ pages: string
+}
+
+interface ManualMeta {
+ journal: JournalMeta,
+ website: WebsiteMeta,
+ book: BookMeta,
+}
+
+interface ManualPayload {
+ type: 'journal'|'website'|'book'
+ authors: Author[]
+ meta: ManualMeta
+}
+
+interface State {
+ pmidList: string
+ citationFormat: string
+ includeLink: boolean
+ attachInline: boolean
+ addManually: boolean
+ manualData: ManualPayload
+}
+
+
+class ReferenceWindow extends React.Component<{}, State> {
+
+ private modal: Modal = new Modal('Insert Formatted Reference');
+ private smartBibIsEnabled =
+ (top.tinyMCE.activeEditor.dom.doc as Document).getElementById('abt-smart-bib');
+
+ constructor() {
+ super();
+ this.state = {
+ pmidList: '',
+ citationFormat: top.tinyMCE.activeEditor.windowManager.windows[0].settings.params.preferredStyle || 'ama',
+ includeLink: false,
+ attachInline: false,
+ addManually: false,
+ manualData: {
+ type: 'journal',
+ authors: [
+ { firstname: '', lastname: '', middleinitial: '', },
+ ],
+ meta: {
+ journal: {
+ title: '',
+ source: '',
+ pubdate: '',
+ volume: '',
+ issue: '',
+ pages: '',
+ },
+ website: {
+ title: '',
+ source: '',
+ pubdate: '',
+ url: '',
+ updated: '',
+ accessed: '',
+ },
+ book: {
+ title: '',
+ source: '',
+ pubdate: '',
+ chapter: '',
+ edition: '',
+ location: '',
+ pages: '',
+ },
+ }
+ },
+ }
+ }
+
+ componentDidMount() {
+ this.modal.resize();
+ }
+
+ componentDidUpdate() {
+ this.modal.resize();
+ }
+
+ handleButtonClick(e: MouseEvent) {
+ let id = (e.target as HTMLInputElement).id;
+
+ switch(id) {
+ case 'searchPubmed':
+ let wm = top.tinyMCE.activeEditor.windowManager;
+ wm.open({
+ title: 'Search PubMed for Reference',
+ url: wm.windows[0].settings.params.baseUrl + 'pubmed-window.html',
+ width: 600,
+ height: 100,
+ onsubmit: (e: any) => {
+ let newList: string = e.target.data.pmid;
+
+ // If the current PMID List is not empty, add PMID to it
+ if (this.state.pmidList !== '') {
+ let combinedInput = this.state.pmidList.split(',');
+ combinedInput.push(e.target.data.pmid);
+ newList = combinedInput.join(',');
+ }
+
+ this.setState(Object.assign({}, this.state, { pmidList: newList }));
+ }}
+ );
+ break;
+ case 'addManually':
+ this.setState(Object.assign({}, this.state, { addManually: !this.state.addManually }));
+ break;
+ }
+
+ }
+
+ handleSubmit(e: Event) {
+ e.preventDefault();
+ let wm = top.tinyMCE.activeEditor.windowManager;
+ wm.setParams({ data: this.state });
+ wm.close();
+ }
+
+ consumeChange(e: Event) {
+
+ // Switch on the type of input element and create a new, non-mutated
+ // state object to apply the result of the state change.
+ let id: string = (e.target as HTMLElement).id;
+ let tagName: string = (e.target as HTMLElement).tagName;
+ let newState = {};
+
+ switch (tagName) {
+ case 'INPUT':
+ let type: string = (e.target as HTMLInputElement).type;
+
+ switch(type) {
+ case 'text':
+ newState[id] = (e.target as HTMLInputElement).value;
+ break;
+ case 'checkbox':
+ newState[id] = (e.target as HTMLInputElement).checked;
+ break;
+ }
+ break;
+ case 'SELECT':
+ newState[id] = (e.target as HTMLSelectElement).value;
+ break;
+ }
+
+ this.setState(Object.assign({}, this.state, newState));
+ }
+
+ consumeManualDataChange(e: Event) {
+
+ let type: string = e.type;
+ let newData = Object.assign({}, this.state.manualData);
+
+ switch(type) {
+ case 'AUTHOR_DATA_CHANGE':
+ newData.authors = (e as CustomEvent).detail;
+ break;
+ case 'ADD_AUTHOR':
+ newData.authors = [...newData.authors, { firstname: '', lastname: '', middleinitial: '', }];
+ break
+ case 'REMOVE_AUTHOR':
+ let removeNum: number = parseInt((e as CustomEvent).detail);
+ newData.authors = [
+ ...newData.authors.slice(0, removeNum),
+ ...newData.authors.slice(removeNum + 1),
+ ];
+ break;
+ case 'TYPE_CHANGE':
+ newData.type = (e as CustomEvent).detail;
+ break;
+ case 'META_CHANGE':
+ newData.meta = (e as CustomEvent).detail;
+ break;
+ }
+
+ this.setState(Object.assign({}, this.state, { manualData: newData }));
+
+ }
+
+ render() {
+ return(
+
+ );
+ }
+
+}
+
+
+class PMIDInput extends React.Component<{pmidList: string, onChange: Function},{}> {
+
+ refs: {
+ [key: string]: Element
+ pmidInput: HTMLInputElement
+ }
+
+ componentDidMount() {
+ (ReactDOM.findDOMNode(this.refs.pmidInput) as HTMLInputElement).focus()
+ }
+
+ render() {
+ let sharedStyle = {
+ padding: '5px',
+ }
+ return(
+
+
+ PMID
+
+
+
+
+ Include Link?
+
+
+
+
+
+
+ )
+ }
+
+}
+
+
+const RefOptions = ({
+ attachInline,
+ citationFormat,
+ onChange,
+ smartBibIsEnabled,
+}) => {
+ let commonStyle = { padding: '5px' }
+ return(
+
+
+
+ Format
+
+
+
+ American Medical Association (AMA)
+ American Psychological Association (APA)
+
+
+
+ { smartBibIsEnabled &&
+
+
+ Also add inline citation at current cursor position?
+
+
+
+
+
+ }
+
+ );
+}
+
+
+const ActionButtons = ({
+ addManually,
+ onClick
+}) => {
+
+ const rowStyle = {
+ textAlign: 'center',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ };
+
+ const spanStyle = {
+ borderRight: 'solid 2px #ccc',
+ height: 25,
+ margin: '0 15px 0 10px',
+ };
+
+ const buttonStyle = { margin: '0 5px' };
+
+ const submitStyle = {
+ flexGrow: 1,
+ margin: '0 15px 0 0'
+ };
+
+ return(
+
+
+
+
+
+
+
+ )
+}
+
+
+
+
+class ManualEntryContainer extends React.Component<{
+ manualData: ManualPayload,
+ onChange
+},{}> {
+
+ constructor(props) {
+ super(props);
+ }
+
+
+ typeChange(e) {
+ let event = new CustomEvent('TYPE_CHANGE', { detail: e.target.value });
+ this.props.onChange(event);
+ }
+
+ authorChange(e) {
+ let type = e.target.dataset['nametype'];
+ let authNumber: number = parseInt(e.target.dataset['authornum']);
+ let newAuthorList = [...this.props.manualData.authors];
+ newAuthorList[authNumber][type] = e.target.value;
+ let event = new CustomEvent('AUTHOR_DATA_CHANGE', {detail: newAuthorList})
+ this.props.onChange(event);
+
+ }
+
+ addAuthor(e) {
+ let event = new CustomEvent('ADD_AUTHOR');
+ this.props.onChange(event);
+ }
+
+ removeAuthor(e) {
+ let authornum = e.target.dataset['authornum'];
+ let event = new CustomEvent('REMOVE_AUTHOR', { detail: authornum });
+ this.props.onChange(event);
+ }
+
+ handleMetaChange(e) {
+ let newMeta = Object.assign({}, this.props.manualData.meta);
+ newMeta[this.props.manualData.type][e.target.dataset['metakey']] = e.target.value;
+ let event = new CustomEvent('META_CHANGE', { detail: newMeta });
+ this.props.onChange(event);
+ }
+
+ render() {
+ return(
+
+ )
+ }
+
+}
+
+const ManualSelection = ({
+ value,
+ onChange,
+}) => {
+ const commonStyle = { padding: '5px' };
+ return(
+
+
+
+ Select Citation Type
+
+
+
+
+ Journal Article
+ Website
+ Book
+
+
+
+ )
+}
+
+const Authors = ({
+ authorList,
+ removeAuthor,
+ onChange,
+ addAuthor,
+ type,
+}) => {
+ const inputStyle = {
+ flex: 1,
+ padding: '0 5px',
+ };
+ const commonStyle = {
+ padding: '0 5px'
+ }
+ return(
+
+
+ Author Name(s)
+
+ {authorList.map((author: Author, i: number) =>
+
+
+
+ First
+
+
+
+
+
+
+
+ M.I.
+
+
+
+
+
+
+
+ Last
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ )
+}
+
+
+const MetaFields = ({type, meta, onChange,} :
+{
+ type: string
+ meta: ManualMeta
+ onChange: Function
+}) => {
+
+ const outerFlex = {
+ display: 'flex',
+ flexDirection: 'column',
+ }
+
+ const innerFlex = {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ }
+
+ let displayMeta: Object[] = [];
+ let title: string = type[0].toUpperCase() + type.substring(1, type.length);
+
+ switch(type) {
+ case 'journal':
+ displayMeta = [
+ { meta: meta.journal.title, key: 'title', label: 'Title', required: true },
+ { meta: meta.journal.source, key: 'source', label: 'Journal Name', required: true },
+ { meta: meta.journal.pubdate, key: 'pubdate', label: 'Year Published', pattern: '[1-2][0-9]{3}', required: true },
+ { meta: meta.journal.volume, key: 'volume', label: 'Volume', pattern: '[0-9]{1,5}', required: false },
+ { meta: meta.journal.issue, key: 'issue', label: 'Issue', pattern: '^[0-9]{1,4}', required: false },
+ { meta: meta.journal.pages, key: 'pages', label: 'Pages', pattern: '^([0-9]{1,4}(?:-[0-9]{1,4}$)?)', required: true }
+ ];
+ break;
+ case 'website':
+ displayMeta = [
+ { meta: meta.website.title, key: 'title', label: 'Content Title', required: true },
+ { meta: meta.website.source, key: 'source', label: 'Website Title', required: true },
+ { meta: meta.website.pubdate, key: 'pubdate', label: 'Published Date', placeholder: 'MM/DD/YYYY', pattern: '[0-1][0-9][-/][0-3][0-9][-/][1-2][0-9]{3}', required: true },
+ { meta: meta.website.url, key: 'url', label: 'URL', required: true },
+ { meta: meta.website.updated, key: 'updated', label: 'Updated Date', placeholder: 'MM/DD/YYYY', pattern: '[0-1][0-9][-/][0-3][0-9][-/][1-2][0-9]{3}', required: false },
+ { meta: meta.website.accessed, key: 'accessed', label: 'Accessed Date', placeholder: 'MM/DD/YYYY', pattern: '[0-1][0-9][-/][0-3][0-9][-/][1-2][0-9]{3}', required: false },
+ ];
+ break;
+ case 'book':
+ displayMeta = [
+ { meta: meta.book.title, key: 'title', label: 'Book Title', required: true },
+ { meta: meta.book.source, key: 'source', label: 'Publisher', required: true },
+ { meta: meta.book.pubdate, key: 'pubdate', label: 'Copyright Year', pattern: '[1-2][0-9]{3}', required: true },
+ { meta: meta.book.chapter, key: 'chapter', label: 'Chapter/Section', required: false },
+ { meta: meta.book.edition, key: 'edition', label: 'Edition', required: false },
+ { meta: meta.book.location, key: 'location', label: 'Publisher Location', required: false },
+ { meta: meta.book.pages, key: 'pages', label: 'Pages', pattern: '^([0-9]{1,4}(?:-[0-9]{1,4}$)?)', required: false },
+ ];
+ break;
+ }
+
+ return(
+
+
+ {title} Information
+
+
+ {displayMeta.map((item, i: number) =>
+
+
+
+ {item.label}
+
+
+
+
+
+
+ )}
+
+
+ )
+}
+
+
+
+ReactDOM.render(
+
,
+ document.getElementById('main-container')
+)
diff --git a/inc/js/peer-review.ts b/inc/js/frontend.ts
similarity index 67%
rename from inc/js/peer-review.ts
rename to inc/js/frontend.ts
index b4eb6db0..8a8c71ca 100644
--- a/inc/js/peer-review.ts
+++ b/inc/js/frontend.ts
@@ -2,28 +2,29 @@
declare var DocumentTouch;
-module ABT_Frontend {
+namespace ABT_Frontend {
export class Accordion {
- private _headings: NodeList;
+ private _headings: NodeListOf
;
constructor() {
- this._headings = document.querySelectorAll('#abt_PR_boxes > h3');
+ this._headings = (document.getElementsByClassName('abt_PR_heading') as NodeListOf);
- for (let heading of (this._headings)) {
- let reviewContent: HTMLElement = heading.nextSibling;
- reviewContent.style.display = 'none';
+ for (let i = 0; i < this._headings.length; i++) {
+ let currentHeading = this._headings[i];
+ let reviewContent = (currentHeading.nextElementSibling as HTMLDivElement);
- heading.addEventListener('click', this._clickHandler);
+ reviewContent.style.display = 'none';
+ currentHeading.addEventListener('click', this._clickHandler);
}
}
private _clickHandler(e: Event): void {
- let targetContent = e.srcElement.nextSibling;
+ let targetContent = (e.srcElement.nextSibling as HTMLDivElement);
// If targetContent already visible, hide it and exit
if (targetContent.style.display != 'none') {
@@ -31,16 +32,20 @@ module ABT_Frontend {
return;
}
- let accordionNodes = e.srcElement.parentElement.childNodes;
- let element: HTMLElement;
+ let accordionChildren = e.srcElement.parentElement.children;
+
+ for (let i = 0; i < accordionChildren.length; i++) {
+
+ let currentElement = accordionChildren[i] as HTMLElement;
- for (element of accordionNodes) {
- if (element.tagName != 'DIV') { continue; }
- if (element.previousSibling === e.srcElement) {
- element.style.display = '';
+ if (currentElement.tagName != 'DIV') { continue; }
+
+ if (currentElement.previousSibling === e.srcElement) {
+ currentElement.style.display = '';
continue;
}
- element.style.display = 'none';
+
+ currentElement.style.display = 'none';
}
}
@@ -53,13 +58,12 @@ module ABT_Frontend {
public static timer: number;
constructor() {
- let referenceList: HTMLOListElement = this._getReferenceList();
- let citationList: NodeListOf = document.querySelectorAll('span.cite');
- let citation: HTMLSpanElement;
+ let referenceList = (document.getElementById('abt-smart-bib') as HTMLOListElement);
+ let citationList = document.getElementsByClassName('abt_cite')
- for (citation of citationList) {
+ for (let i = 0; i < citationList.length; i++) {
- let citeNums: number[] = JSON.parse(citation.dataset['reflist']);
+ let citeNums: number[] = JSON.parse((citationList[i] as HTMLSpanElement).dataset['reflist']);
let citationHTML = citeNums.map((citeNum: number): string => {
// Correct for zero-based index
citeNum--;
@@ -78,14 +82,14 @@ module ABT_Frontend {
});
// Save CSV string of citenums as data attr 'citations'
- citation.dataset['citations'] = citationHTML.join('');
+ (citationList[i] as HTMLSpanElement).dataset['citations'] = citationHTML.join('');
// Conditionally create tooltip based on device
if (this._isTouchDevice()) {
- citation.addEventListener('touchstart', this._createTooltip.bind(this));
+ citationList[i].addEventListener('touchstart', this._createTooltip.bind(this));
} else {
- citation.addEventListener('mouseover', this._createTooltip.bind(this));
- citation.addEventListener('mouseout', this._destroyTooltip);
+ citationList[i].addEventListener('mouseover', this._createTooltip.bind(this));
+ citationList[i].addEventListener('mouseout', this._destroyTooltip);
}
}
@@ -93,23 +97,13 @@ module ABT_Frontend {
}
- private _getReferenceList(): HTMLOListElement {
- let orderedLists: NodeListOf = document.getElementsByTagName('ol');
- for (let i = (orderedLists.length - 1); i >= 0; i--){
- if (orderedLists[i].parentElement.className !== 'abt_chat_bubble') {
- return orderedLists[i];
- }
- }
- }
-
-
private _isTouchDevice(): boolean {
return true == ("ontouchstart" in window || (window).DocumentTouch && document instanceof DocumentTouch);
}
private _createTooltip(e: Event): void {
-
+ e.preventDefault();
clearTimeout(Citations.timer);
let preExistingTooltip: HTMLElement = document.getElementById('abt_tooltip');
@@ -132,6 +126,7 @@ module ABT_Frontend {
let closeButton: HTMLDivElement = document.createElement('div');
let touchContainer: HTMLDivElement = document.createElement('div');
+
touchContainer.className = 'abt_tooltip_touch_close-container';
closeButton.className = 'abt_tooltip_touch_close';
touchContainer.addEventListener('touchend', () => tooltip.remove());
@@ -139,9 +134,9 @@ module ABT_Frontend {
tooltip.style.left = '0';
tooltip.style.right = '0';
tooltip.style.maxWidth = '90%'
+
touchContainer.appendChild(closeButton);
tooltip.appendChild(touchContainer);
- // tooltip.appendChild(closeButton);
document.body.appendChild(tooltip);
tooltip.appendChild(tooltipArrow);
@@ -162,13 +157,14 @@ module ABT_Frontend {
// Set tooltip above or below based on window position + set arrow position
if ((rect.top - tooltip.offsetHeight) < 0) {
+ // On bottom - Upwards arrow
tooltip.style.top = (rect.bottom + window.scrollY + 5) + 'px';
- tooltipArrow.style.top = '-15px';
- tooltipArrow.style.borderColor = 'transparent transparent #fff';
+ tooltip.style.animation = 'fadeInUp .2s';
+ tooltipArrow.classList.add('abt_arrow_up');
} else {
+ // On top - Downwards arrow
tooltip.style.top = (rect.top + window.scrollY - tooltip.offsetHeight - 5) + 'px';
- tooltipArrow.style.bottom = '-15px';
- tooltipArrow.style.borderColor = '#fff transparent transparent';
+ tooltipArrow.classList.add('abt_arrow_down');
}
tooltip.style.visibility = '';
@@ -196,7 +192,7 @@ if (document.readyState != 'loading'){
function frontendJS() {
- new ABT_Frontend.Citations();
- new ABT_Frontend.Accordion();
+ new ABT_Frontend.Citations;
+ new ABT_Frontend.Accordion;
}
diff --git a/inc/js/meta-box-image.js b/inc/js/meta-box-image.js
deleted file mode 100644
index b0d00d80..00000000
--- a/inc/js/meta-box-image.js
+++ /dev/null
@@ -1,146 +0,0 @@
-
-if (document.readyState != 'loading'){
- editorJS();
-} else {
- document.addEventListener('DOMContentLoaded', editorJS);
-}
-
-function editorJS() {
-
- var authorNameInputs = document.querySelectorAll('input[id^=author_name_]');
- var authorResponseTables = document.querySelectorAll('table[id^=author_response]');
- var reviewContent = document.querySelectorAll('textarea[id^=peer_review_content_]');
- var responseContent = document.querySelectorAll('textarea[id^=author_content_]');
-
- // Inital actions on load
- for (var i = 0; i < 3; i++) {
-
- // Hide empty author responses
- if (authorNameInputs[i].value == '') {
- authorResponseTables[i].style.display = 'none';
- }
-
- // Replace and tags with actual line breaks on post edit screen
- reviewContent[i].value = reviewContent[i].value.replace(/( )|( )|(
)|(<\/p>)/, "").replace(/( )|( )|(
)|(<\/p>)/g, "\r");
- responseContent[i].value = responseContent[i].value.replace(/( )|( )|(
)|(<\/p>)/, "").replace(/( )|( )|(
)|(<\/p>)/g, "\r");
-
- }
-
-
- // ==================================================
- // SELECT BOX HANDLER
- // ==================================================
-
- // Show/hide nodes based on select option
- var selectBox = document.querySelector('#reviewer_selector');
- var selectDivs = document.querySelectorAll('#peer_review_metabox_wrapper > div');
- selectBox.addEventListener('change', selectHandler);
-
- // Simulate a change event on initial page load
- var simulatedChange = new Event('change');
- selectBox.dispatchEvent(simulatedChange);
-
- function selectHandler(e) {
- for (var i = 0; i < selectDivs.length; i++) {
- selectDivs[i].style.display = 'none';
- }
-
- switch (e.target.value) {
- case '3':
- selectDivs['2'].style.display = '';
- case '2':
- selectDivs['1'].style.display = '';
- case '1':
- selectDivs['0'].style.display = '';
- }
-
- };
-
-
- // ==================================================
- // AUTHOR RESPONSE BUTTON HANDLER
- // ==================================================
-
- // Show hide author response based on button toggle
- var toggleButtons = document.querySelectorAll('input[id^=author_response_button]');
- for (var i = 0; i < toggleButtons.length; i++) {
-
- toggleButtons[i].addEventListener('click', function(e) {
-
- currentIndex = parseInt(e.target.id.slice(-1)) - 1;
-
- if (authorResponseTables[currentIndex].style.display == 'none') {
- authorResponseTables[currentIndex].style.display = 'block';
- return;
- }
-
- authorResponseTables[currentIndex].style.display = 'none';
-
- });
- }
-
- //=====================================================
- // MEDIA UPLOAD HANDLER
- //=====================================================
-
- // Instantiates the variable that holds the media library frame.
- var abt_meta_image_frames = [null, null, null, null, null, null];
- wp.media.frames.abt_meta_image_frames = {
- '1': null,
- '2': null,
- '3': null,
- '4': null,
- '5': null,
- '6': null
- };
-
- var headshotButtons = document.querySelectorAll('input[id^=headshot_image_button]');
- for (var i = 0; i < headshotButtons.length; i++) {
-
- headshotButtons[i].addEventListener('click', function(e) {
-
- var headshotImageInput;
- var i = parseInt(e.target.id.slice(-1)) - 1;
- switch (i) {
- case 0:
- case 1:
- case 2:
- headshotImageInput = document.querySelector('#reviewer_headshot_image_' + (i+1).toString());
- break;
- case 3:
- case 4:
- case 5:
- headshotImageInput = document.querySelector('#author_headshot_image_' + (i-2).toString());
- break;
- }
-
- if (abt_meta_image_frames[i]) {
- abt_meta_image_frames[i].open();
- return;
- }
-
- abt_meta_image_frames[i] = wp.media.frames.abt_meta_image_frames[i] = wp.media({
- title: meta_image.title,
- button: { text: meta_image.button },
- library: { type: 'image' }
- });
-
- // Runs when an image is selected.
- abt_meta_image_frames[i].on('select', function(){
-
- // Grabs the attachment selection and creates a JSON representation of the model.
- var media_attachment = abt_meta_image_frames[i].state().get('selection').first().toJSON();
-
- // Sends the attachment URL to our custom image input field.
- headshotImageInput.value = media_attachment.url;
-
- });
-
- // Opens the media library frame.
- abt_meta_image_frames[i].open();
-
- });
-
- }
-
-}
diff --git a/inc/js/metaboxes.ts b/inc/js/metaboxes.ts
new file mode 100644
index 00000000..f1d15edc
--- /dev/null
+++ b/inc/js/metaboxes.ts
@@ -0,0 +1,142 @@
+declare var wp, ABT_locationInfo;
+
+class PeerReviewMetabox {
+
+ public inputs = {
+ authorNames: [] as HTMLInputElement[],
+ toggleAuthorResponse: [] as HTMLInputElement[],
+ imageButtons: [] as HTMLInputElement[],
+ }
+
+ public containers = {
+ reviews: [] as HTMLDivElement[],
+ }
+
+ public tables = {
+ authorResponses: [] as HTMLTableElement[],
+ }
+
+ public textareas = {
+ reviewContent: [] as HTMLTextAreaElement[],
+ responseContent: [] as HTMLTextAreaElement[],
+ }
+
+ constructor() {
+ this._initFields();
+ wp.media.frames.abt_reviewer_photos = [null, null, null, null, null, null];
+
+ this._selectHandler();
+ document.getElementById('reviewer_selector').addEventListener('change', this._selectHandler.bind(this));
+ }
+
+
+ private _initFields(): void {
+
+ for (let i = 0; i < 3; i++) {
+ this.inputs.authorNames[i] = document.getElementById(`author_name_${i + 1}`) as HTMLInputElement;
+ this.inputs.toggleAuthorResponse[i] = document.getElementById(`author_response_button_${i + 1}`) as HTMLInputElement;
+ this.inputs.imageButtons[i] = document.getElementById(`headshot_image_button_${i + 1}`) as HTMLInputElement;
+ this.inputs.imageButtons[i+3] = document.getElementById(`headshot_image_button_${i+3 + 1}`) as HTMLInputElement;
+ this.tables.authorResponses[i] = document.getElementById(`author_response_${i + 1}`) as HTMLTableElement;
+ this.textareas.reviewContent[i] = document.getElementById(`peer_review_content_${i + 1}`) as HTMLTextAreaElement;
+ this.textareas.responseContent[i] = document.getElementById(`author_content_${i + 1}`) as HTMLTextAreaElement;
+ this.containers.reviews[i] = document.getElementById(`tabs-${i + 1}`) as HTMLDivElement
+
+ // Hide empty author response tables
+ if (this.inputs.authorNames[i].value === '') {
+ this.tables.authorResponses[i].style.display = 'none';
+ }
+
+ // Reformat textareas to remove raw HTML
+ this.textareas.reviewContent[i].value = this.textareas.reviewContent[i].value
+ .replace(/( )|( )|(
)|(<\/p>)/, "")
+ .replace(/( )|( )|(
)|(<\/p>)/g, "\r");
+
+ this.textareas.responseContent[i].value = this.textareas.responseContent[i].value
+ .replace(/( )|( )|(
)|(<\/p>)/, "")
+ .replace(/( )|( )|(
)|(<\/p>)/g, "\r");
+
+ this.inputs.toggleAuthorResponse[i].addEventListener('click', this._toggleAuthorResponse.bind(this));
+ this.inputs.imageButtons[i].addEventListener('click', this._mediaUploadHandler.bind(this))
+ this.inputs.imageButtons[i+3].addEventListener('click', this._mediaUploadHandler.bind(this))
+
+ }
+ }
+
+ private _selectHandler(e?: Event): void {
+
+ for (let el of this.containers.reviews ) {
+ el.style.display = 'none';
+ }
+
+ let selectBox = document.getElementById('reviewer_selector') as HTMLSelectElement
+ switch (selectBox.value) {
+ case '3':
+ this.containers.reviews[2].style.display = '';
+ case '2':
+ this.containers.reviews[1].style.display = '';
+ case '1':
+ this.containers.reviews[0].style.display = '';
+ }
+ }
+
+ private _toggleAuthorResponse(e: Event): void {
+ let i = parseInt(e.srcElement.id.slice(-1)) - 1;
+ let response = this.tables.authorResponses[i];
+ response.style.display = response.style.display === 'none' ? '' : 'none';
+ }
+
+ private _mediaUploadHandler(e: Event): void {
+
+ let headshotImageInput: HTMLInputElement;
+ let i: number = parseInt(e.srcElement.id.slice(-1)) - 1;
+ switch (i) {
+ case 0:
+ case 1:
+ case 2:
+ headshotImageInput = document.getElementById(`reviewer_headshot_image_${i+1}`) as HTMLInputElement;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ headshotImageInput = document.getElementById(`author_headshot_image_${i-2}`) as HTMLInputElement;
+ break;
+ }
+
+ if (wp.media.frames.abt_reviewer_photos[i]) {
+ wp.media.frames.abt_reviewer_photos[i].open();
+ return;
+ }
+
+ wp.media.frames.abt_reviewer_photos[i] = wp.media({
+ title: 'Choose or Upload an Image',
+ button: { text: 'Use this image' },
+ library: { type: 'image' }
+ });
+
+ // Runs when an image is selected.
+ wp.media.frames.abt_reviewer_photos[i].on('select', function(){
+
+ // Grabs the attachment selection and creates a JSON representation of the model.
+ let media_attachment = wp.media.frames.abt_reviewer_photos[i].state().get('selection').first().toJSON();
+
+ // Sends the attachment URL to our custom image input field.
+ headshotImageInput.value = media_attachment.url;
+
+ });
+
+ // Opens the media library frame.
+ wp.media.frames.abt_reviewer_photos[i].open();
+
+
+ }
+
+}
+
+if (document.readyState != 'loading'){
+ if (ABT_locationInfo.postType !== 'page') { new PeerReviewMetabox; }
+} else {
+ document.addEventListener('DOMContentLoaded', () => {
+ if (ABT_locationInfo.postType !== 'page') { new PeerReviewMetabox }
+ });
+}
diff --git a/inc/js/peer-review.js b/inc/js/peer-review.js
deleted file mode 100644
index e0a8b478..00000000
--- a/inc/js/peer-review.js
+++ /dev/null
@@ -1,145 +0,0 @@
-var ABT_Frontend;
-(function (ABT_Frontend) {
- var Accordion = (function () {
- function Accordion() {
- this._headings = document.querySelectorAll('#abt_PR_boxes > h3');
- for (var _i = 0, _a = this._headings; _i < _a.length; _i++) {
- var heading = _a[_i];
- var reviewContent = heading.nextSibling;
- reviewContent.style.display = 'none';
- heading.addEventListener('click', this._clickHandler);
- }
- }
- Accordion.prototype._clickHandler = function (e) {
- var targetContent = e.srcElement.nextSibling;
- if (targetContent.style.display != 'none') {
- targetContent.style.display = 'none';
- return;
- }
- var accordionNodes = e.srcElement.parentElement.childNodes;
- var element;
- for (var _i = 0, _a = accordionNodes; _i < _a.length; _i++) {
- element = _a[_i];
- if (element.tagName != 'DIV') {
- continue;
- }
- if (element.previousSibling === e.srcElement) {
- element.style.display = '';
- continue;
- }
- element.style.display = 'none';
- }
- };
- return Accordion;
- }());
- ABT_Frontend.Accordion = Accordion;
- var Citations = (function () {
- function Citations() {
- var referenceList = this._getReferenceList();
- var citationList = document.querySelectorAll('span.cite');
- var citation;
- for (var _i = 0, _a = citationList; _i < _a.length; _i++) {
- citation = _a[_i];
- var citeNums = JSON.parse(citation.dataset['reflist']);
- var citationHTML = citeNums.map(function (citeNum) {
- citeNum--;
- if (!referenceList.children[citeNum]) {
- return;
- }
- return ("
" +
- "" +
- ("" + (citeNum + 1) + ". ") +
- ("" + referenceList.children[citeNum].innerHTML) +
- " " +
- "
");
- });
- citation.dataset['citations'] = citationHTML.join('');
- if (this._isTouchDevice()) {
- citation.addEventListener('touchstart', this._createTooltip.bind(this));
- }
- else {
- citation.addEventListener('mouseover', this._createTooltip.bind(this));
- citation.addEventListener('mouseout', this._destroyTooltip);
- }
- }
- }
- Citations.prototype._getReferenceList = function () {
- var orderedLists = document.getElementsByTagName('ol');
- for (var i = (orderedLists.length - 1); i >= 0; i--) {
- if (orderedLists[i].parentElement.className !== 'abt_chat_bubble') {
- return orderedLists[i];
- }
- }
- };
- Citations.prototype._isTouchDevice = function () {
- return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
- };
- Citations.prototype._createTooltip = function (e) {
- clearTimeout(Citations.timer);
- var preExistingTooltip = document.getElementById('abt_tooltip');
- if (preExistingTooltip !== null) {
- preExistingTooltip.remove();
- }
- var rect = e.srcElement.getBoundingClientRect();
- var tooltip = document.createElement('div');
- tooltip.className = tooltip.id = 'abt_tooltip';
- tooltip.innerHTML = e.srcElement.getAttribute('data-citations');
- tooltip.style.visibility = 'hidden';
- var tooltipArrow = document.createElement('div');
- tooltipArrow.className = 'abt_tooltip_arrow';
- if (this._isTouchDevice()) {
- var closeButton = document.createElement('div');
- var touchContainer = document.createElement('div');
- touchContainer.className = 'abt_tooltip_touch_close-container';
- closeButton.className = 'abt_tooltip_touch_close';
- touchContainer.addEventListener('touchend', function () { return tooltip.remove(); });
- tooltip.style.left = '0';
- tooltip.style.right = '0';
- tooltip.style.maxWidth = '90%';
- touchContainer.appendChild(closeButton);
- tooltip.appendChild(touchContainer);
- document.body.appendChild(tooltip);
- tooltip.appendChild(tooltipArrow);
- tooltipArrow.style.left = "calc(" + rect.left + "px - 5% + " + ((rect.right - rect.left) / 2) + "px - 3px)";
- }
- else {
- tooltipArrow.style.left = '50%';
- tooltip.appendChild(tooltipArrow);
- document.body.appendChild(tooltip);
- tooltip.style.marginRight = '10px';
- tooltip.style.maxWidth = (500 > ((rect.left * 2) + ((rect.right - rect.left) / 2))) ? (rect.left * 2) + 'px' : '500px';
- tooltip.style.left = rect.left + ((rect.right - rect.left) / 2) - (tooltip.clientWidth / 2) + 'px';
- tooltip.addEventListener('mouseover', function () { return clearTimeout(Citations.timer); });
- tooltip.addEventListener('mouseout', this._destroyTooltip);
- }
- if ((rect.top - tooltip.offsetHeight) < 0) {
- tooltip.style.top = (rect.bottom + window.scrollY + 5) + 'px';
- tooltipArrow.style.top = '-15px';
- tooltipArrow.style.borderColor = 'transparent transparent #fff';
- }
- else {
- tooltip.style.top = (rect.top + window.scrollY - tooltip.offsetHeight - 5) + 'px';
- tooltipArrow.style.bottom = '-15px';
- tooltipArrow.style.borderColor = '#fff transparent transparent';
- }
- tooltip.style.visibility = '';
- };
- Citations.prototype._destroyTooltip = function () {
- Citations.timer = setTimeout(function () {
- document.getElementById('abt_tooltip').remove();
- }, 200);
- };
- return Citations;
- }());
- ABT_Frontend.Citations = Citations;
-})(ABT_Frontend || (ABT_Frontend = {}));
-if (document.readyState != 'loading') {
- frontendJS();
-}
-else {
- document.addEventListener('DOMContentLoaded', frontendJS);
-}
-function frontendJS() {
- new ABT_Frontend.Citations();
- new ABT_Frontend.Accordion();
-}
diff --git a/inc/js/tinymce-buttons.js b/inc/js/tinymce-buttons.js
deleted file mode 100644
index 331b85c8..00000000
--- a/inc/js/tinymce-buttons.js
+++ /dev/null
@@ -1,456 +0,0 @@
-(function() {
-
- tinymce.PluginManager.add('abt_ref_id_parser_mce_button', function(editor, url) {
- editor.addButton('abt_ref_id_parser_mce_button', {
- type: 'menubutton',
- image: url + '/../images/book.png',
- title: "Academic Blogger's Toolkit",
- icon: true,
- menu: [{
- text: 'Bibliography Tools',
- menu: [
- // Inline Citation Menu Item
- {
- text: 'Inline Citation',
- onclick: function() {
- editor.windowManager.open({
- title: 'Insert Citation',
- width: 600,
- height: 58,
- body: [{
- type: 'textbox',
- name: 'citation_number',
- label: 'Citation Number',
- value: ''
- }, {
- type: 'container',
- html: 'Protip: Use the keyboard shortcut to access this menu PC/Linux: (Ctrl+Alt+C) | Mac: (Cmd+Alt+C)'
- }],
-
- onsubmit: function(e) {
- editor.insertContent(
- '[cite num="' + e.data.citation_number + '"]'
- );
- }
- });
- }
- },
- // Separator
- {
- text: '-'
- },
- // Single Reference Menu Item
- {
- text: 'Formatted Reference',
- onclick: function() {
- editor.windowManager.open({
- title: 'Insert Formatted Reference',
- width: 600,
- height: 125,
- body: [{
- type: 'textbox',
- name: 'ref_id_number',
- label: 'PMID',
- value: ''
- }, {
- type: 'listbox',
- label: 'Citation Format',
- name: 'ref_id_citation_type',
- 'values': [{
- text: 'American Medical Association (AMA)',
- value: 'AMA'
- }, {
- text: 'American Psychological Association (APA)',
- value: 'APA'
- }]
-
- }, {
- type: 'checkbox',
- name: 'ref_id_include_link',
- label: 'Include link to PubMed?'
- }, {
- type: 'container',
- html: 'Protip: Use the keyboard shortcut to access this menu PC/Linux: (Ctrl+Alt+R) | Mac: (Cmd+Alt+R)'
- }],
- onsubmit: function(e) {
-
- editor.setProgressState(1);
- var PMID = e.data.ref_id_number;
- var citationFormat = e.data.ref_id_citation_type;
- var includePubmedLink = e.data.ref_id_include_link;
-
- var request = new XMLHttpRequest();
- request.open('GET', 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=' + PMID + '&version=2.0&retmode=json', true);
- request.onload = function() {
- if (request.readyState === 4) {
- if (request.status === 200) {
- output = parseRequestData(JSON.parse(request.responseText), PMID, citationFormat, includePubmedLink);
- editor.insertContent(output);
- editor.setProgressState(0);
- } else {
- alert('ERROR: PMID not recognized.');
- editor.setProgressState(0);
- return;
- }
- }
- };
- request.send(null);
-
- }
- });
- }
- }
- ]
- }, {
- text: 'Tracked Link',
- onclick: function() {
- var user_selection = tinyMCE.activeEditor.selection.getContent({
- format: 'text'
- });
- editor.windowManager.open({
- title: 'Insert Tracked Link',
- width: 600,
- height: 160,
- buttons: [{
- text: 'Insert',
- onclick: 'submit'
- }],
- body: [{
- type: 'textbox',
- name: 'tracked_url',
- label: 'URL',
- value: ''
- }, {
- type: 'textbox',
- name: 'tracked_title',
- label: 'Link Text',
- value: user_selection
- }, {
- type: 'textbox',
- name: 'tracked_tag',
- label: 'Custom Tag ID',
- tooltip: 'Don\'t forget to create matching tag in Google Tag Manager!',
- value: ''
- }, {
- type: 'checkbox',
- name: 'tracked_new_window',
- label: 'Open link in a new window/tab'
- }, ],
- onsubmit: function(e) {
- var trackedUrl = e.data.tracked_url;
- var trackedTitle = e.data.tracked_title;
- var trackedTag = e.data.tracked_tag;
- var trackedLink;
-
-
- if (e.data.tracked_new_window) {
- trackedLink = '' + trackedTitle + ' ';
- } else {
- trackedLink = '' + trackedTitle + ' ';
- }
-
- editor.execCommand('mceInsertContent', false, trackedLink);
- }
- });
- }
- }, {
- text: '-'
- }, {
- text: 'Request More Tools',
- onclick: function() {
- editor.windowManager.open({
- title: 'Request More Tools',
- body: [
-
- {
- type: 'label',
- text: "Have a feature or tool in mind that isn't available? Visit the link below to send a feature request. We'll do our best to make it happen."
- }, {
- type: 'button',
- text: 'Send us your thoughts!',
- onclick: function() {
- window.open('https://github.com/dsifford/academic-bloggers-toolkit/issues', '_blank');
- },
- }
-
- ],
- onsubit: function() {
- return;
- }
- });
- }
- }
-
- ]
- });
- editor.addShortcut('meta+alt+r', 'Insert Formatted Reference', function() {
- editor.windowManager.open({
- title: 'Insert Formatted Reference',
- width: 600,
- height: 125,
- body: [{
- type: 'textbox',
- name: 'ref_id_number',
- label: 'PMID',
- value: ''
- }, {
- type: 'listbox',
- label: 'Citation Format',
- name: 'ref_id_citation_type',
- 'values': [{
- text: 'American Medical Association (AMA)',
- value: 'AMA'
- }, {
- text: 'American Psychological Association (APA)',
- value: 'APA'
- }]
-
- }, {
- type: 'checkbox',
- name: 'ref_id_include_link',
- label: 'Include link to PubMed?'
- }],
- onsubmit: function(e) {
-
- editor.setProgressState(1);
- var PMID = e.data.ref_id_number;
- var citationFormat = e.data.ref_id_citation_type;
- var includePubmedLink = e.data.ref_id_include_link;
-
- var request = new XMLHttpRequest();
- request.open('GET', 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=' + PMID + '&version=2.0&retmode=json', true);
- request.onload = function() {
- if (request.readyState === 4) {
- if (request.status === 200) {
- output = parseRequestData(JSON.parse(request.responseText), PMID, citationFormat, includePubmedLink);
- editor.insertContent(output);
- editor.setProgressState(0);
- } else {
- alert('ERROR: PMID not recognized.');
- editor.setProgressState(0);
- return;
- }
- }
- };
- request.send(null);
-
- }
- });
- });
-
- editor.addShortcut('meta+alt+c', 'Insert Inline Citation', function() {
- editor.windowManager.open({
- title: 'Insert Citation',
- width: 600,
- height: 58,
- body: [{
- type: 'textbox',
- name: 'citation_number',
- label: 'Citation Number',
- value: ''
- }],
- onsubmit: function(e) {
- editor.insertContent(
- '[cite num="' + e.data.citation_number + '"]'
- );
- }
- });
- });
-
- });
-
- function parseRequestData(data, PMID, citationFormat, includePubmedLink) {
-
- var authorsRaw = data.result[PMID].authors;
- var title = data.result[PMID].title;
- var journalName = data.result[PMID].source;
- var pubYear = data.result[PMID].pubdate.substr(0, 4);
- var volume = data.result[PMID].volume;
- var issue = data.result[PMID].issue;
- var pages = data.result[PMID].pages;
-
- var authors = '';
- var output, i;
-
- if (citationFormat === 'AMA') {
-
- /**
- * AUTHOR PARSING
- */
-
- // 0 AUTHORS
- if (authorsRaw.length === 0) {
- alert('ERROR: No authors were found for this PMID.\n\nPlease double-check PMID or insert reference manually.');
- }
- // 1 AUTHOR
- else if (authorsRaw.length === 1) {
- authors = data.result[PMID].authors[0].name;
- }
- // 2 - 6 AUTHORS
- else if (authorsRaw.length > 1 && authorsRaw.length < 7) {
-
- for (i = 0; i < authorsRaw.length - 1; i++) {
- authors += authorsRaw[i].name + ', ';
- }
- authors += authorsRaw[authorsRaw.length - 1].name + '. ';
- }
- // >7 AUTHORS
- else {
- for (i = 0; i < 3; i++) {
- authors += authorsRaw[i].name + ', ';
- }
- authors += 'et al. ';
- }
-
- // NO VOLUME NUMBER
- if (volume === '') {
- output = authors + ' ' + title + ' ' + journalName + '. ' + pubYear + '; ' + volume + ': ' + pages + '.';
- }
- // NO ISSUE NUMBER
- else if (issue === '' || issue === undefined) {
- output = authors + ' ' + title + ' ' + journalName + '. ' + pubYear + '; ' + volume + ': ' + pages + '.';
- } else {
- output = authors + ' ' + title + ' ' + journalName + '. ' + pubYear + '; ' + volume + '(' + issue + '): ' + pages + '.';
- }
-
-
- } else if (citationFormat === 'APA') {
-
- /**
- * AUTHOR PARSING
- */
-
- // 0 AUTHORS
- if (authorsRaw.length === 0) {
- alert('ERROR: No authors were found for this PMID.\n\nPlease double-check PMID or insert reference manually.');
- }
- // 1 AUTHOR
- else if (authorsRaw.length === 1) {
-
- // Check to see if both initials are listed
- if ((/( \w\w)/g).test(data.result[PMID].authors[0].name)) {
- authors += data.result[PMID].authors[0].name.substring(0, data.result[PMID].authors[0].name.length - 3) + ', ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 2, data.result[PMID].authors[0].name.length - 1) + '. ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 1) + '. ';
- } else {
- authors += data.result[PMID].authors[0].name.substring(0, data.result[PMID].authors[0].name.length - 2) + ', ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 1) + '. ';
- }
-
- }
- // 2 Authors
- else if (authorsRaw.length === 2) {
-
- if ((/( \w\w)/g).test(data.result[PMID].authors[0].name)) {
-
- authors += data.result[PMID].authors[0].name.substring(0, data.result[PMID].authors[0].name.length - 3) + ', ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 2, data.result[PMID].authors[0].name.length - 1) + '. ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 1) + '., & ';
-
- } else {
-
- authors += data.result[PMID].authors[0].name.substring(0, data.result[PMID].authors[0].name.length - 2) + ', ' +
- data.result[PMID].authors[0].name.substring(data.result[PMID].authors[0].name.length - 1) + '., & ';
-
- }
-
- if ((/( \w\w)/g).test(data.result[PMID].authors[1].name)) {
-
- authors += data.result[PMID].authors[1].name.substring(0, data.result[PMID].authors[1].name.length - 3) + ', ' +
- data.result[PMID].authors[1].name.substring(data.result[PMID].authors[1].name.length - 2, data.result[PMID].authors[1].name.length - 1) + '. ' +
- data.result[PMID].authors[1].name.substring(data.result[PMID].authors[1].name.length - 1) + '. ';
-
- } else {
-
- authors += data.result[PMID].authors[1].name.substring(0, data.result[PMID].authors[1].name.length - 2) + ', ' +
- data.result[PMID].authors[1].name.substring(data.result[PMID].authors[1].name.length - 1) + '. ';
-
- }
-
- }
- // 3-7 AUTHORS
- else if (authorsRaw.length > 2 && authorsRaw.length < 8) {
-
- for (i = 0; i < authorsRaw.length - 1; i++) {
-
- if ((/( \w\w)/g).test(data.result[PMID].authors[i].name)) {
-
- authors += data.result[PMID].authors[i].name.substring(0, data.result[PMID].authors[i].name.length - 3) + ', ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 2, data.result[PMID].authors[i].name.length - 1) + '. ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 1) + '., ';
-
- } else {
-
- authors += data.result[PMID].authors[i].name.substring(0, data.result[PMID].authors[i].name.length - 2) + ', ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 1) + '., ';
-
- }
-
- }
-
- if ((/( \w\w)/g).test(data.result[PMID].lastauthor)) {
-
- authors += '& ' + data.result[PMID].lastauthor.substring(0, data.result[PMID].lastauthor.length - 3) + ', ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 2, data.result[PMID].lastauthor.length - 1) + '. ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 1) + '. ';
-
- } else {
-
- authors += '& ' + data.result[PMID].lastauthor.substring(0, data.result[PMID].lastauthor.length - 2) + ', ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 1) + '. ';
-
- }
-
- }
- // >7 AUTHORS
- else {
-
- for (i = 0; i < 6; i++) {
-
- if ((/( \w\w)/g).test(data.result[PMID].authors[i].name)) {
-
- authors += data.result[PMID].authors[i].name.substring(0, data.result[PMID].authors[i].name.length - 3) + ', ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 2, data.result[PMID].authors[i].name.length - 1) + '. ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 1) + '., ';
-
- } else {
-
- authors += data.result[PMID].authors[i].name.substring(0, data.result[PMID].authors[i].name.length - 2) + ', ' +
- data.result[PMID].authors[i].name.substring(data.result[PMID].authors[i].name.length - 1) + '., ';
-
- }
-
- }
-
- if ((/( \w\w)/g).test(data.result[PMID].lastauthor)) {
-
- authors += '. . . ' + data.result[PMID].lastauthor.substring(0, data.result[PMID].lastauthor.length - 3) + ', ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 2, data.result[PMID].lastauthor.length - 1) + '. ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 1) + '. ';
-
- } else {
-
- authors += '. . . ' + data.result[PMID].lastauthor.substring(0, data.result[PMID].lastauthor.length - 2) + ', ' +
- data.result[PMID].lastauthor.substring(data.result[PMID].lastauthor.length - 1) + '. ';
-
- }
-
-
- }
-
- output = authors + '(' + pubYear + '). ' + title + ' ' + journalName + ' , ' + (volume !== '' ? volume : '') + (issue !== '' ? '(' + issue + '), ' : '') + pages + '.';
-
- }
-
- // INCLUDE LINK TO PUBMED IF CHECKBOX IS CHECKED
- if (includePubmedLink) {
- output += ' PMID: ' + PMID + ' ';
- }
-
- return output;
-
-
- }
-
-})
-();
diff --git a/inc/js/tinymce-entrypoint.ts b/inc/js/tinymce-entrypoint.ts
new file mode 100644
index 00000000..55dc8134
--- /dev/null
+++ b/inc/js/tinymce-entrypoint.ts
@@ -0,0 +1,231 @@
+import Dispatcher from './utils/Dispatcher';
+import { parseInlineCitationString } from './utils/HelperFunctions';
+
+declare var tinyMCE, ABT_locationInfo
+
+tinyMCE.PluginManager.add('abt_main_menu', (editor, url: string) => {
+
+ //==================================================
+ // MAIN BUTTON
+ //==================================================
+
+ let ABT_Button: any = {
+ id: 'abt_menubutton',
+ type: 'menubutton',
+ icon: 'abt_menu dashicons-welcome-learn-more',
+ title: 'Academic Blogger\'s Toolkit',
+ menu: [],
+ };
+
+ //==================================================
+ // BUTTON FUNCTIONS
+ //==================================================
+
+ let openInlineCitationWindow = () => {
+ editor.windowManager.open({
+ title: 'Inline Citation',
+ url: ABT_locationInfo.tinymceViewsURL + 'citation-window.html',
+ width: 400,
+ height: 85,
+ onClose: (e) => {
+ if (!e.target.params.data) { return; }
+ editor.insertContent(
+ '[cite num="' + parseInlineCitationString(e.target.params.data) + '"]'
+ );
+ }
+ });
+ }
+
+ let openFormattedReferenceWindow = () => {
+ editor.windowManager.open({
+ title: 'Insert Formatted Reference',
+ url: ABT_locationInfo.tinymceViewsURL + 'reference-window.html',
+ width: 600,
+ height: 10,
+ params: {
+ baseUrl: ABT_locationInfo.tinymceViewsURL,
+ preferredStyle: ABT_locationInfo.preferredCitationStyle,
+ },
+ onclose: (e: any) => {
+
+ // If the user presses the exit button, return.
+ if (Object.keys(e.target.params).length === 0) {
+ return;
+ }
+
+ editor.setProgressState(1);
+ let payload: ReferenceFormData = e.target.params.data;
+ let refparser = new Dispatcher(payload, editor);
+
+ if (payload.addManually === true) {
+ refparser.fromManualInput(payload);
+ return;
+ }
+
+ refparser.fromPMID();
+ },
+ });
+ };
+
+ let generateSmartBib = function() {
+ let dom: HTMLDocument = editor.dom.doc;
+ let existingSmartBib: HTMLOListElement = dom.getElementById('abt-smart-bib');
+
+ if (!existingSmartBib) {
+ let smartBib: HTMLOListElement = dom.createElement('OL');
+ let horizontalRule: HTMLHRElement = dom.createElement('HR');
+ smartBib.id = 'abt-smart-bib';
+ horizontalRule.className = 'abt_editor-only';
+ let comment = dom.createComment(`Smart Bibliography Generated By Academic Blogger's Toolkit`);
+ dom.body.appendChild(comment);
+ dom.body.appendChild(horizontalRule);
+ dom.body.appendChild(smartBib);
+ this.state.set('disabled', true);
+ }
+
+ return;
+ }
+
+
+ //==================================================
+ // MENU ITEMS
+ //==================================================
+
+ let separator: TinyMCEMenuItem = { text: '-' };
+
+
+ let bibToolsMenu: TinyMCEMenuItem = {
+ text: 'Other Tools',
+ menu: [],
+ };
+
+
+ let inlineCitation: TinyMCEMenuItem = {
+ text: 'Inline Citation',
+ onclick: openInlineCitationWindow,
+ }
+ editor.addShortcut('meta+alt+c', 'Insert Inline Citation', openInlineCitationWindow);
+
+ let formattedReference: TinyMCEMenuItem = {
+ text: 'Formatted Reference',
+ onclick: openFormattedReferenceWindow,
+ }
+ editor.addShortcut('meta+alt+r', 'Insert Formatted Reference', openFormattedReferenceWindow);
+
+
+ let smartBib: TinyMCEMenuItem = {
+ text: 'Generate Smart Bibliography',
+ id: 'smartbib',
+ onclick: generateSmartBib,
+ disabled: false,
+ }
+
+
+ /** NOTE: THIS WILL BE DEPRECIATED NEXT RELEASE! */
+ let trackedLink: TinyMCEMenuItem = {
+ text: 'Tracked Link',
+ onclick: () => {
+
+ let user_selection = tinyMCE.activeEditor.selection.getContent({format: 'text'});
+
+ editor.windowManager.open({
+ title: 'Insert Tracked Link === Depreciating Next Release! ===',
+ width: 600,
+ height: 160,
+ buttons: [{
+ text: 'Insert',
+ onclick: 'submit'
+ }],
+ body: [
+ {
+ type: 'textbox',
+ name: 'tracked_url',
+ label: 'URL',
+ value: ''
+ },
+ {
+ type: 'textbox',
+ name: 'tracked_title',
+ label: 'Link Text',
+ value: user_selection
+ },
+ {
+ type: 'textbox',
+ name: 'tracked_tag',
+ label: 'Custom Tag ID',
+ tooltip: 'Don\'t forget to create matching tag in Google Tag Manager!',
+ value: ''
+ },
+ {
+ type: 'checkbox',
+ name: 'tracked_new_window',
+ label: 'Open link in a new window/tab'
+ },
+ ],
+ onsubmit: (e) => {
+ let trackedUrl = e.data.tracked_url;
+ let trackedTitle = e.data.tracked_title;
+ let trackedTag = e.data.tracked_tag;
+ let trackedLink = `${trackedTitle} `;
+
+ editor.execCommand('mceInsertContent', false, trackedLink);
+
+ }
+ });
+ }
+ }
+ // End Tracked Link Menu Item
+
+ let requestTools: TinyMCEMenuItem = {
+ text: 'Request More Tools',
+ onclick: () => {
+ editor.windowManager.open({
+ title: 'Request More Tools',
+ body: [{
+ type: 'container',
+ html:
+ `` +
+ `Have a feature or tool in mind that isn't available?
` +
+ `
Open an issue on the GitHub repository and let me know!` +
+ `
`,
+ }],
+ buttons: [],
+ });
+ }
+ }
+
+
+ let keyboardShortcuts: TinyMCEMenuItem = {
+ text: 'Keyboard Shortcuts',
+ onclick: () => {
+ editor.windowManager.open({
+ title: 'Keyboard Shortcuts',
+ url: ABT_locationInfo.tinymceViewsURL + 'keyboard-shortcuts.html',
+ width: 400,
+ height: 90,
+ });
+ }
+ }
+
+ // Workaround for checking to see if a smart bib exists.
+ setTimeout(() => {
+ let dom: HTMLDocument = editor.dom.doc;
+ let existingSmartBib: HTMLOListElement = dom.getElementById('abt-smart-bib');
+ if (existingSmartBib) {
+ smartBib.disabled = true;
+ smartBib.text = 'Smart Bibliography Generated!';
+ }
+ }, 500);
+
+ bibToolsMenu.menu.push(trackedLink, separator, requestTools);
+ ABT_Button.menu.push(smartBib, inlineCitation, formattedReference, bibToolsMenu, separator, keyboardShortcuts);
+
+ editor.addButton('abt_main_menu', ABT_Button);
+
+
+
+});
diff --git a/inc/js/tinymce-views/citation-window.html b/inc/js/tinymce-views/citation-window.html
new file mode 100644
index 00000000..d6d6c8ff
--- /dev/null
+++ b/inc/js/tinymce-views/citation-window.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/inc/js/tinymce-views/keyboard-shortcuts.html b/inc/js/tinymce-views/keyboard-shortcuts.html
new file mode 100644
index 00000000..256b0ea6
--- /dev/null
+++ b/inc/js/tinymce-views/keyboard-shortcuts.html
@@ -0,0 +1,14 @@
+
+
+
+ ctrl+alt+c
+ Open Inline Citations Menu
+
+
+ ctrl+alt+r
+ Open Formatted Reference Menu
+
+
+
+ Note: If you're on Mac, substitute ctrl for cmd
+
diff --git a/inc/js/tinymce-views/pubmed-window.html b/inc/js/tinymce-views/pubmed-window.html
new file mode 100644
index 00000000..e92505c0
--- /dev/null
+++ b/inc/js/tinymce-views/pubmed-window.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/inc/js/tinymce-views/reference-window.html b/inc/js/tinymce-views/reference-window.html
new file mode 100644
index 00000000..09161edf
--- /dev/null
+++ b/inc/js/tinymce-views/reference-window.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/inc/js/tinymce-views/styles.scss b/inc/js/tinymce-views/styles.scss
new file mode 100644
index 00000000..9b048b7b
--- /dev/null
+++ b/inc/js/tinymce-views/styles.scss
@@ -0,0 +1,177 @@
+body {
+ overflow: hidden;
+}
+
+.row {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.manual-input-table {
+ width: 100%;
+ input {
+ width: 100%;
+ }
+}
+
+.btn {
+ background: #f7f7f7;
+ border-color: #ccc;
+ border-radius: 3px;
+ border-style: solid;
+ border-width: 1px;
+ box-shadow: 0 1px 0 #ccc;
+ box-sizing: border-box;
+ color: #555;
+ cursor: pointer;
+ display: inline-block;
+ font-size: 13px;
+ height: 30px;
+ line-height: 31px;
+ margin: 2px 0;
+ padding: 0 12px 2px;
+ vertical-align: top;
+ white-space: nowrap;
+ -webkit-appearance: none;
+
+ &:hover {
+ background: #fafafa;
+ border-color: #999;
+ color: #23282d;
+ }
+
+ &:active {
+ background: #eee;
+ border-color: #999;
+ box-shadow: inset 0 2px 5px -3px rgba(0,0,0,.5);
+ transform: translateY(1px);
+ outline: 0;
+ }
+
+ &:disabled {
+ color: #a0a5aa;
+ border-color: #ddd;
+ background: #f7f7f7;
+ box-shadow: none;
+ text-shadow: 0 1px 0 #fff;
+ cursor: default;
+ transform: none;
+ }
+}
+
+.submit-btn {
+ @extend .btn;
+ background: #0085ba;
+ border-color: #0073aa #006799 #006799;
+ box-shadow: 0 1px 0 #006799;
+ color: #fff;
+ text-decoration: none;
+ text-shadow: 0 -1px 1px #006799,1px 0 1px #006799,0 1px 1px #006799,-1px 0 1px #006799;
+
+ &:hover {
+ background: #008ec2;
+ border-color: #006799;
+ color: #fff
+ }
+
+ &:active {
+ background: #0073aa;
+ border-color: #006799;
+ box-shadow: inset 0 2px 0 #006799;
+ vertical-align: top;
+ }
+
+ &:disabled {
+ background: #008ec2;
+ color: #66c6e4;
+ border-color: #007cb2;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.1);
+ cursor: default;
+ }
+}
+
+input, select {
+ border: 1px solid #ddd;
+ box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
+ background-color: #fff;
+ color: #32373c;
+ outline: 0;
+ height: 31px;
+ transition: 50ms border-color ease-in-out;
+ margin: 1px;
+ padding: 5px;
+ font-size: 1em;
+
+ &:focus {
+ border-color: #5b9dd9;
+ box-shadow: 0 0 2px rgba(30, 140, 190, .8);
+ &:invalid {
+ box-shadow: 0 0 2px rgba(204,0,0,.8);
+ border-color: #dc3232!important;
+ }
+ }
+
+ &[type="checkbox"] {
+ height: 18px;
+ width: 18px;
+ vertical-align: middle;
+ }
+
+ &:invalid {
+ background: #f6c9cc;
+ color: #dc3232;
+ border: solid #E58383 1px;
+ }
+
+}
+
+select {
+ padding: 2px;
+ vertical-align: middle;
+ margin: 1px;
+ font-size: 1em;
+}
+
+a {
+ color: #0073aa;
+
+ &:hover {
+ color: #00a0d2;
+ }
+}
+
+kbd {
+ font-family: monospace;
+ padding: 2px 7px 3px;
+ font-weight: 400;
+ margin: 0;
+ font-size: 15px;
+ background: #eaeaea;
+ background: rgba(0,0,0,.08)
+}
+
+.cite-row {
+ padding: 5px;
+ font-size: 0.8em;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ &.even {
+ background: #fafafa;
+ border: 1px solid #ccc;
+ border-left: none;
+ border-right: none;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+}
+
+.cite-selected {
+ background: rgba(0, 133, 186, 0.5)!important;
+}
diff --git a/inc/js/utils/Dispatcher.ts b/inc/js/utils/Dispatcher.ts
new file mode 100644
index 00000000..13e70103
--- /dev/null
+++ b/inc/js/utils/Dispatcher.ts
@@ -0,0 +1,205 @@
+import { AMA, APA } from './Parsers';
+import './PrototypeFunctions';
+
+export default class Dispatcher {
+ public citationFormat: string;
+ public includeLink: boolean;
+ public attachInline: boolean;
+ public manualCitationType: string;
+ public PMIDquery: string;
+ public editor: any;
+ public smartBib: HTMLOListElement|boolean
+
+ constructor(data: ReferenceFormData, editor: Object) {
+ this.citationFormat = data.citationFormat;
+ this.PMIDquery = data.pmidList !== ''
+ ? data.pmidList.replace(/\s/g, '')
+ : '';
+ this.manualCitationType = data.manualData.type;
+ this.includeLink = data.includeLink;
+ this.attachInline = data.attachInline;
+ this.editor = editor;
+ let smartBib = (this.editor.dom.doc as HTMLDocument)
+ .getElementById('abt-smart-bib') as HTMLOListElement;
+ this.smartBib = smartBib || false;
+ }
+
+ public fromPMID(): void {
+ let requestURL = `http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=${this.PMIDquery}&version=2.0&retmode=json`;
+ let request = new XMLHttpRequest();
+ request.open('GET', requestURL, true);
+ request.addEventListener('load', this._parsePMID.bind(this));
+ request.send(null);
+ }
+
+ public fromManualInput(data: ReferenceFormData): void {
+ let cleanedData: ReferenceObj;
+ let type = this.manualCitationType;
+
+ // Reformat name to
+ let authors: Author[] = data.manualData.authors.map((author: any) => {
+ let name = this._prepareName(author);
+ return { name };
+ });
+
+ let meta: JournalMeta|BookMeta|WebsiteMeta = data.manualData.meta[type];
+
+ let title: string = meta.title.toTitleCase();
+ let source: string = meta.source;
+ let pubdate: string = meta.pubdate;
+ let lastauthor: string = data.manualData.authors.length > 0
+ ? this._prepareName(data.manualData.authors[data.manualData.authors.length - 1])
+ : '';
+
+ let volume: string;
+ let issue: string;
+ let pages: string;
+
+ let url: string;
+ let accessdate: string;
+ let updated: string;
+
+ let location: string;
+ let chapter: string;
+ let edition: string;
+
+ switch (type) {
+ case 'journal':
+ volume = (meta as JournalMeta).volume;
+ issue = (meta as JournalMeta).issue;
+ pages = (meta as JournalMeta).pages;
+ break;
+ case 'website':
+ url = (meta as WebsiteMeta).url;
+ accessdate = (meta as WebsiteMeta).accessed;
+ updated = (meta as WebsiteMeta).updated;
+ break;
+ case 'book':
+ pages = (meta as BookMeta).pages;
+ location = (meta as BookMeta).location;
+ chapter = (meta as BookMeta).chapter;
+ edition = (meta as BookMeta).edition;
+ break;
+ }
+
+ cleanedData = {
+ authors,
+ title,
+ source,
+ pubdate,
+ volume,
+ issue,
+ pages,
+ lastauthor,
+ url,
+ accessdate,
+ updated,
+ location,
+ chapter,
+ edition,
+ }
+
+ let payload: string[]|Error;
+ switch (this.citationFormat) {
+ case 'ama':
+ let ama = new AMA(this.includeLink, this.manualCitationType);
+ payload = ama.parse([cleanedData]);
+ break;
+ case 'apa':
+ let apa = new APA(this.includeLink, this.manualCitationType);;
+ payload = apa.parse([cleanedData]);
+ break;
+ default:
+ this.editor.windowManager.alert('An error occurred while trying to parse the citation');
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ this._deliverContent(payload);
+
+ }
+
+ private _prepareName(author: Author): string {
+ return( author.lastname[0].toUpperCase() +
+ author.lastname.substring(1, author.lastname.length) + ' ' +
+ author.firstname[0].toUpperCase() + author.middleinitial.toUpperCase()
+ )
+ }
+
+
+ private _parsePMID(e: Event): void {
+ let req = e.target;
+
+ // Handle bad request
+ if (req.readyState !== 4 || req.status !== 200) {
+ this.editor.windowManager.alert('Your request could not be processed. Please try again.');
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ let res = JSON.parse(req.responseText);
+
+ // Handle response errors
+ if (res.error) {
+ let badPMID = res.error.match(/uid (\S+)/)[1];
+ let badIndex = this.PMIDquery.split(',').indexOf(badPMID);
+ this.editor.windowManager.alert(
+ `PMID "${badPMID}" at index #${badIndex + 1} failed to process. Double check your list!`
+ );
+ }
+
+ let payload: string[]|Error;
+ switch (this.citationFormat) {
+ case 'ama':
+ let ama = new AMA(this.includeLink, this.citationFormat);
+ payload = ama.parse(res.result);
+ break;
+ case 'apa':
+ let apa = new APA(this.includeLink, this.citationFormat);
+ payload = apa.parse(res.result);
+ break;
+ default:
+ this.editor.windowManager.alert('An error occurred while trying to parse the citation');
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ this._deliverContent(payload);
+
+ }
+
+ private _deliverContent(payload: string[]|Error): void {
+ if ((payload as Error).name === 'Error') {
+ this.editor.windowManager.alert((payload as Error).message);
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ if (this.smartBib) {
+ let beforeLength: number = (this.smartBib as HTMLOListElement).children.length + 1;
+ for (let key in (payload as string[])) {
+ let listItem = (this.editor.dom.doc as HTMLDocument).createElement('LI');
+ listItem.innerHTML = payload[key];
+ (this.smartBib as HTMLOListElement).appendChild(listItem);
+ }
+ if (this.attachInline) {
+ let afterLength: number = (this.smartBib as HTMLOListElement).children.length;
+ this.editor.insertContent(`[cite num="${beforeLength}${afterLength > beforeLength ? '-' + afterLength : ''}"]`);
+ }
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ if ((payload as string[]).length === 1) {
+ this.editor.insertContent((payload as string[]).join());
+ this.editor.setProgressState(0);
+ return;
+ }
+
+ let orderedList: string =
+ '' + (payload as string[]).map((ref: string) => `${ref} `).join('') + ' ';
+
+ this.editor.insertContent(orderedList);
+ this.editor.setProgressState(0);
+ }
+}
diff --git a/inc/js/utils/HelperFunctions.ts b/inc/js/utils/HelperFunctions.ts
new file mode 100644
index 00000000..8ebca34b
--- /dev/null
+++ b/inc/js/utils/HelperFunctions.ts
@@ -0,0 +1,68 @@
+
+
+/**
+ * Function that takes a sorted array of integers as input and returns
+ * an inline citation string representation of the numbers.
+ *
+ * Example: [1,3,4,5,10] => "1,3-5,10"
+ *
+ * @param {number[]} numArr Sorted array of integers.
+ * @returns {string} A formatted inline citation string.
+ */
+export function parseInlineCitationString(numArr: number[]): string {
+ if (numArr.length === 0) { return ''; }
+
+ let output: string = numArr[0].toString();
+
+ for (let i = 1; i < numArr.length; i++) {
+ switch (output[output.length - 1]) {
+ case ',':
+ output += numArr[i];
+ break;
+ case '-':
+ if (numArr[i+1] === numArr[i] + 1) { break; }
+ if (i === numArr.length - 1) { output += numArr[i]; break;}
+ output += numArr[i] + ',';
+ break;
+ default:
+ let lastNum = parseInt(output.split(',')[output.split(',').length - 1]);
+ if (lastNum === numArr[i] - 1 && numArr[i + 1] === numArr[i] + 1) {
+ output += '-';
+ break;
+ }
+ output += ',' + numArr[i];
+ }
+ }
+
+ return output;
+}
+
+/**
+ * Function that takes an inline citation string and returns an array of
+ * integers for that string.
+ *
+ * Example: "1-3,6,8-10" => [1,2,3,6,8,9,10]
+ *
+ * @param {string} input An inline citation string.
+ * @returns {number[]} An array of integers that represent the input string.
+ */
+export function parseCitationNumArray(input: string): number[] {
+ let x = input.split(',');
+ let output: number[] = [];
+
+ if (x.length === 0 || (x.length === 1 && x[0] === '')) { return []; }
+
+ for (let i = 0; i < x.length; i++) {
+ switch (x[i].match('-')) {
+ case null:
+ output.push(parseInt(x[i]));
+ break;
+ default:
+ for (let j = parseInt(x[i].split('-')[0]); j <= parseInt(x[i].split('-')[1]); j++) {
+ output.push(j);
+ }
+ }
+ }
+
+ return output;
+}
diff --git a/inc/js/utils/Modal.ts b/inc/js/utils/Modal.ts
new file mode 100644
index 00000000..bafd12a5
--- /dev/null
+++ b/inc/js/utils/Modal.ts
@@ -0,0 +1,40 @@
+export default class Modal {
+
+ public title: string
+ public outer: HTMLElement
+ public inner: HTMLElement
+ public mceReset: HTMLElement
+ public mainRect: HTMLElement
+ public initialSize: {
+ outer: number
+ inner: number
+ }
+
+ constructor(title: string) {
+ this.title = title;
+ this._getModal();
+ this.initialSize = {
+ outer: parseInt(this.outer.style.height.substr(0, this.outer.style.height.length - 2)),
+ inner: parseInt(this.inner.style.height.substr(0, this.inner.style.height.length - 2)),
+ }
+ }
+
+ public resize(): void {
+ let height = this.mainRect.getBoundingClientRect().height;
+ let position = `calc(50% - ${(height + 56) / 2}px)`;
+ this.outer.style.height = height + 56 + 'px';
+ this.outer.style.top = position;
+ };
+
+ private _getModal(): void {
+ let outerModalID: string = top.document.querySelector(`div.mce-floatpanel[aria-label="${this.title}"]`).id;
+ let innerModalID: string = `${outerModalID}-body`;
+ this.outer = top.document.getElementById(outerModalID);
+ this.inner = top.document.getElementById(innerModalID);
+ this.mceReset = this.outer.children[0] as HTMLElement;
+ this.mainRect = document.getElementById('main-container');
+ this.mceReset.style.height = '100%';
+ this.inner.style.height = '100%';
+ }
+
+}
diff --git a/inc/js/utils/Parsers.ts b/inc/js/utils/Parsers.ts
new file mode 100644
index 00000000..ed93e1bc
--- /dev/null
+++ b/inc/js/utils/Parsers.ts
@@ -0,0 +1,302 @@
+
+export class AMA {
+
+ private _isManual: boolean = true;
+ private manualCitationType: string;
+ private includeLink: boolean;
+
+ constructor(includeLink?: boolean, manualCitationType?: string) {
+ this.includeLink = includeLink;
+ this.manualCitationType = manualCitationType;
+ }
+
+ public parse(data: ReferencePayload): string[]|Error {
+
+ if (data.uids) {
+ this._isManual = false
+ }
+
+ if (this._isManual) {
+ return [this._fromManual(data)];
+ }
+
+ return this._fromPMID(data, data.uids);
+ }
+
+ private _fromPMID(data: ReferencePayload, pmidArray: string[]): string[]|Error {
+ let output: string[]|Error;
+ try {
+ output = pmidArray.map((PMID: string): string => {
+ let ref: ReferenceObj = data[PMID];
+ let year: string = ref.pubdate.substr(0, 4);
+ let link = this.includeLink === true
+ ? ` PMID: ${PMID} `
+ : '';
+
+ let authors: string|Error = this._parseAuthors(ref.authors);
+ if ((authors as Error).name === 'Error') {
+ throw authors;
+ }
+
+ return `${authors} ${ref.title} ${ref.source}. ${year}; ` +
+ `${ref.volume === undefined || ref.volume === '' ? '' : ref.volume}` +
+ `${ref.issue === undefined || ref.issue === '' ? '' : '('+ref.issue+')'}:` +
+ `${ref.pages}.${link}`;
+ });
+ } catch(e) {
+ return e;
+ }
+ return output;
+ }
+
+ private _fromManual(data: ReferencePayload): string {
+
+ let payload: string;
+ switch (this.manualCitationType) {
+ case 'journal':
+ payload = this._parseJournal(data);
+ break;
+ case 'website':
+ payload = this._parseWebsite(data);
+ break;
+ case 'book':
+ payload = this._parseBook(data);
+ break;
+ }
+
+ return payload;
+ }
+
+ private _parseAuthors(authorArr: Author[]): string|Error {
+ let authors: string = '';
+ switch (authorArr.length) {
+ case 0:
+ if (this._isManual === true) { break; }
+ return new Error(`No authors were found for given reference`);
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ authors = authorArr.map((author: Author) => author.name).join(', ') + '.';
+ break;
+ default:
+ for (let i = 0; i < 3; i++) { authors+= authorArr[i].name + ', ' };
+ authors += 'et al.';
+ }
+ return authors;
+ }
+
+ private _parseJournal(data: ReferencePayload): string {
+ let authors = this._parseAuthors(data[0].authors);
+ let year = (new Date(data[0].pubdate).getFullYear() + 1).toString();
+ let source = data[0].source.toTitleCase();
+ let issue = `(${data[0].issue})` || '';
+ let volume = data[0].volume || '';
+
+ return `${authors} ${data[0].title}. ${source}. ${year}; ` +
+ `${volume}${issue}:${data[0].pages}.`;
+ }
+
+ private _parseWebsite(data: ReferencePayload): string {
+ let authors = data[0].authors.length > 0
+ ? this._parseAuthors(data[0].authors) + ' '
+ : '';
+ let pubdate: string = `Published ${new Date(data[0].pubdate).toLocaleDateString('en-us', {month: 'long', year: 'numeric'})}. `;
+ let updated: string = data[0].updated !== ''
+ ? `Updated ${new Date(data[0].updated).toLocaleDateString('en-us', {month: 'long', day: 'numeric', year: 'numeric'})}. `
+ : ''
+ let accessed: string = data[0].accessdate !== ''
+ ? `Accessed ${new Date(data[0].accessdate).toLocaleDateString('en-us', {month: 'long', day: 'numeric', year: 'numeric'})}. `
+ : `Accessed ${new Date(Date.now()).toLocaleDateString('en-us', {month: 'long', day: 'numeric', year: 'numeric'})}`;
+
+ return `${authors}${data[0].title}. ${data[0].source} . Available at: ` +
+ `${data[0].url} . ${pubdate}${updated}${accessed}`;
+ }
+
+ private _parseBook(data: ReferencePayload): string {
+ let authors = this._parseAuthors(data[0].authors);
+ let title = data[0].title;
+ let pubLocation = data[0].location !== ''
+ ? `${data[0].location}:`
+ : ``;
+ let publisher = data[0].source;
+ let year = data[0].pubdate;
+ let chapter = data[0].chapter !== ''
+ ? ` ${data[0].chapter}. In:`
+ : ``;
+ let pages = data[0].pages !== ''
+ ? `: ${data[0].pages}.`
+ : `.`;
+
+ return `${authors}${chapter} ${title} . ${pubLocation}${publisher}; ${year}${pages}`;
+ }
+
+}
+
+
+export class APA {
+
+ private _isManual: boolean = true;
+ private manualCitationType: string;
+ private includeLink: boolean;
+
+ constructor(includeLink?: boolean, manualCitationType?: string) {
+ this.includeLink = includeLink;
+ this.manualCitationType = manualCitationType;
+ }
+
+ public parse(data: ReferencePayload): string[]|Error {
+ let pmidArray: string[]|boolean = data.uids || false;
+
+ if (pmidArray) {
+ this._isManual = false;
+ return this._fromPMID(data, (pmidArray as string[]));
+ }
+
+ return [this._fromManual(data)];
+ }
+
+ private _fromPMID(data: ReferencePayload, pmidArray: string[]): string[]|Error {
+
+ let output: string[];
+
+ try {
+ output = pmidArray.map((PMID: string): string => {
+ let ref: ReferenceObj = data[PMID];
+ let year: string = ref.pubdate.substr(0, 4);
+ let link = this.includeLink === true
+ ? ` PMID: ${PMID} `
+ : '';
+
+ let authors: string|Error = this._parseAuthors(ref.authors, ref.lastauthor);
+ if ((authors as Error).name === 'Error') {
+ throw authors;
+ }
+
+ return `${authors} (${year}). ${ref.title} ` +
+ `${ref.fulljournalname === undefined || ref.fulljournalname === '' ? ref.source : ref.fulljournalname.toTitleCase()}. , ` +
+ `${ref.volume === undefined || ref.volume === '' ? '' : ref.volume}` +
+ `${ref.issue === undefined || ref.issue === '' ? '' : '('+ref.issue+')'}, ` +
+ `${ref.pages}.${link}`;
+ });
+ } catch(e) {
+ return e;
+ }
+
+ return output;
+
+ }
+
+ private _fromManual(data: ReferencePayload): string {
+ let payload: string;
+ switch (this.manualCitationType) {
+ case 'journal':
+ payload = this._parseJournal(data);
+ break;
+ case 'website':
+ payload = this._parseWebsite(data);
+ break;
+ case 'book':
+ payload = this._parseBook(data);
+ break;
+ }
+
+ return payload;
+ }
+
+ private _parseAuthors(authorArr: Author[], lastAuthor: string): string|Error {
+ let authors: string = '';
+
+ switch (authorArr.length) {
+ case 0:
+ if (this._isManual === true) { break; }
+ return new Error(`No authors were found for given reference`);
+ case 1:
+ authors = authorArr.map((author: Author) =>
+ `${author.name.split(' ')[0]}, ` + // Last name
+ `${author.name.split(' ')[1].split('').join('. ')}.` // First Initial(s)
+ ).join();
+ break;
+ case 2:
+ authors = authorArr.map((author: Author) =>
+ `${author.name.split(' ')[0]}, ` + // Last name
+ `${author.name.split(' ')[1].split('').join('. ')}.` // First Initial(s)
+ ).join(', & ');
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ authors = authorArr.map((author, i, arr) => {
+ if (i === arr.length - 1) {
+ return(
+ `& ${author.name.split(' ')[0]}, ` +
+ `${author.name.split(' ')[1].split('').join('. ')}.`
+ );
+ }
+ return(
+ `${author.name.split(' ')[0]}, ` +
+ `${author.name.split(' ')[1].split('').join('. ')}., `
+ );
+ }).join('');
+ break;
+ default:
+ for (let i = 0; i < 6; i++) {
+ authors +=
+ `${authorArr[i].name.split(' ')[0]}, ` +
+ `${authorArr[i].name.split(' ')[1].split('').join('. ')}., `
+ }
+ authors += `. . . ` +
+ `${lastAuthor.split(' ')[0]}, ` +
+ `${lastAuthor.split(' ')[1].split('').join('. ')}.`;
+ break;
+ }
+ return authors;
+
+
+ }
+
+ private _parseJournal(data: ReferencePayload): string {
+ let authors = this._parseAuthors(data[0].authors, data[0].lastauthor);
+ let year = (new Date(data[0].pubdate).getFullYear() + 1).toString();
+ let source = data[0].source.toTitleCase();
+ let issue = `(${data[0].issue})` || '';
+ let volume = data[0].volume || '';
+
+ return `${authors} (${year}). ${data[0].title}. ` +
+ `${source}. , ${volume}${issue}, ${data[0].pages}.`;
+ }
+
+ private _parseWebsite(data: ReferencePayload): string {
+ let authors = this._parseAuthors(data[0].authors, data[0].lastauthor);
+ let rawDate = new Date(data[0].pubdate);
+ let source = data[0].source.toTitleCase();
+ let date = `${rawDate.getFullYear()}, ` +
+ `${rawDate.toLocaleDateString('en-us', {month: 'long', day: 'numeric'})}`;
+
+ return `${authors} (${date}). ${data[0].title}. ${source} . ` +
+ `Retrieved from ${data[0].url} `;
+ }
+
+ private _parseBook(data: ReferencePayload): string {
+ let authors = this._parseAuthors(data[0].authors, data[0].lastauthor);
+ let year = (new Date(data[0].pubdate).getFullYear() + 1).toString();
+ let pubLocation = data[0].location !== ''
+ ? `${data[0].location}:`
+ : '';
+ let publisher = data[0].source;
+ let chapter = data[0].chapter !== ''
+ ? ` ${data[0].chapter}. In`
+ : '';
+ let pages = data[0].pages !== ''
+ ? ` (${data[0].pages})`
+ : '';
+
+ return `${authors} (${year}).${chapter} ${data[0].title} ${pages}. ` +
+ `${pubLocation}${publisher}.`;
+ }
+
+}
diff --git a/inc/js/utils/PrototypeFunctions.ts b/inc/js/utils/PrototypeFunctions.ts
new file mode 100644
index 00000000..6a780374
--- /dev/null
+++ b/inc/js/utils/PrototypeFunctions.ts
@@ -0,0 +1,26 @@
+export default {};
+
+declare global {
+ interface String {
+ toTitleCase(): string
+ }
+}
+
+String.prototype.toTitleCase = function(): string {
+ let smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\.?|via)$/i;
+
+ return this.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, function(match, index, title){
+ if (index > 0 && index + match.length !== title.length &&
+ match.search(smallWords) > -1 && title.charAt(index - 2) !== ":" &&
+ (title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-') &&
+ title.charAt(index - 1).search(/[^\s-]/) < 0) {
+ return match.toLowerCase();
+ }
+
+ if (match.substr(1).search(/[A-Z]|\../) > -1) {
+ return match;
+ }
+
+ return match.charAt(0).toUpperCase() + match.substr(1);
+ });
+};
diff --git a/inc/js/utils/PubmedAPI.ts b/inc/js/utils/PubmedAPI.ts
new file mode 100644
index 00000000..2ae99f3e
--- /dev/null
+++ b/inc/js/utils/PubmedAPI.ts
@@ -0,0 +1,68 @@
+
+
+export function PubmedQuery(query: string, callback: Function): void {
+
+ let requestURL: string = `http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=${encodeURI(query)}&retmode=json`
+ let request = new XMLHttpRequest();
+ request.open('GET', requestURL, true);
+ request.onload = () => {
+
+ // Handle bad request
+ if (request.readyState !== 4 || request.status !== 200) {
+ let error = new Error('Error: PubMed Query, Phase 1 - PubMed returned a non-200 status code.');
+ callback(error);
+ return;
+ }
+
+ let res = JSON.parse(request.responseText);
+
+ // Handle response errors
+ if (res.error) {
+ let error = new Error('Error: PubMed Query, Phase 1 - Unknown response error.');
+ callback(error);
+ return;
+ }
+
+ getData(res.esearchresult.idlist.join(), callback);
+
+ };
+ request.send(null);
+}
+
+function getData(PMIDlist: string, callback: Function): void {
+
+ let requestURL = `http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=${PMIDlist}&version=2.0&retmode=json`;
+ let request = new XMLHttpRequest();
+ request.open('GET', requestURL, true);
+ request.onload = () => {
+
+ // Handle bad request
+ if (request.readyState !== 4 || request.status !== 200) {
+ let error = new Error('Error: PubMed Query, Phase 2 - PubMed returned a non-200 status code.');
+ callback(error);
+ return;
+ }
+
+ let res = JSON.parse(request.responseText);
+
+ // Handle response errors
+ if (res.error) {
+ let error = new Error('Error: PubMed Query, Phase 2 - Unknown response error.');
+ callback(error);
+ return;
+ }
+
+
+ let iterable = [];
+
+ for (let i in (res.result as Object)) {
+ if (i === 'uids') { continue; }
+ iterable.push(res.result[i]);
+ }
+
+ callback(iterable);
+
+ };
+ request.send(null);
+
+}
diff --git a/inc/options-page-wrapper.php b/inc/options-page-wrapper.php
index f4110d5e..9134ce10 100644
--- a/inc/options-page-wrapper.php
+++ b/inc/options-page-wrapper.php
@@ -1,22 +1,12 @@
+
diff --git a/inc/peer-review.php b/inc/peer-review.php
index 2127bbe4..376a6da8 100644
--- a/inc/peer-review.php
+++ b/inc/peer-review.php
@@ -6,7 +6,6 @@ function abt_peer_review_meta() {
add_action( 'add_meta_boxes', 'abt_peer_review_meta' );
-
function abt_peer_review_callback( $post ) {
wp_nonce_field( basename( __file__ ), 'abt_nonce' );
@@ -48,7 +47,6 @@ function abt_peer_review_callback( $post ) {