Jump to content

User:DaxServer/BooksToSfn.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//<nowiki>

/**
 * –––––
 *       YOU ARE FULLY RESPONSIBLE FOR PUBLISHING EDITS USING THIS SCRIPT
 * –––––
 *
 * This script is not ready for use, unless of course, if you know what you are doing.
 * You must verify if the conversion is successful and modify if not.
 * Recommended to also use the [[User:Trappist the monk/HarvErrors]] script in conjunction.
 */

$.when(
    $.ready
).then(function () {
    // Only on main namespace or sandbox
    if ( ! [0, 2].includes(mw.config.get('wgNamespaceNumber'))) {
        return
    }

    let articleName = mw.config.get('wgPageName')
	articleName = encodeURIComponent(articleName); // fix bug involving & not getting converted to &amp;
	let pageIsSandbox = articleName.match(/sandbox$/)

    // Only in sandbox in user namespace
    if (2 === mw.config.get('wgNamespaceNumber') && ! pageIsSandbox) {
        return
    }

    let notifyTitle = 'Books-to-Sfn'

    // Activate portlet when VE source editor is enabled
    mw.hook( 've.activationComplete' ).add(function () {
        // Remove portlet when VE visual editor is enabled
        if (0 === $('.ve-ui-surface-source').length) {
            $('#ds-books-to-sfn').remove()

            return
        }

        $.when(
            mw.loader.using( [ 'mediawiki.util' ] )
        ).then( function () {
            main()
        })
    })

    // Remove portlet when VE is deactivated
    mw.hook( 've.deactivationComplete' ).add(function () {
        $('#ds-books-to-sfn').remove()
    })

    function main() {
        const node = mw.util.addPortletLink('p-tb', '#', 'Books to Sfn', 'ds-books-to-sfn', 'Convert {{cite book}} to {{Sfn}}')

        $( node ).click(function (e) {
            let books = []
            let textBox = $('#wpTextbox1')
            let match, page, cite
            let errors = 0
            let index = 0
            let errorMatches = []

            if ( ! checkIfRefSectionExists(textBox)) {
                return
            }

            // ToDo Add support for quotes?

            while (true) {
                match = getNextMatch(index)

                if (null === match) {
                    break
                }

                // Determine author names
                let authorNames = determineAuthorNames(match[2], 'last')
                console.log('Last name(s)', authorNames)

                if (0 === authorNames.length) {
                    authorNames = determineAuthorNames(match[2], 'author')
                    console.log('Author(s)', authorNames)
                }

                if (0 === authorNames.length) {
                    authorNames = determineAuthorNames(match[2], 'editor')
                    console.log('Editor(s)', authorNames)
                }

                if (authorNames.length > 4) {
                    console.error('More than four authors found. Please fix it manually.', authorNames, match)
                    // mw.notify('More than four authors found. Please fix it manually.', {
                    //     type: 'error',
                    //     title: notifyTitle,
                    // })
                    errors++
                    errorMatches.push({
                        error: 'More than four authors found',
                        match,
                    })

                    // Increment index as we need the next match
                    index = match.index + 1

                    break
                }

                if (0 === authorNames.length) {
                    console.error('Last name(s) / author(s) / editor(s) could not be determined. Please fix it manually.', authorNames, match)
                    // mw.notify('Last name(s) and author(s) could not be determined. Please fix it manually.', {
                    //     type: 'error',
                    //     title: notifyTitle,
                    // })
                    errors++
                    errorMatches.push({
                        error: 'Last name(s) / author(s) / editor(s) could not be determined',
                        match,
                    })

                    // Increment index as we need the next match
                    index = match.index + 1

                    continue
                }
                // End author names

                // Determine year
                let year = determineYear(match[2])
                console.log('Year', year)

                if (null === year) {
                    console.error('Year could not be determined. Please fix it manually.', match)
                    // mw.notify('Year could not be determined. Please fix it manually.', {
                    //     type: 'error',
                    //     title: notifyTitle,
                    // })
                    errors++
                    errorMatches.push({
                        error: 'Year could not be determined',
                        match,
                    })

                    // Increment index as we need the next match
                    index = match.index + 1

                    break
                }

                // Determine page
                ({page, cite} = determinePage(match[2]))
                console.log('Page(s)', page)
                console.log(cite)

                // Duplicate Refs to replace with Sfn
                duplicateRefs(textBox, match[1], match[0])

                // Replace with Sfn
                replaceWithSfn(textBox, authorNames, year, page, match[0])

                books.push(cite)

                // One conversion per click
                break
            }

            if (0 === books.length) {
                if (errors > 0) {
                    mw.notify(`First error: ${errorMatches[0]['error']}. Please fix it manually.`, {
                        type: 'error',
                        title: `${notifyTitle}: ${errors} error(s)`,
                        autoHideSeconds: 'long',
                    })

                    // Highlight the first error
                    let content = textBox.textSelection('getContents')

                    textBox.textSelection('setSelection', {
                        start: content.indexOf(errorMatches[0]['match'][0]),
                        end: content.indexOf(errorMatches[0]['match'][0]) + errorMatches[0]['match'][0].length,
                    })

                    console.log(errorMatches)
                } else {
                    mw.notify('No books to convert', {
                        title: notifyTitle,
                    })
                }

                return
            }

            // Create Bibliography sub-section under References
            createBiblioSectionIfNotExists(textBox)

            // Add Sfns to Bibliography section
            addBooksToBibliography(textBox, books)

            // Hook to add edit summary
            mw.hook( 've.saveDialog.stateChanged' ).add(prefillEditSummary)

            e.preventDefault()
        })
    }

    const matchRe = /<ref(?: name=["']?(:?[\w\s]+)["']?)?> *(\{\{cite book\s?\|[|\w\s=?-–-—&'#.:+,%\/[\]()]+\}\})<\/ref>/imu
    const yearRe = /(?<=year\s*=)([\w\s]*)(?=\||}})/im
    const dateRe = /(?<!access-?)date\s*=([\w\s-]*)(?=\||}})/im
    const pageRe = /\|\s*page\s*=([\w\s]*)(?=\||}})/im
    const pagesRe = /\|\s*pages\s*=([\w\s–]*)(?=\||}})/im
    const reflistRe = /==\s?References\s?==\s*{{Reflist(\|.*)?}}/im
    const biblioRe = /(===? ?Bibliography ?===?\s?\{\{Refbegin(\|.*)?\}\}\s(?:\*\s\{\{cite book[|\w\s=?-–-&'#.:+,%\/[\]()]+\}\}\s){0,})\{\{Refend\}\}/imu

    function getNextMatch(index) {
        console.log('Index for match to start from', index)
        const content = $('#wpTextbox1').textSelection('getContents')

        if ( ! matchRe.test(content.substring(index))) {
            console.log('No matches')

            return null
        }

        return matchRe.exec(content.substring(index))
    }

    function determineAuthorNames(cite, type) {
        let match
        let names = []
        let nameReStr = `\\|\\s*${type}\\s*=([\\w\\s\\.]*)(?=\\|\|\\}\\})`
        let re = new RegExp(nameReStr, 'imu')

        console.log(cite)

        // Determine name without index
        if (re.test(cite)) {
            match = re.exec(cite)
            names.push(match[1].trim())
        }

        let nameIndex = 1

        // Determine nth name with indexing
        while (true) {
            nameReStr = `\\|\\s*${type}${nameIndex}\\s*=([\\w\\s\\.]*)(?=\\|\|\\}\\})`
            re = new RegExp(nameReStr, 'imu')

            // nth name not found. No further searches for names
            if ( ! re.test(cite)) {
                break
            }

            match = re.exec(cite)
            names.push(match[1].trim())

            nameIndex++
        }

        return names
    }

    function determineYear(cite) {
        if (yearRe.test(cite)) {
            return yearRe.exec(cite)[1].trim()
        }

        // Determine year from date
        if ( ! dateRe.test(cite)) {
            return null
        }

        return (new Date(dateRe.exec(cite)[1])).getFullYear()
    }

    function determinePage(cite) {
        let result = {
            page: '',
            cite,
        }

        if (pageRe.test(cite)) {
            result.page = `|p=${pageRe.exec(cite)[1].trim()}`
            result.cite = cite.replace(pageRe, '')
        } else {
            if (pagesRe.test(cite)) {
                result.page = `|pp=${pagesRe.exec(cite)[1].trim()}`
                result.cite = cite.replace(pagesRe, '')
            }
        }

        return result
    }

    // Duplicate Refs to replace with Sfn
    function duplicateRefs(textBox, refName, fullRef) {
        console.log('RefName', refName)

        // Ref not duplicated
        if (undefined === refName) {
            return
        }

        const reStr = `<ref name=\["']?${refName}\["']? ?\\/>`
        const content = textBox.textSelection('getContents')

        textBox.textSelection('setContents', content.replaceAll(new RegExp(reStr, 'imgu'), fullRef))
    }

    function replaceWithSfn(textBox, authorNames, year, page, fullRef) {
        // page will have pipe set
        let sfn = `{{Sfn|${authorNames.join('|')}|${year}${page}}}`

        console.log('Sfn', sfn);

        const content = textBox.textSelection('getContents')

        textBox.textSelection('setContents', content.replaceAll(fullRef, sfn))

        mw.notify( `Replaced ${sfn}`, {
            type: 'success',
            title: notifyTitle,
        })
    }

    function checkIfRefSectionExists(textBox) {
        const content = textBox.textSelection('getContents')

        // Bibliography section exists
        if (biblioRe.test(content)) {
            return true
        }

        // References section exists, but not Bibliography which can be created
        if (reflistRe.test(content)) {
            return true;
        }

        // References section regex failure
        mw.notify('References section not found. Possible regex failure.', {
            type: 'error',
            title: notifyTitle,
        })

        return false
    }

    function createBiblioSectionIfNotExists(textBox) {
        const content = textBox.textSelection('getContents')

        // Section exists
        if (biblioRe.test(content)) {
            return;
        }

        const reflistMatch = reflistRe.exec(content)

        // Add Bibliography section
        textBox.textSelection('encapsulateSelection', {
            post: `\n\n=== Bibliography ===\n{{Refbegin}}\n{{Refend}}`,
            selectionStart: content.indexOf(reflistMatch[0]),
            selectionEnd: content.indexOf(reflistMatch[0]) + reflistMatch[0].length,
        })
    }

    function addBooksToBibliography(textBox, books) {
        const content = textBox.textSelection('getContents')

        let bookRefStr = ''
        for (let book of books) {
            bookRefStr += `* ${book}\n`
        }

        const biblioMatch = biblioRe.exec(content)

        textBox.textSelection('encapsulateSelection', {
            post: bookRefStr,
            selectionStart: content.indexOf(biblioMatch[1]),
            selectionEnd: content.indexOf(biblioMatch[1]) + biblioMatch[1].length,
        })
    }

    function prefillEditSummary() {
        if (ve.init.target.saveDialog) {
            ve.init.target.saveDialog.editSummaryInput.$input.val('Convert [[Template:Cite book|{{cite book}}]] reference(s) to [[Template:Sfn|Sfn]]s ([[User:DaxServer/BooksToSfn|BooksToSfn.js]])')
        }

        // Remove hook upon prefilling
        mw.hook( 've.saveDialog.stateChanged' ).remove(prefillEditSummary)
    }
})

//</nowiki>