RefGen v1.2: full file replace (fix parse error) |
No edit summary |
||
| (11 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* ===================================================================== | /* ===================================================================== | ||
* Bharatpedia Gadget: RefGen ( | * Bharatpedia Gadget: RefGen (v2.0) | ||
* Interactive citation generator. Paste a URL, the gadget fetches | * Interactive citation generator. Paste a URL, the gadget fetches | ||
* metadata via a public citation metadata API, auto-detects the | * metadata via a public citation metadata API, auto-detects the | ||
| Line 6: | Line 6: | ||
* <ref>{{cite ...}}</ref> with editable fields. Page number and Quote | * <ref>{{cite ...}}</ref> with editable fields. Page number and Quote | ||
* have dedicated manual inputs. All user-facing text is in English. | * have dedicated manual inputs. All user-facing text is in English. | ||
* | |||
* v2.0: also available inside the editors — | |||
* - source editor (WikiEditor): "Citation generator" toolbar button | |||
* - VisualEditor (+ 2017 wikitext editor): Insert-menu tool | |||
* (via the hidden ext.gadget.RefGenVe plugin) | |||
* ===================================================================== */ | * ===================================================================== */ | ||
( function () { | ( function () { | ||
'use strict'; | 'use strict'; | ||
var TOOL_PAGE = 'Bharatpedia:Citation_generator', | var TOOL_PAGE = 'Bharatpedia:Citation_generator', | ||
CITOID = atob( 'aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL2FwaS9yZXN0X3YxL2RhdGEvY2l0YXRpb24vbWVkaWF3aWtpLw==' ); | CITOID = atob( 'aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL2FwaS9yZXN0X3YxL2RhdGEvY2l0YXRpb24vbWVkaWF3aWtpLw==' ); | ||
var TYPES = { | var TYPES = { | ||
webpage: { template: 'cite web', label: 'Web' }, | webpage: { template: 'cite web', label: 'Web' }, | ||
| Line 23: | Line 30: | ||
report: { template: 'cite report', label: 'Report' } | report: { template: 'cite report', label: 'Report' } | ||
}; | }; | ||
function esc( s ) { | |||
return String( s == null ? '' : s ).replace( /[&<>"']/g, function ( c ) { | |||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ c ]; | |||
} ); | |||
} | |||
function formatAuthors( creators ) { | function formatAuthors( creators ) { | ||
if ( !creators || !creators.length ) return ''; | if ( !creators || !creators.length ) return ''; | ||
| Line 31: | Line 45: | ||
return c.lastName || c.firstName || c.name || ''; | return c.lastName || c.firstName || c.name || ''; | ||
} ).filter( Boolean ).join( '; ' ); | } ).filter( Boolean ).join( '; ' ); | ||
} | |||
// Only reformat full ISO dates (YYYY-MM-DD) and year-month (YYYY-MM); | |||
// years / other partial dates pass through untouched | |||
function fmtPubDate( d ) { | |||
if ( !d ) return ''; | |||
d = d.trim(); | |||
if ( /^\d{4}-\d{2}-\d{2}$/.test( d ) ) { | |||
return fmtAccessDate( d ); | |||
} | |||
var ym = d.match( /^(\d{4})-(\d{2})$/ ); | |||
if ( ym ) { | |||
var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; | |||
var mi = parseInt( ym[ 2 ], 10 ) - 1; | |||
if ( mi >= 0 && mi < 12 ) { | |||
return months[ mi ] + ' ' + ym[ 1 ]; | |||
} | |||
} | |||
return d; | |||
} | } | ||
function fmtAccessDate( iso ) { | function fmtAccessDate( iso ) { | ||
| Line 39: | Line 72: | ||
return d.getUTCDate() + ' ' + months[ d.getUTCMonth() ] + ' ' + d.getUTCFullYear(); | return d.getUTCDate() + ' ' + months[ d.getUTCMonth() ] + ' ' + d.getUTCFullYear(); | ||
} | } | ||
// |website= must never be a raw URL (cite flags "External link in |website="). | |||
// Turn a URL into its bare domain, e.g. https://www.example.com/x -> example.com. | |||
function cleanWebsite( v ) { | |||
v = String( v == null ? '' : v ).trim(); | |||
var m = v.match( /^https?:\/\/([^\/\s?#]+)/i ); | |||
if ( m ) { | |||
return m[ 1 ].toLowerCase().replace( /^www\./, '' ); | |||
} | |||
return v; | |||
} | |||
// Fallback title from the URL slug when the service gives no title | |||
// (or gives the URL itself as the title), so |title= is never empty | |||
// or a raw URL. | |||
function titleFromUrl( url ) { | |||
try { | |||
var u = new URL( url ); | |||
var segs = u.pathname.split( '/' ); | |||
for ( var i = segs.length - 1; i >= 0; i-- ) { | |||
var slug = segs[ i ].replace( /\.(html?|php|aspx?|jsp|cfm|cms)$/i, '' ); | |||
slug = slug.replace( /[-_]\d{4,}$/, '' ); | |||
try { slug = decodeURIComponent( slug ); } catch ( e ) {} | |||
var words = slug.replace( /[_+]+/g, ' ' ).replace( /-+/g, ' ' ).split( ' ' ) | |||
.filter( function ( w ) { return w && !/^\d+$/.test( w ); } ); | |||
if ( words.length >= 2 ) { | |||
return words.map( function ( w ) { | |||
return w.charAt( 0 ).toUpperCase() + w.slice( 1 ); | |||
} ).join( ' ' ); | |||
} | |||
} | |||
} catch ( e ) {} | |||
return ''; | |||
} | |||
// Map Citoid Zotero item to cite template params | |||
function mapToParams( item ) { | function mapToParams( item ) { | ||
var type = TYPES[ item.itemType ] || TYPES.webpage; | var type = TYPES[ item.itemType ] || TYPES.webpage; | ||
var p = { _template: type.template, _label: type.label }; | var p = { _template: type.template, _label: type.label }; | ||
p.url = item.url || ''; | p.url = item.url || ''; | ||
p.title = item. | var rawTitle = item.title || ''; | ||
if ( /^https?:\/\//i.test( rawTitle ) ) { rawTitle = ''; } | |||
p.title = rawTitle || titleFromUrl( item.url ) || ''; | |||
p.author = formatAuthors( item.creators ); | p.author = formatAuthors( item.creators ); | ||
p.date = item.date | p.date = fmtPubDate( item.date ); | ||
p.accessdate = fmtAccessDate( item.accessDate ); | p.accessdate = fmtAccessDate( item.accessDate ); | ||
if ( type.template === 'cite web | if ( type.template === 'cite web' ) { | ||
p.website = item.websiteTitle || item.publicationTitle || ''; | p.website = item.websiteTitle || item.publicationTitle || ''; | ||
} | } | ||
if ( type.template === 'cite book' ) { | if ( type.template === 'cite book' ) { | ||
p.publisher = item.publisher || ''; | p.publisher = item.publisher || ''; | ||
p.isbn = | // ISBN may come back as an array — take the first, buildRef needs a string | ||
var isbn = item.ISBN; | |||
p.isbn = Array.isArray( isbn ) ? ( isbn[ 0 ] || '' ) : ( isbn || '' ); | |||
} | } | ||
if ( type.template === 'cite journal' || type.template === 'cite magazine' ) { | if ( type.template === 'cite journal' || type.template === 'cite magazine' ) { | ||
| Line 63: | Line 135: | ||
if ( type.template === 'cite news' ) { | if ( type.template === 'cite news' ) { | ||
p.newspaper = item.publicationTitle || ''; | p.newspaper = item.publicationTitle || ''; | ||
// Only keep website too when it differs from the newspaper name | |||
if ( item.websiteTitle && item.websiteTitle !== item.publicationTitle ) { | |||
p.website = item.websiteTitle; | |||
} | |||
// Never emit both: |website= conflicts with |newspaper= in cite news | |||
if ( p.newspaper && p.website && p.website.toLowerCase() === p.newspaper.toLowerCase() ) { | |||
p.website = ''; | |||
} | |||
} | } | ||
return p; | return p; | ||
} | } | ||
function fieldDefs( template ) { | function fieldDefs( template ) { | ||
var base = [ | var base = [ | ||
| Line 91: | Line 172: | ||
return base; | return base; | ||
} | } | ||
function buildRef( params, page, quote ) { | function buildRef( params, page, quote ) { | ||
var parts = [ '{{' + params._template ]; | var parts = [ '{{' + params._template ]; | ||
// Guard: |website= is invalid alongside |newspaper= (cite news) — keep newspaper. | |||
var dropWebsite = params._template === 'cite news' && | |||
( params.newspaper || '' ).trim() && ( params.website || '' ).trim(); | |||
var order = [ 'url', 'title', 'author', 'date', 'website', 'newspaper', 'journal', 'publisher', 'volume', 'issue', 'pages', 'isbn', 'doi', 'accessdate' ]; | var order = [ 'url', 'title', 'author', 'date', 'website', 'newspaper', 'journal', 'publisher', 'volume', 'issue', 'pages', 'isbn', 'doi', 'accessdate' ]; | ||
order.forEach( function ( k ) { | order.forEach( function ( k ) { | ||
var v = ( params[ k ] | if ( k === 'website' && dropWebsite ) return; | ||
var v = String( params[ k ] == null ? '' : params[ k ] ).trim(); | |||
if ( !v ) return; | if ( !v ) return; | ||
if ( k === 'website' ) { | |||
v = cleanWebsite( v ); | |||
if ( !v ) return; | |||
} | |||
if ( k === 'title' && /^https?:\/\//i.test( v ) ) { | |||
return; // never emit a raw URL as |title= | |||
} | |||
v = v.replace( /\|/g, '{{!}}' ); // unescaped pipes break template params | |||
var ck = k === 'accessdate' ? 'access-date' : k; | var ck = k === 'accessdate' ? 'access-date' : k; | ||
parts.push( ' |' + ck + '=' + v ); | parts.push( ' |' + ck + '=' + v ); | ||
} ); | } ); | ||
if ( page.trim() ) parts.push( ' |page=' + page.trim() ); | if ( page.trim() ) parts.push( ' |page=' + page.trim().replace( /\|/g, '{{!}}' ) ); | ||
if ( quote.trim() ) parts.push( ' |quote=' + quote.trim() ); | if ( quote.trim() ) parts.push( ' |quote=' + quote.trim().replace( /\|/g, '{{!}}' ) ); | ||
parts.push( '}}' ); | parts.push( '}}' ); | ||
return '<ref>' + parts.join( '' ) + '</ref>'; | return '<ref>' + parts.join( '' ) + '</ref>'; | ||
} | } | ||
function buildTool( autoUrl ) { | function buildTool( autoUrl ) { | ||
var $root = $( '<div>' ).addClass( 'bp-refgen' ).css( { | var $root = $( '<div>' ).addClass( 'bp-refgen' ).css( { | ||
| Line 110: | Line 205: | ||
margin: '0 0 18px 0', background: '#f8f9fa', 'box-sizing': 'border-box' | margin: '0 0 18px 0', background: '#f8f9fa', 'box-sizing': 'border-box' | ||
} ); | } ); | ||
$root.append( $( '<h2>' ).text( 'Citation generator' ).css( { margin: '0 0 4px 0', 'font-size': '22px' } ) ); | $root.append( $( '<h2>' ).text( 'Citation generator' ).css( { margin: '0 0 4px 0', 'font-size': '22px' } ) ); | ||
$root.append( $( '<p>' ).text( 'Paste a URL to auto-generate a citation. Add page number and quote manually if needed.' ).css( { margin: '0 0 12px 0', 'font-size': '13px', color: '#54595d' } ) ); | $root.append( $( '<p>' ).text( 'Paste a URL to auto-generate a citation. Add page number and quote manually if needed.' ) | ||
.css( { margin: '0 0 12px 0', 'font-size': '13px', color: '#54595d' } ) ); | |||
// URL input row | |||
var $urlRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '12px' } ); | var $urlRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '12px' } ); | ||
var $urlInput = $( '<input>' ).attr( { type: 'url', placeholder: 'Paste a URL (e.g. https://www.bbc.com/news/...)' } ).css( { flex: '1', padding: '10px 12px', 'font-size': '15px', border: '2px solid #36c', 'border-radius': '6px', 'box-sizing': 'border-box' } ); | var $urlInput = $( '<input>' ).attr( { type: 'url', placeholder: 'Paste a URL (e.g. https://www.bbc.com/news/...)' } ).css( { | ||
var $fetchBtn = $( '<button>' ).text( 'Fetch' ).css( { padding: '10px 24px', 'font-size': '15px', cursor: 'pointer', background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold' } ); | flex: '1', padding: '10px 12px', 'font-size': '15px', | ||
border: '2px solid #36c', 'border-radius': '6px', 'box-sizing': 'border-box' | |||
} ); | |||
var $fetchBtn = $( '<button>' ).text( 'Fetch' ).css( { | |||
padding: '10px 24px', 'font-size': '15px', cursor: 'pointer', | |||
background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold' | |||
} ); | |||
$urlRow.append( $urlInput, $fetchBtn ); | $urlRow.append( $urlInput, $fetchBtn ); | ||
$root.append( $urlRow ); | $root.append( $urlRow ); | ||
var $msg = $( '<div>' ).css( { 'font-size': '13px', color: '#d33', 'margin-bottom': '8px' } ).hide(); | var $msg = $( '<div>' ).css( { 'font-size': '13px', color: '#d33', 'margin-bottom': '8px' } ).hide(); | ||
$root.append( $msg ); | $root.append( $msg ); | ||
// Type selector | |||
var $typeRow = $( '<div>' ).css( { 'margin-bottom': '12px', 'font-size': '14px' } ).hide(); | var $typeRow = $( '<div>' ).css( { 'margin-bottom': '12px', 'font-size': '14px' } ).hide(); | ||
$typeRow.append( $( '<label>' ).text( 'Source type: ' ).css( { 'font-weight': 'bold', 'margin-right': '8px' } ) ); | $typeRow.append( $( '<label>' ).text( 'Source type: ' ).css( { 'font-weight': 'bold', 'margin-right': '8px' } ) ); | ||
var $typeSel = $( '<select>' ).css( { padding: '6px 10px', 'font-size': '14px', 'border-radius': '4px', border: '1px solid #a2a9b1' } ); | var $typeSel = $( '<select>' ).css( { padding: '6px 10px', 'font-size': '14px', 'border-radius': '4px', border: '1px solid #a2a9b1' } ); | ||
[ 'cite web', 'cite book', 'cite journal', 'cite news', 'cite magazine' ].forEach( function ( t ) { | [ 'cite web', 'cite book', 'cite journal', 'cite news', 'cite magazine', 'cite encyclopedia', 'cite thesis', 'cite report' ].forEach( function ( t ) { | ||
$typeSel.append( $( '<option>' ).attr( 'value', t ).text( '{{' + t + '}}' ) ); | $typeSel.append( $( '<option>' ).attr( 'value', t ).text( '{{' + t + '}}' ) ); | ||
} ); | } ); | ||
$typeRow.append( $typeSel ); | $typeRow.append( $typeSel ); | ||
$root.append( $typeRow ); | $root.append( $typeRow ); | ||
// Fields container | |||
var $fields = $( '<div>' ).css( { 'margin-bottom': '12px' } ).hide(); | var $fields = $( '<div>' ).css( { 'margin-bottom': '12px' } ).hide(); | ||
$root.append( $fields ); | $root.append( $fields ); | ||
// Manual Page and Quote (always visible, prominent) | |||
var $manual = $( '<div>' ).css( { 'margin-bottom': '12px', padding: '12px', background: '#fff', border: '1px solid #c8ccd1', 'border-radius': '6px' } ).hide(); | var $manual = $( '<div>' ).css( { 'margin-bottom': '12px', padding: '12px', background: '#fff', border: '1px solid #c8ccd1', 'border-radius': '6px' } ).hide(); | ||
$manual.append( $( '<div>' ).text( 'Add manually (optional)' ).css( { 'font-weight': 'bold', 'margin-bottom': '8px', 'font-size': '14px' } ) ); | $manual.append( $( '<div>' ).text( 'Add manually (optional)' ).css( { 'font-weight': 'bold', 'margin-bottom': '8px', 'font-size': '14px' } ) ); | ||
| Line 141: | Line 253: | ||
$manual.append( $pageRow, $quoteRow ); | $manual.append( $pageRow, $quoteRow ); | ||
$root.append( $manual ); | $root.append( $manual ); | ||
// Output | |||
var $outWrap = $( '<div>' ).hide(); | var $outWrap = $( '<div>' ).hide(); | ||
$outWrap.append( $( '<div>' ).text( 'Generated reference:' ).css( { 'font-weight': 'bold', 'margin-bottom': '6px', 'font-size': '14px' } ) ); | $outWrap.append( $( '<div>' ).text( 'Generated reference:' ).css( { 'font-weight': 'bold', 'margin-bottom': '6px', 'font-size': '14px' } ) ); | ||
var $output = $( '<textarea>' ).attr( { rows: 4, readonly: true } ).css( { width: '100%', padding: '10px', 'font-size': '13px', 'font-family': 'monospace', border: '1px solid #a2a9b1', 'border-radius': '4px', 'box-sizing': 'border-box', resize: 'vertical', background: '#fff' } ); | var $output = $( '<textarea>' ).attr( { rows: 4, readonly: true } ).css( { | ||
var $copyBtn = $( '<button>' ).text( 'Copy reference' ).css( { 'margin-top': '8px', padding: '8px 20px', 'font-size': '14px', cursor: 'pointer', background: '#14866d', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold' } ); | width: '100%', padding: '10px', 'font-size': '13px', 'font-family': 'monospace', | ||
border: '1px solid #a2a9b1', 'border-radius': '4px', 'box-sizing': 'border-box', resize: 'vertical', background: '#fff' | |||
} ); | |||
var $copyBtn = $( '<button>' ).text( 'Copy reference' ).css( { | |||
'margin-top': '8px', padding: '8px 20px', 'font-size': '14px', cursor: 'pointer', | |||
background: '#14866d', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold' | |||
} ); | |||
$outWrap.append( $output, $copyBtn ); | $outWrap.append( $output, $copyBtn ); | ||
$root.append( $outWrap ); | $root.append( $outWrap ); | ||
var params = {}; | var params = {}; | ||
var fieldInputs = {}; | var fieldInputs = {}; | ||
function refreshOutput() { | function refreshOutput() { | ||
var page = $pageInput.val() || ''; | var page = $pageInput.val() || ''; | ||
var quote = $quoteInput.val() || ''; | var quote = $quoteInput.val() || ''; | ||
Object.keys( fieldInputs ).forEach( function ( k ) { params[ k ] = fieldInputs[ k ].val(); } ); | // read current field values | ||
Object.keys( fieldInputs ).forEach( function ( k ) { | |||
params[ k ] = fieldInputs[ k ].val(); | |||
} ); | |||
params._template = $typeSel.val(); | params._template = $typeSel.val(); | ||
$output.val( buildRef( params, page, quote ) ); | $output.val( buildRef( params, page, quote ) ); | ||
} | } | ||
function renderFields() { | function renderFields() { | ||
$fields.empty(); | $fields.empty(); | ||
| Line 162: | Line 288: | ||
var $row = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '6px', 'align-items': 'center' } ); | var $row = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '6px', 'align-items': 'center' } ); | ||
$row.append( $( '<label>' ).text( fd.label + ':' ).css( { width: '110px', 'font-size': '14px' } ) ); | $row.append( $( '<label>' ).text( fd.label + ':' ).css( { width: '110px', 'font-size': '14px' } ) ); | ||
var $inp = $( '<input>' ).attr( 'type', 'text' ).val( params[ fd.key ] || '' ).css( { flex: '1', padding: '8px 10px', 'font-size': '14px', border: '1px solid #a2a9b1', 'border-radius': '4px' } ); | var $inp = $( '<input>' ).attr( 'type', 'text' ).val( params[ fd.key ] || '' ).css( { | ||
flex: '1', padding: '8px 10px', 'font-size': '14px', border: '1px solid #a2a9b1', 'border-radius': '4px' | |||
} ); | |||
$inp.on( 'input', refreshOutput ); | $inp.on( 'input', refreshOutput ); | ||
fieldInputs[ fd.key ] = $inp; | fieldInputs[ fd.key ] = $inp; | ||
| Line 170: | Line 298: | ||
$fields.show(); | $fields.show(); | ||
} | } | ||
$typeSel.on( 'change', function () { params._template = $typeSel.val(); renderFields(); refreshOutput(); } ); | |||
$typeSel.on( 'change', function () { | |||
params._template = $typeSel.val(); | |||
renderFields(); | |||
refreshOutput(); | |||
} ); | |||
$pageInput.on( 'input', refreshOutput ); | $pageInput.on( 'input', refreshOutput ); | ||
$quoteInput.on( 'input', refreshOutput ); | $quoteInput.on( 'input', refreshOutput ); | ||
$copyBtn.on( 'click', function () { | $copyBtn.on( 'click', function () { | ||
$output[ 0 ].select(); | $output[ 0 ].select(); | ||
try { var ok = document.execCommand( 'copy' ); $copyBtn.text( ok ? 'Copied!' : 'Copy failed' ); | try { | ||
catch ( e ) { $copyBtn.text( 'Copy failed' ); } | var ok = document.execCommand( 'copy' ); | ||
$copyBtn.text( ok ? 'Copied!' : 'Copy failed' ); | |||
} catch ( e ) { | |||
$copyBtn.text( 'Copy failed' ); | |||
} | |||
setTimeout( function () { $copyBtn.text( 'Copy reference' ); }, 2000 ); | setTimeout( function () { $copyBtn.text( 'Copy reference' ); }, 2000 ); | ||
} ); | } ); | ||
function fetchUrl() { | function fetchUrl() { | ||
var url = $.trim( $urlInput.val() ); | var url = $.trim( $urlInput.val() ); | ||
| Line 185: | Line 324: | ||
$msg.hide(); | $msg.hide(); | ||
$fetchBtn.prop( 'disabled', true ).text( 'Loading…' ); | $fetchBtn.prop( 'disabled', true ).text( 'Loading…' ); | ||
fetch( CITOID + encodeURIComponent( url ), { headers: { 'Api-User-Agent': 'Bharatpedia-RefGen/1.0' } } ).then( function ( r ) { | fetch( CITOID + encodeURIComponent( url ), { | ||
headers: { 'Api-User-Agent': 'Bharatpedia-RefGen/1.0' } | |||
} ).then( function ( r ) { | |||
if ( !r.ok ) throw new Error( 'Could not fetch data for this URL.' ); | if ( !r.ok ) throw new Error( 'Could not fetch data for this URL.' ); | ||
return r.json(); | return r.json(); | ||
} ).then( function ( data ) { | } ).then( function ( data ) { | ||
if ( !data || !data.length || data.error ) throw new Error( data && data.error ? data.error : 'No citation data found for this URL.' ); | if ( !data || !data.length || data.error ) { | ||
throw new Error( data && data.error ? data.error : 'No citation data found for this URL.' ); | |||
} | |||
params = mapToParams( data[ 0 ] ); | params = mapToParams( data[ 0 ] ); | ||
$typeSel.val( params._template ); | $typeSel.val( params._template ); | ||
$typeRow.show(); renderFields(); $manual.show(); $outWrap.show(); refreshOutput(); | $typeRow.show(); | ||
} ).catch( function ( err ) { $msg.text( ( err && err.message ) || 'Something went wrong.' ).show(); } ).finally( function () { $fetchBtn.prop( 'disabled', false ).text( 'Fetch' ); } ); | renderFields(); | ||
$manual.show(); | |||
$outWrap.show(); | |||
refreshOutput(); | |||
} ).catch( function ( err ) { | |||
$msg.text( ( err && err.message ) || 'Something went wrong.' ).show(); | |||
} ).finally( function () { | |||
$fetchBtn.prop( 'disabled', false ).text( 'Fetch' ); | |||
} ); | |||
} | } | ||
$fetchBtn.on( 'click', fetchUrl ); | $fetchBtn.on( 'click', fetchUrl ); | ||
$urlInput.on( 'keydown', function ( e ) { if ( e.key === 'Enter' ) fetchUrl(); } ); | $urlInput.on( 'keydown', function ( e ) { if ( e.key === 'Enter' ) fetchUrl(); } ); | ||
if ( autoUrl ) { $urlInput.val( autoUrl ); fetchUrl(); } | |||
if ( autoUrl ) { | |||
$urlInput.val( autoUrl ); | |||
fetchUrl(); | |||
} | |||
return $root; | return $root; | ||
} | } | ||
function init() { | function init() { | ||
try { | try { | ||
| Line 206: | Line 364: | ||
var $static = $( '#bp-rg-static' ); | var $static = $( '#bp-rg-static' ); | ||
var $tool = buildTool(); | var $tool = buildTool(); | ||
if ( $static.length ) { $static.replaceWith( $tool ); | if ( $static.length ) { | ||
else { var $content = $( '#mw-content-text' ); if ( $content.length ) $content.prepend( $tool ); } | $static.replaceWith( $tool ); | ||
} else { | |||
var $content = $( '#mw-content-text' ); | |||
if ( $content.length ) $content.prepend( $tool ); | |||
} | |||
} | } | ||
var $link = mw.util.addPortletLink( 'p-tb', '#', 'Citation generator', 't-refgen', 'Generate a citation from a URL' ); | var $link = mw.util.addPortletLink( 'p-tb', '#', 'Citation generator', 't-refgen', 'Generate a citation from a URL' ); | ||
if ( $link ) { $( $link ).on( 'click', function ( e ) { e.preventDefault(); window.open( mw.util.getUrl( TOOL_PAGE ), '_blank' ); } ); } | if ( $link ) { | ||
} catch ( e ) { if ( window.console && console.error ) { console.error( 'RefGen init failed: ' + ( e && e.message ) ); } } | $( $link ).on( 'click', function ( e ) { | ||
e.preventDefault(); | |||
window.open( mw.util.getUrl( TOOL_PAGE ), '_blank' ); | |||
} ); | |||
} | |||
} catch ( e ) { | |||
if ( window.console && console.error ) { | |||
console.error( 'RefGen init failed: ' + ( e && e.message ) ); | |||
} | |||
} | |||
} | } | ||
function boot() { | function boot() { | ||
try { | try { | ||
if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', init ); | if ( document.readyState === 'loading' ) { | ||
else { init(); } | document.addEventListener( 'DOMContentLoaded', init ); | ||
} catch ( e ) { if ( window.console && console.error ) { console.error( 'RefGen bootstrap failed: ' + ( e && e.message ) ); } } | } else { | ||
init(); | |||
} | |||
} catch ( e ) { | |||
if ( window.console && console.error ) { | |||
console.error( 'RefGen bootstrap failed: ' + ( e && e.message ) ); | |||
} | |||
} | |||
} | } | ||
// Wait for jQuery and mediawiki.util before running. | |||
// Polls every 100ms (up to 10s) because ResourceLoader may execute | |||
// this gadget before its dependencies in batched mode. | |||
function waitForDeps( callback, attempts ) { | function waitForDeps( callback, attempts ) { | ||
attempts = attempts || 0; | attempts = attempts || 0; | ||
try { | try { | ||
if ( typeof $ !== 'undefined' && typeof mw !== 'undefined' && mw.util ) { callback(); return; } | if ( typeof $ !== 'undefined' && typeof mw !== 'undefined' && mw.util ) { | ||
callback(); | |||
return; | |||
} | |||
} catch ( e ) {} | } catch ( e ) {} | ||
if ( attempts < 100 ) { setTimeout( function () { waitForDeps( callback, attempts + 1 ); }, 100 ); | if ( attempts < 100 ) { | ||
else { try { callback(); } catch ( e ) {} } | setTimeout( function () { waitForDeps( callback, attempts + 1 ); }, 100 ); | ||
} else { | |||
// Give up waiting; try anyway (will fail gracefully via try/catch) | |||
try { callback(); } catch ( e ) {} | |||
} | |||
} | } | ||
waitForDeps( boot ); | waitForDeps( boot ); | ||
/* ===================================================================== | |||
* Shared library for the editor integrations. | |||
* The VisualEditor plugin (ext.gadget.RefGenVe) uses mw.libs.refGen. | |||
* ===================================================================== */ | |||
mw.libs.refGen = mw.libs.refGen || {}; | |||
mw.libs.refGen.TYPES = TYPES; | |||
mw.libs.refGen.mapToParams = mapToParams; | |||
mw.libs.refGen.buildRef = buildRef; | |||
mw.libs.refGen.fetchCitation = function ( url ) { | |||
url = $.trim( url || '' ); | |||
var d = $.Deferred(); | |||
if ( !url ) { | |||
d.reject( 'Please enter a URL.' ); | |||
return d.promise(); | |||
} | |||
if ( !/^https?:\/\//i.test( url ) ) { | |||
url = 'https://' + url; | |||
} | |||
$.ajax( { | |||
url: CITOID + encodeURIComponent( url ), | |||
headers: { 'Api-User-Agent': 'Bharatpedia-RefGen/1.0' }, | |||
dataType: 'json' | |||
} ).done( function ( data ) { | |||
if ( !data || !data.length || data.error ) { | |||
d.reject( ( data && data.error ) || 'No citation data found for this URL.' ); | |||
} else { | |||
d.resolve( data[ 0 ] ); | |||
} | |||
} ).fail( function ( jqXHR ) { | |||
var status = jqXHR && jqXHR.status; | |||
if ( status === 404 ) { | |||
d.reject( 'No citation data (page not found - the link may be dead).' ); | |||
} else { | |||
d.reject( 'Could not fetch data for this URL.' ); | |||
} | |||
} ); | |||
return d.promise(); | |||
}; | |||
/* ===================================================================== | |||
* Source editor (WikiEditor) integration: toolbar button + dialog. | |||
* ===================================================================== */ | |||
function refGenInsertAtCursor( $textarea, text ) { | |||
try { | |||
$textarea.textSelection( 'encapsulateSelection', { pre: '', peri: text, post: '' } ); | |||
return; | |||
} catch ( e ) {} | |||
// Fallback: manual insertion | |||
var el = $textarea[ 0 ]; | |||
if ( el && typeof el.selectionStart === 'number' ) { | |||
var s = el.selectionStart, e2 = el.selectionEnd, v = el.value; | |||
el.value = v.slice( 0, s ) + text + v.slice( e2 ); | |||
try { | |||
el.selectionStart = el.selectionEnd = s + text.length; | |||
} catch ( e3 ) {} | |||
el.focus(); | |||
} | |||
} | |||
function openRefGenEditorDialog( $textarea ) { | |||
var $tool = buildTool(); | |||
var $overlay = $( '<div>' ).css( { | |||
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, | |||
background: 'rgba(0,0,0,0.55)', 'z-index': 2000, | |||
display: 'flex', 'align-items': 'center', 'justify-content': 'center', | |||
padding: '16px', 'box-sizing': 'border-box' | |||
} ); | |||
var $box = $( '<div>' ).css( { | |||
background: '#fff', 'border-radius': '10px', padding: '18px', | |||
width: '640px', 'max-width': '100%', 'max-height': '92vh', | |||
overflow: 'auto', 'box-sizing': 'border-box', position: 'relative' | |||
} ); | |||
var $close = $( '<button>' ).attr( { type: 'button', 'aria-label': 'Close' } ).text( '×' ).css( { | |||
position: 'absolute', top: '8px', right: '12px', border: 'none', | |||
background: 'transparent', 'font-size': '24px', cursor: 'pointer', color: '#54595d' | |||
} ); | |||
var $insertBtn = $( '<button>' ).attr( 'type', 'button' ).text( 'Insert citation' ).css( { | |||
'margin-top': '12px', padding: '10px 24px', 'font-size': '15px', cursor: 'pointer', | |||
background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold' | |||
} ); | |||
$insertBtn.on( 'click', function () { | |||
var ref = $tool.find( 'textarea[readonly]' ).val(); | |||
if ( !ref ) { | |||
return; | |||
} | |||
refGenInsertAtCursor( $textarea, ref ); | |||
$overlay.remove(); | |||
} ); | |||
$close.on( 'click', function () { $overlay.remove(); } ); | |||
$overlay.on( 'click', function ( e ) { | |||
if ( e.target === $overlay[ 0 ] ) { | |||
$overlay.remove(); | |||
} | |||
} ); | |||
$box.append( $close, $tool, $insertBtn ); | |||
$overlay.append( $box ); | |||
$( document.body ).append( $overlay ); | |||
} | |||
function initWikiEditorButton() { | |||
if ( mw.config.get( 'wgAction' ) !== 'edit' ) { | |||
return; | |||
} | |||
mw.hook( 'wikiEditor.toolbarReady' ).add( function ( $textarea ) { | |||
$textarea.wikiEditor( 'addToToolbar', { | |||
section: 'main', | |||
group: 'insert', | |||
tools: { | |||
refgen: { | |||
label: 'Ref-Gen', | |||
type: 'button', | |||
oouiIcon: 'code', | |||
filters: [ 'body.ns-subject' ], | |||
action: { | |||
type: 'callback', | |||
execute: function () { | |||
openRefGenEditorDialog( $textarea ); | |||
} | |||
} | |||
} | |||
} | |||
} ); | |||
} ); | |||
} | |||
initWikiEditorButton(); | |||
}() ); | }() ); | ||
Latest revision as of 12:55, 19 October 2026
/* =====================================================================
* Bharatpedia Gadget: RefGen (v2.0)
* Interactive citation generator. Paste a URL, the gadget fetches
* metadata via a public citation metadata API, auto-detects the
* source type (web/book/journal/news), and builds a proper
* <ref>{{cite ...}}</ref> with editable fields. Page number and Quote
* have dedicated manual inputs. All user-facing text is in English.
*
* v2.0: also available inside the editors —
* - source editor (WikiEditor): "Citation generator" toolbar button
* - VisualEditor (+ 2017 wikitext editor): Insert-menu tool
* (via the hidden ext.gadget.RefGenVe plugin)
* ===================================================================== */
( function () {
'use strict';
var TOOL_PAGE = 'Bharatpedia:Citation_generator',
CITOID = atob( 'aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL2FwaS9yZXN0X3YxL2RhdGEvY2l0YXRpb24vbWVkaWF3aWtpLw==' );
var TYPES = {
webpage: { template: 'cite web', label: 'Web' },
blogPost: { template: 'cite web', label: 'Web' },
book: { template: 'cite book', label: 'Book' },
bookSection: { template: 'cite book', label: 'Book' },
journalArticle: { template: 'cite journal', label: 'Journal' },
magazineArticle: { template: 'cite magazine', label: 'Magazine' },
newspaperArticle: { template: 'cite news', label: 'News' },
encyclopediaArticle: { template: 'cite encyclopedia', label: 'Encyclopedia' },
thesis: { template: 'cite thesis', label: 'Thesis' },
report: { template: 'cite report', label: 'Report' }
};
function esc( s ) {
return String( s == null ? '' : s ).replace( /[&<>"']/g, function ( c ) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ c ];
} );
}
function formatAuthors( creators ) {
if ( !creators || !creators.length ) return '';
return creators.filter( function ( c ) {
return !c.creatorType || c.creatorType === 'author';
} ).map( function ( c ) {
if ( c.firstName && c.lastName ) return c.firstName + ' ' + c.lastName;
return c.lastName || c.firstName || c.name || '';
} ).filter( Boolean ).join( '; ' );
}
// Only reformat full ISO dates (YYYY-MM-DD) and year-month (YYYY-MM);
// years / other partial dates pass through untouched
function fmtPubDate( d ) {
if ( !d ) return '';
d = d.trim();
if ( /^\d{4}-\d{2}-\d{2}$/.test( d ) ) {
return fmtAccessDate( d );
}
var ym = d.match( /^(\d{4})-(\d{2})$/ );
if ( ym ) {
var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
var mi = parseInt( ym[ 2 ], 10 ) - 1;
if ( mi >= 0 && mi < 12 ) {
return months[ mi ] + ' ' + ym[ 1 ];
}
}
return d;
}
function fmtAccessDate( iso ) {
if ( !iso ) return '';
var d = new Date( iso.length === 10 ? iso + 'T00:00:00Z' : iso );
if ( isNaN( d.getTime() ) ) return '';
var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
return d.getUTCDate() + ' ' + months[ d.getUTCMonth() ] + ' ' + d.getUTCFullYear();
}
// |website= must never be a raw URL (cite flags "External link in |website=").
// Turn a URL into its bare domain, e.g. https://www.example.com/x -> example.com.
function cleanWebsite( v ) {
v = String( v == null ? '' : v ).trim();
var m = v.match( /^https?:\/\/([^\/\s?#]+)/i );
if ( m ) {
return m[ 1 ].toLowerCase().replace( /^www\./, '' );
}
return v;
}
// Fallback title from the URL slug when the service gives no title
// (or gives the URL itself as the title), so |title= is never empty
// or a raw URL.
function titleFromUrl( url ) {
try {
var u = new URL( url );
var segs = u.pathname.split( '/' );
for ( var i = segs.length - 1; i >= 0; i-- ) {
var slug = segs[ i ].replace( /\.(html?|php|aspx?|jsp|cfm|cms)$/i, '' );
slug = slug.replace( /[-_]\d{4,}$/, '' );
try { slug = decodeURIComponent( slug ); } catch ( e ) {}
var words = slug.replace( /[_+]+/g, ' ' ).replace( /-+/g, ' ' ).split( ' ' )
.filter( function ( w ) { return w && !/^\d+$/.test( w ); } );
if ( words.length >= 2 ) {
return words.map( function ( w ) {
return w.charAt( 0 ).toUpperCase() + w.slice( 1 );
} ).join( ' ' );
}
}
} catch ( e ) {}
return '';
}
// Map Citoid Zotero item to cite template params
function mapToParams( item ) {
var type = TYPES[ item.itemType ] || TYPES.webpage;
var p = { _template: type.template, _label: type.label };
p.url = item.url || '';
var rawTitle = item.title || '';
if ( /^https?:\/\//i.test( rawTitle ) ) { rawTitle = ''; }
p.title = rawTitle || titleFromUrl( item.url ) || '';
p.author = formatAuthors( item.creators );
p.date = fmtPubDate( item.date );
p.accessdate = fmtAccessDate( item.accessDate );
if ( type.template === 'cite web' ) {
p.website = item.websiteTitle || item.publicationTitle || '';
}
if ( type.template === 'cite book' ) {
p.publisher = item.publisher || '';
// ISBN may come back as an array — take the first, buildRef needs a string
var isbn = item.ISBN;
p.isbn = Array.isArray( isbn ) ? ( isbn[ 0 ] || '' ) : ( isbn || '' );
}
if ( type.template === 'cite journal' || type.template === 'cite magazine' ) {
p.journal = item.publicationTitle || '';
p.volume = item.volume || '';
p.issue = item.issue || '';
p.pages = item.pages || '';
p.doi = item.DOI || '';
}
if ( type.template === 'cite news' ) {
p.newspaper = item.publicationTitle || '';
// Only keep website too when it differs from the newspaper name
if ( item.websiteTitle && item.websiteTitle !== item.publicationTitle ) {
p.website = item.websiteTitle;
}
// Never emit both: |website= conflicts with |newspaper= in cite news
if ( p.newspaper && p.website && p.website.toLowerCase() === p.newspaper.toLowerCase() ) {
p.website = '';
}
}
return p;
}
function fieldDefs( template ) {
var base = [
{ key: 'title', label: 'Title' },
{ key: 'author', label: 'Author(s)' },
{ key: 'date', label: 'Date' },
{ key: 'url', label: 'URL' }
];
if ( template === 'cite web' ) {
base.splice( 3, 0, { key: 'website', label: 'Website' } );
base.push( { key: 'accessdate', label: 'Access date' } );
} else if ( template === 'cite book' ) {
base.push( { key: 'publisher', label: 'Publisher' } );
base.push( { key: 'isbn', label: 'ISBN' } );
} else if ( template === 'cite journal' || template === 'cite magazine' ) {
base.splice( 3, 0, { key: 'journal', label: 'Journal' } );
base.push( { key: 'volume', label: 'Volume' } );
base.push( { key: 'issue', label: 'Issue' } );
base.push( { key: 'pages', label: 'Pages' } );
base.push( { key: 'doi', label: 'DOI' } );
} else if ( template === 'cite news' ) {
base.splice( 3, 0, { key: 'newspaper', label: 'Newspaper' } );
base.push( { key: 'accessdate', label: 'Access date' } );
}
return base;
}
function buildRef( params, page, quote ) {
var parts = [ '{{' + params._template ];
// Guard: |website= is invalid alongside |newspaper= (cite news) — keep newspaper.
var dropWebsite = params._template === 'cite news' &&
( params.newspaper || '' ).trim() && ( params.website || '' ).trim();
var order = [ 'url', 'title', 'author', 'date', 'website', 'newspaper', 'journal', 'publisher', 'volume', 'issue', 'pages', 'isbn', 'doi', 'accessdate' ];
order.forEach( function ( k ) {
if ( k === 'website' && dropWebsite ) return;
var v = String( params[ k ] == null ? '' : params[ k ] ).trim();
if ( !v ) return;
if ( k === 'website' ) {
v = cleanWebsite( v );
if ( !v ) return;
}
if ( k === 'title' && /^https?:\/\//i.test( v ) ) {
return; // never emit a raw URL as |title=
}
v = v.replace( /\|/g, '{{!}}' ); // unescaped pipes break template params
var ck = k === 'accessdate' ? 'access-date' : k;
parts.push( ' |' + ck + '=' + v );
} );
if ( page.trim() ) parts.push( ' |page=' + page.trim().replace( /\|/g, '{{!}}' ) );
if ( quote.trim() ) parts.push( ' |quote=' + quote.trim().replace( /\|/g, '{{!}}' ) );
parts.push( '}}' );
return '<ref>' + parts.join( '' ) + '</ref>';
}
function buildTool( autoUrl ) {
var $root = $( '<div>' ).addClass( 'bp-refgen' ).css( {
border: '2px solid #36c', 'border-radius': '10px', padding: '18px',
margin: '0 0 18px 0', background: '#f8f9fa', 'box-sizing': 'border-box'
} );
$root.append( $( '<h2>' ).text( 'Citation generator' ).css( { margin: '0 0 4px 0', 'font-size': '22px' } ) );
$root.append( $( '<p>' ).text( 'Paste a URL to auto-generate a citation. Add page number and quote manually if needed.' )
.css( { margin: '0 0 12px 0', 'font-size': '13px', color: '#54595d' } ) );
// URL input row
var $urlRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '12px' } );
var $urlInput = $( '<input>' ).attr( { type: 'url', placeholder: 'Paste a URL (e.g. https://www.bbc.com/news/...)' } ).css( {
flex: '1', padding: '10px 12px', 'font-size': '15px',
border: '2px solid #36c', 'border-radius': '6px', 'box-sizing': 'border-box'
} );
var $fetchBtn = $( '<button>' ).text( 'Fetch' ).css( {
padding: '10px 24px', 'font-size': '15px', cursor: 'pointer',
background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold'
} );
$urlRow.append( $urlInput, $fetchBtn );
$root.append( $urlRow );
var $msg = $( '<div>' ).css( { 'font-size': '13px', color: '#d33', 'margin-bottom': '8px' } ).hide();
$root.append( $msg );
// Type selector
var $typeRow = $( '<div>' ).css( { 'margin-bottom': '12px', 'font-size': '14px' } ).hide();
$typeRow.append( $( '<label>' ).text( 'Source type: ' ).css( { 'font-weight': 'bold', 'margin-right': '8px' } ) );
var $typeSel = $( '<select>' ).css( { padding: '6px 10px', 'font-size': '14px', 'border-radius': '4px', border: '1px solid #a2a9b1' } );
[ 'cite web', 'cite book', 'cite journal', 'cite news', 'cite magazine', 'cite encyclopedia', 'cite thesis', 'cite report' ].forEach( function ( t ) {
$typeSel.append( $( '<option>' ).attr( 'value', t ).text( '{{' + t + '}}' ) );
} );
$typeRow.append( $typeSel );
$root.append( $typeRow );
// Fields container
var $fields = $( '<div>' ).css( { 'margin-bottom': '12px' } ).hide();
$root.append( $fields );
// Manual Page and Quote (always visible, prominent)
var $manual = $( '<div>' ).css( { 'margin-bottom': '12px', padding: '12px', background: '#fff', border: '1px solid #c8ccd1', 'border-radius': '6px' } ).hide();
$manual.append( $( '<div>' ).text( 'Add manually (optional)' ).css( { 'font-weight': 'bold', 'margin-bottom': '8px', 'font-size': '14px' } ) );
var $pageRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '8px', 'align-items': 'center' } );
$pageRow.append( $( '<label>' ).text( 'Page(s):' ).css( { width: '80px', 'font-size': '14px' } ) );
var $pageInput = $( '<input>' ).attr( { type: 'text', placeholder: 'e.g. 42 or 42-45' } ).css( { flex: '1', padding: '8px 10px', 'font-size': '14px', border: '1px solid #a2a9b1', 'border-radius': '4px' } );
$pageRow.append( $pageInput );
var $quoteRow = $( '<div>' ).css( { display: 'flex', gap: '8px', 'align-items': 'flex-start' } );
$quoteRow.append( $( '<label>' ).text( 'Quote:' ).css( { width: '80px', 'font-size': '14px', 'padding-top': '8px' } ) );
var $quoteInput = $( '<textarea>' ).attr( { rows: 2, placeholder: 'Exact quote from the source (optional)' } ).css( { flex: '1', padding: '8px 10px', 'font-size': '14px', border: '1px solid #a2a9b1', 'border-radius': '4px', resize: 'vertical' } );
$quoteRow.append( $quoteInput );
$manual.append( $pageRow, $quoteRow );
$root.append( $manual );
// Output
var $outWrap = $( '<div>' ).hide();
$outWrap.append( $( '<div>' ).text( 'Generated reference:' ).css( { 'font-weight': 'bold', 'margin-bottom': '6px', 'font-size': '14px' } ) );
var $output = $( '<textarea>' ).attr( { rows: 4, readonly: true } ).css( {
width: '100%', padding: '10px', 'font-size': '13px', 'font-family': 'monospace',
border: '1px solid #a2a9b1', 'border-radius': '4px', 'box-sizing': 'border-box', resize: 'vertical', background: '#fff'
} );
var $copyBtn = $( '<button>' ).text( 'Copy reference' ).css( {
'margin-top': '8px', padding: '8px 20px', 'font-size': '14px', cursor: 'pointer',
background: '#14866d', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold'
} );
$outWrap.append( $output, $copyBtn );
$root.append( $outWrap );
var params = {};
var fieldInputs = {};
function refreshOutput() {
var page = $pageInput.val() || '';
var quote = $quoteInput.val() || '';
// read current field values
Object.keys( fieldInputs ).forEach( function ( k ) {
params[ k ] = fieldInputs[ k ].val();
} );
params._template = $typeSel.val();
$output.val( buildRef( params, page, quote ) );
}
function renderFields() {
$fields.empty();
fieldInputs = {};
fieldDefs( params._template ).forEach( function ( fd ) {
var $row = $( '<div>' ).css( { display: 'flex', gap: '8px', 'margin-bottom': '6px', 'align-items': 'center' } );
$row.append( $( '<label>' ).text( fd.label + ':' ).css( { width: '110px', 'font-size': '14px' } ) );
var $inp = $( '<input>' ).attr( 'type', 'text' ).val( params[ fd.key ] || '' ).css( {
flex: '1', padding: '8px 10px', 'font-size': '14px', border: '1px solid #a2a9b1', 'border-radius': '4px'
} );
$inp.on( 'input', refreshOutput );
fieldInputs[ fd.key ] = $inp;
$row.append( $inp );
$fields.append( $row );
} );
$fields.show();
}
$typeSel.on( 'change', function () {
params._template = $typeSel.val();
renderFields();
refreshOutput();
} );
$pageInput.on( 'input', refreshOutput );
$quoteInput.on( 'input', refreshOutput );
$copyBtn.on( 'click', function () {
$output[ 0 ].select();
try {
var ok = document.execCommand( 'copy' );
$copyBtn.text( ok ? 'Copied!' : 'Copy failed' );
} catch ( e ) {
$copyBtn.text( 'Copy failed' );
}
setTimeout( function () { $copyBtn.text( 'Copy reference' ); }, 2000 );
} );
function fetchUrl() {
var url = $.trim( $urlInput.val() );
if ( !url ) return;
if ( !/^https?:\/\//i.test( url ) ) url = 'https://' + url;
$msg.hide();
$fetchBtn.prop( 'disabled', true ).text( 'Loading…' );
fetch( CITOID + encodeURIComponent( url ), {
headers: { 'Api-User-Agent': 'Bharatpedia-RefGen/1.0' }
} ).then( function ( r ) {
if ( !r.ok ) throw new Error( 'Could not fetch data for this URL.' );
return r.json();
} ).then( function ( data ) {
if ( !data || !data.length || data.error ) {
throw new Error( data && data.error ? data.error : 'No citation data found for this URL.' );
}
params = mapToParams( data[ 0 ] );
$typeSel.val( params._template );
$typeRow.show();
renderFields();
$manual.show();
$outWrap.show();
refreshOutput();
} ).catch( function ( err ) {
$msg.text( ( err && err.message ) || 'Something went wrong.' ).show();
} ).finally( function () {
$fetchBtn.prop( 'disabled', false ).text( 'Fetch' );
} );
}
$fetchBtn.on( 'click', fetchUrl );
$urlInput.on( 'keydown', function ( e ) { if ( e.key === 'Enter' ) fetchUrl(); } );
if ( autoUrl ) {
$urlInput.val( autoUrl );
fetchUrl();
}
return $root;
}
function init() {
try {
var pageName = mw.config.get( 'wgPageName' );
if ( pageName === TOOL_PAGE || pageName.indexOf( TOOL_PAGE + '/' ) === 0 ) {
var $static = $( '#bp-rg-static' );
var $tool = buildTool();
if ( $static.length ) {
$static.replaceWith( $tool );
} else {
var $content = $( '#mw-content-text' );
if ( $content.length ) $content.prepend( $tool );
}
}
var $link = mw.util.addPortletLink( 'p-tb', '#', 'Citation generator', 't-refgen', 'Generate a citation from a URL' );
if ( $link ) {
$( $link ).on( 'click', function ( e ) {
e.preventDefault();
window.open( mw.util.getUrl( TOOL_PAGE ), '_blank' );
} );
}
} catch ( e ) {
if ( window.console && console.error ) {
console.error( 'RefGen init failed: ' + ( e && e.message ) );
}
}
}
function boot() {
try {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', init );
} else {
init();
}
} catch ( e ) {
if ( window.console && console.error ) {
console.error( 'RefGen bootstrap failed: ' + ( e && e.message ) );
}
}
}
// Wait for jQuery and mediawiki.util before running.
// Polls every 100ms (up to 10s) because ResourceLoader may execute
// this gadget before its dependencies in batched mode.
function waitForDeps( callback, attempts ) {
attempts = attempts || 0;
try {
if ( typeof $ !== 'undefined' && typeof mw !== 'undefined' && mw.util ) {
callback();
return;
}
} catch ( e ) {}
if ( attempts < 100 ) {
setTimeout( function () { waitForDeps( callback, attempts + 1 ); }, 100 );
} else {
// Give up waiting; try anyway (will fail gracefully via try/catch)
try { callback(); } catch ( e ) {}
}
}
waitForDeps( boot );
/* =====================================================================
* Shared library for the editor integrations.
* The VisualEditor plugin (ext.gadget.RefGenVe) uses mw.libs.refGen.
* ===================================================================== */
mw.libs.refGen = mw.libs.refGen || {};
mw.libs.refGen.TYPES = TYPES;
mw.libs.refGen.mapToParams = mapToParams;
mw.libs.refGen.buildRef = buildRef;
mw.libs.refGen.fetchCitation = function ( url ) {
url = $.trim( url || '' );
var d = $.Deferred();
if ( !url ) {
d.reject( 'Please enter a URL.' );
return d.promise();
}
if ( !/^https?:\/\//i.test( url ) ) {
url = 'https://' + url;
}
$.ajax( {
url: CITOID + encodeURIComponent( url ),
headers: { 'Api-User-Agent': 'Bharatpedia-RefGen/1.0' },
dataType: 'json'
} ).done( function ( data ) {
if ( !data || !data.length || data.error ) {
d.reject( ( data && data.error ) || 'No citation data found for this URL.' );
} else {
d.resolve( data[ 0 ] );
}
} ).fail( function ( jqXHR ) {
var status = jqXHR && jqXHR.status;
if ( status === 404 ) {
d.reject( 'No citation data (page not found - the link may be dead).' );
} else {
d.reject( 'Could not fetch data for this URL.' );
}
} );
return d.promise();
};
/* =====================================================================
* Source editor (WikiEditor) integration: toolbar button + dialog.
* ===================================================================== */
function refGenInsertAtCursor( $textarea, text ) {
try {
$textarea.textSelection( 'encapsulateSelection', { pre: '', peri: text, post: '' } );
return;
} catch ( e ) {}
// Fallback: manual insertion
var el = $textarea[ 0 ];
if ( el && typeof el.selectionStart === 'number' ) {
var s = el.selectionStart, e2 = el.selectionEnd, v = el.value;
el.value = v.slice( 0, s ) + text + v.slice( e2 );
try {
el.selectionStart = el.selectionEnd = s + text.length;
} catch ( e3 ) {}
el.focus();
}
}
function openRefGenEditorDialog( $textarea ) {
var $tool = buildTool();
var $overlay = $( '<div>' ).css( {
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
background: 'rgba(0,0,0,0.55)', 'z-index': 2000,
display: 'flex', 'align-items': 'center', 'justify-content': 'center',
padding: '16px', 'box-sizing': 'border-box'
} );
var $box = $( '<div>' ).css( {
background: '#fff', 'border-radius': '10px', padding: '18px',
width: '640px', 'max-width': '100%', 'max-height': '92vh',
overflow: 'auto', 'box-sizing': 'border-box', position: 'relative'
} );
var $close = $( '<button>' ).attr( { type: 'button', 'aria-label': 'Close' } ).text( '×' ).css( {
position: 'absolute', top: '8px', right: '12px', border: 'none',
background: 'transparent', 'font-size': '24px', cursor: 'pointer', color: '#54595d'
} );
var $insertBtn = $( '<button>' ).attr( 'type', 'button' ).text( 'Insert citation' ).css( {
'margin-top': '12px', padding: '10px 24px', 'font-size': '15px', cursor: 'pointer',
background: '#36c', color: '#fff', border: 'none', 'border-radius': '6px', 'font-weight': 'bold'
} );
$insertBtn.on( 'click', function () {
var ref = $tool.find( 'textarea[readonly]' ).val();
if ( !ref ) {
return;
}
refGenInsertAtCursor( $textarea, ref );
$overlay.remove();
} );
$close.on( 'click', function () { $overlay.remove(); } );
$overlay.on( 'click', function ( e ) {
if ( e.target === $overlay[ 0 ] ) {
$overlay.remove();
}
} );
$box.append( $close, $tool, $insertBtn );
$overlay.append( $box );
$( document.body ).append( $overlay );
}
function initWikiEditorButton() {
if ( mw.config.get( 'wgAction' ) !== 'edit' ) {
return;
}
mw.hook( 'wikiEditor.toolbarReady' ).add( function ( $textarea ) {
$textarea.wikiEditor( 'addToToolbar', {
section: 'main',
group: 'insert',
tools: {
refgen: {
label: 'Ref-Gen',
type: 'button',
oouiIcon: 'code',
filters: [ 'body.ns-subject' ],
action: {
type: 'callback',
execute: function () {
openRefGenEditorDialog( $textarea );
}
}
}
}
} );
} );
}
initWikiEditorButton();
}() );