User : Rillke/SVGedit.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5 , Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R ), Opera/Konqueror: press F5 , Safari: hold down Shift + Alt while clicking Reload , Chrome: hold down Shift while clicking Reload .
/**

* Allow editing SVG file's source code without having to save them locally (aka "download") them.

* @docu https://commons.wikimedia.org/wiki/User_talk:Rillke/SVGedit.js

*

* @rev 1 (2014-03-22)

* @rev 2 (2015-05-29)

* @author Rillke, 2014-2015

*/

// List the global variables for jsHint-Validation. Please make sure that it passes http://jshint.com/

// Scheme: globalVariable:allowOverwriting[, globalVariable:allowOverwriting][, globalVariable:allowOverwriting]

/* global jQuery:false, mediaWiki:false, MwJSBot:false, CodeMirror:false */

// Set jsHint-options. You should not set forin or undef to false if your script does not validate.

/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true,

 undef:true, curly:false, browser:true, multistr:true */

/* eslint indent:["error","tab",{"outerIIFEBody":0}] */


(
function
 (
$
,
 mw
)
 {

'use strict'
;


var
 svgEdit
,

	i
,

	MYSELF
 =
 'SVGEdit'
,

	conf
 =
 mw
.
config
.
get
([

		'wgDBname'
,

		'wgPageName'
,

		'wgNamespaceNumber'
,

		'wgRevisionId'
,

		'wgTitle'

	]),

	isCommonsWiki
 =
 conf
.
wgDBname
 ===
 'commonswiki'
,

	random
 =
 Math
.
round
(
Math
.
random
()
 *
 0x1000000000
),

	commonwWikiKey
 =
 'commonswiki'
 +
 random
,

	commonsWiki
 =
 {},

	modules
 =
 [

		[
'ext.gadget.jquery.blockUI'
,
 'ver1_svg'
,
 [],
 null
,
 commonwWikiKey
],

		[
'ext.gadget.libAPI'
,
 'ver1_svg'
,
 [
'user.options'
],
 null
,
 commonwWikiKey
],

		[
'ext.gadget.editDropdown'
,
 'ver1_svg'
,
 [
'jquery.client'
,
 'user.options'
],
 null
,
 commonwWikiKey
]

	];


svgEdit
 =
 {

	version
:
 '0.0.15.2'
,

	init
:
 function
 ()
 {

		var
 $activationLinks
 =
 $
();

		// File namespace?

		if
 (
conf
.
wgNamespaceNumber
 !==
 6
 ||
 !
/\.svg$/i
.
test
(
conf
.
wgPageName
))

			return
 svgEdit
.
log
(
'Not a SVG-file. Aborting initialization.'
);


		if
 (
mw
.
user
.
isAnon
())

			return
 svgEdit
.
log
(
'Anonymous users cannot upload files. Aborting initialization.'
);


		// if (!conf.wgRevisionId || !$('.filehistory').find('td.filehistory-selected').length) return svgEdit.log('Page or file does not exist.');


		$activationLinks
 =
 $activationLinks
.
add
(
mw
.
libs
.
commons
.
ui
.
addEditLink
(
'#SVGedit'
,
 'Edit SVG'
,
 'e-edit-raw-SVG'
,
 'Edit SVG source code'
));


		$activationLinks
.
click
(
function
 (
e
)
 {

			e
.
preventDefault
();

			svgEdit
.
run
();

			$activationLinks
.
addClass
(
'ui-state-disabled'
);

		});

		if
 (
mw
.
util
.
getParamValue
(
'svgrawedit'
))

			svgEdit
.
run
();

	},

	registerModules
:
 function
 ()
 {

		// Register custom modules

		if
 (
!
mw
.
loader
.
getState
(
'mediawiki.commons.MwJSBot'
))
 {

			mw
.
loader
.
implement
(
'mediawiki.commons.MwJSBot'
,
 [
'//commons.wikimedia.org/w/index.php?action=raw&ctype=text/javascript&title=User:Rillke/MwJSBot.js'
],

				{
 /* no styles*/
 },
 {
 /* no messages*/
 });

		}

	},

	run
:
 function
 ()
 {

		// Create GUI

		svgEdit
.
registerModules
();


		mw
.
loader
.
using
([
'mediawiki.commons.MwJSBot'
,
 'user.options'
],
 function
 ()
 {

			svgEdit
.
gui
();

		});

	},

	gui
:
 function
 ()
 {

		var
 $gui
 =
 $
(
'<form action="/">'
),

			$preview
 =
 $
(
'<div>'
)

				.
appendTo
(
$gui
),

			$diffContainer
 =
 $
(
'<div>'
)

				.
css
({
 border
:
 '1px solid grey'
 })

				.
text
(
'Diff: '
)

				.
hide
()

				.
appendTo
(
$gui
),

			$validationWrapper
 =
 $
(
'<div>'
)

				.
css
({

					'border'
:
 '1px solid grey'
,

					'min-height'
:
 '2em'
,

					'max-height'
:
 '40em'
,

					'resize'
:
 'both'
,

					'overflow'
:
 'auto'

				})

				.
hide
()

				.
appendTo
(
$gui
),

			$validationDoctypeLabel
 =
 $
(
'<div>'
)

				.
css
({

					'float'
:
 'right'
,

					'background'
:
 '#FFD'
,

					'padding'
:
 '.3em'
,

					'font-family'
:
 'monospace'

				})

				.
attr
({
 title
:
 'document type used for validation'
 })

				.
appendTo
(
$validationWrapper
),

			$validationContainer
 =
 $
(
'<ul>'
)

				.
appendTo
(
$validationWrapper
),

			$validationContainer2
 =
 $
(
'<ul>'
)

				.
appendTo
(
$validationWrapper
),

			$diff
 =
 $
(
'<div>'
)

				.
css
({
 font
:
 '12px "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace'
 })

				.
appendTo
(
$diffContainer
),

			$imgPreviewContainer
 =
 $
(
'<div>'
)

				.
css
({

					position
:
 'relative'
,

					overflow
:
 'hidden'
,

					display
:
 'inline-block'

				})

				.
html
(
'<a href="https://en.wikipedia.org/wiki/Librsvg" target="_blank">RSVG</a> rendering:<br>'
)

				.
hide
()

				.
appendTo
(
$preview
),

			$imgPreview
 =
 $
(
'<img>'
)

				.
attr
({
 title
:
 'rsvg preview'
 })

				.
css
({
 'vertical-align'
:
 'top'
 })

				.
addClass
(
'com-svgedit-preview'
)

				.
appendTo
(
$imgPreviewContainer
),

			$imgPreview2Container
 =
 $
(
'<div>'
)

				.
css
({

					position
:
 'relative'
,

					overflow
:
 'hidden'
,

					display
:
 'inline-block'

				})

				.
html
(
'Browser rendering (iframe):<br>'
)

				.
hide
()

				.
appendTo
(
$preview
),

			$imgPreview2Overlay
 =
 $
(
'<div>'
)

				.
attr
({
 title
:
 'browser preview'
 })

				.
css
({

					'position'
:
 'absolute'
,

					'left'
:
 0
,

					'top'
:
 0
,

					'bottom'
:
 0
,

					'right'
:
 0
,

					'z-index'
:
 1

				})

				.
appendTo
(
$imgPreview2Container
),

			$imgPreview2
 =
 $
(
'<iframe>'
)

				.
attr
({

					sandbox
:
 'sandbox'
,

					title
:
 'browser preview'

				})

				.
css
({

					'border'
:
 '1px solid #EEE'
,

					'width'
:
 0
,

					'height'
:
 0
,

					'resizable'
:
 'both'
,

					'vertical-align'
:
 'top'

				})

				.
addClass
(
'com-svgedit-preview'
)

				.
appendTo
(
$imgPreview2Container
),

			$taWrap
 =
 $
(
'<div>'
)

				.
appendTo
(
$gui
),

			$ta
 =
 $
(
'<textarea>'
).
attr
({

				rows
:
 mw
.
user
.
options
.
get
(
'rows'
),

				cols
:
 mw
.
user
.
options
.
get
(
'cols'
),

				disabled
:
 'disabled'

			}).
css
({
 width
:
 '99%'
 }).
appendTo
(
$taWrap
),

			$sum
 =
 $
(
'<input type="text" style="width:99%" maxlength="200" pattern=".{3,}" required placeholder="upload summary (changes, techniques, 3-200 characters)" title="3-200 letters, please">'
)

				.
appendTo
(
$gui
),

			$buttonPane
 =
 $
(
'<div>'
)

				.
addClass
(
'com-svg-edit-buttonpane'
)

				.
appendTo
(
$gui
),

			$saveBtn
 =
 $
(
'<button>'
).
attr
({

				type
:
 'submit'
,

				role
:
 'submit'
,

				disabled
:
 'disabled'

			}).
text
(
'Save SVG'
).
appendTo
(
$buttonPane
),

			$loadCodeEditorBtn
 =
 $
(
'<button>'
).
attr
({

				type
:
 'button'
,

				role
:
 'button'
,

				disabled
:
 'disabled'
,

				title
:
 'Loads a code editor (XML mode)'

			}).
text
(
'Load CodeMirror'
).
appendTo
(
$buttonPane
),

			$previewBtn
 =
 $
(
'<button>'
).
attr
({

				type
:
 'button'
,

				role
:
 'button'
,

				disabled
:
 'disabled'
,

				title
:
 'Render a preview'

			}).
text
(
'Preview'
).
appendTo
(
$buttonPane
),

			$diffBtn
 =
 $
(
'<button>'
).
attr
({

				type
:
 'button'
,

				role
:
 'button'
,

				disabled
:
 'disabled'
,

				title
:
 'Show difference between saved and working copy'

			}).
text
(
'Diff'
).
appendTo
(
$buttonPane
),

			$validationDoctype
 =
 $
(
'<select>'
)

				.
html
(

'<option value="Inline" selected="">(detect automatically)</option>\

<option value="SVG 1.0">SVG 1.0</option>\

<option value="SVG 1.1">SVG 1.1</option>\

<option value="SVG 1.1 Tiny">SVG 1.1 Tiny</option>\

<option value="SVG 1.1 Basic">SVG 1.1 Basic</option>'

				)

				.
hide
()

				.
appendTo
(
$buttonPane
),

			$validateButton
 =
 $
(
'<button>'
).
attr
({

				type
:
 'button'
,

				role
:
 'button'
,

				disabled
:
 'disabled'
,

				title
:
 'Check for glitches against validators'

			}).
text
(
'Validate'
).
appendTo
(
$buttonPane
),

			$uploadButton
 =
 $
(
'<input type="file">'
).
attr
({

				disabled
:
 'disabled'
,

				title
:
 'Replace editor contents with file contents'

			}).
appendTo
(
$buttonPane
),

			allowCloseWindow
,

			timeout
,

			getCurrentValue
,

			setCurrentValue
,

			getOriginal
,

			$fetchCB
;

		mw
.
util
.
addCSS
(
'.com-svgedit-preview:hover, .com-svgedit-preview-hover { \

			background: url("//upload.wikimedia.org/wikipedia/commons/5/5d/Checker-16x16.png") repeat scroll }'
);

		$
(
'<div>'
).
css
({

			'float'
:
 'right'
,

			'color'
:
 '#DDD'

		}).
text
(
'Version: '
 +
 this
.
version
).
appendTo
(
$buttonPane
);


		getCurrentValue
 =
 function
 ()
 {

			return
 svgEdit
.
CodeMirror
 ?

				svgEdit
.
CodeMirror
.
getValue
()
 :

				$ta
.
val
();

		};

		setCurrentValue
 =
 function
 (
val
)
 {

			if
 (
svgEdit
.
CodeMirror
)

				svgEdit
.
CodeMirror
.
setValue
(
val
);

			else

				$ta
.
val
(
val
);


		};

		getOriginal
 =
 function
 ()
 {

			return
 $ta
.
data
(
'orignal-svg'
);

		};

		$fetchCB
 =
 function
 (
r
)
 {

			$ta
.
val
(
r
);

			$ta
.
data
(
'orignal-svg'
,
 r
);

			$saveBtn

				.
add
(
$ta
)

				.
add
(
$loadCodeEditorBtn
)

				.
add
(
$previewBtn
)

				.
add
(
$diffBtn
)

				.
add
(
$validateButton
)

				.
add
(
$uploadButton
)

				.
removeAttr
(
'disabled'
);

			timeout
 =
 setTimeout
(
function
 ()
 {

				mw
.
loader
.
using
(
'mediawiki.confirmCloseWindow'
,
 function
 ()
 {

					allowCloseWindow
 =
 mw
.
confirmCloseWindow
({
 test
:
 function
 ()
 {

						return
 getCurrentValue
()
 !==
 getOriginal
();

					}
 });

				});

			},
 5000
);

		};


		$ta
.
val
(
'Loading SVG'
);

		this
.
fileUrl
 =
 ''
;


		$
(
'#file'
).
find
(
'a'
).
each
(
function
 (
i
,
 el
)
 {

			var
 href
 =
 $
(
el
).
attr
(
'href'
),

				fileDomainPos
 =
 href
.
indexOf
(
'upload.wikimedia.org'
);

			if
 (
fileDomainPos
 <
 10
 &&
 fileDomainPos
 !==
 -
1
 &&
 /\.svg$/i
.
test
(
href
))
 {

				svgEdit
.
fileUrl
 =
 href
;

				return
 false
;

			}

		});


		if
 (
!
this
.
fileUrl
)
 {

		// Get filepath if in edit-mode

			$
.
ajax
({

				url
:
 mw
.
config
.
get
(
'wgServer'
)
 +
 mw
.
util
.
wikiScript
(
'api'
)
 +
 '?action=query&format=json&prop=imageinfo&titles='
 +
 mw
.
util
.
wikiUrlencode
(
conf
.
wgPageName
)
 +
 '&iiprop=url&iilimit=1'
,

				dataType
:
 'json'
,

				success
:
 function
 (
r
)
 {

					if
 (
r
 &&
 r
.
query
 &&
 r
.
query
.
pages
)
 {

						r
 =
 r
.
query
.
pages
;

						for
 (
var
 id
 in
 r
)
 {

							if
 (
r
[
id
].
imageinfo
[
0
]
 &&
 r
[
id
].
imageinfo
[
0
].
url
)
 {

								svgEdit
.
fileUrl
 =
 r
[
id
].
imageinfo
[
0
].
url
;

								return
 svgEdit
.
$fetch
().
done
(
$fetchCB
);

							}
 else
 {

								svgEdit
.
failURL
();

							}

						}

					}
 else
 {

						svgEdit
.
failURL
();

					}

				}

			});

		}
 else
 {

			this
.
$fetch
().
done
(
$fetchCB
);

		}


		$imgPreview2Overlay
.
click
(
function
 ()
 {

			if
 (
prompt
(
'DANGER ZONE: For your security, we added \

				an overlay over the iframe protecting you from accidental \

				interactions with the potentially evil/ harmful SVG code. \

				Type "sudo" to disable this security-layer. \

				(Otherwise just cancel)'
)
 ===
 'sudo'
)

				$imgPreview2Overlay
.
hide
();


		}).
hover
(
function
 ()
 {

			$imgPreview2
.
addClass
(
'com-svgedit-preview-hover'
);

		},
 function
 ()
 {

			$imgPreview2
.
removeClass
(
'com-svgedit-preview-hover'
);

		});


		$gui
.
submit
(
function
 (
e
)
 {

			e
.
preventDefault
();

			$saveBtn
.
add
(
$sum
).
attr
(
'disabled'
,
 'disabled'
);

			svgEdit
.
save
(

				svgEdit
.
CodeMirror
 ?

					svgEdit
.
CodeMirror
.
getValue
()
 :

					$ta
.
val
(),

				$sum
.
val
()

			).
done
(
function
 (
httpStatus
,
 response
)
 {

				if
 (
response
 &&
 window
.
JSON
)

					response
 =
 JSON
.
parse
(
response
);


				if
 (
response
 &&
 response
.
error
)
 {

					alert
(
'API Error '
 +
 response
.
error
.
code
 +
 ':\n'
 +
 response
.
error
.
info
);

					$saveBtn
.
add
(
$sum
).
removeAttr
(
'disabled'
);

					$taWrap
.
attr
(
'noblock'
,
 1
).
unblock
();

				}
 else
 {

					clearTimeout
(
timeout
);

					if
 (
allowCloseWindow
)

						allowCloseWindow
.
release
();


					svgEdit
.
reload
();

				}

			}).
fail
(
function
 ()
 {

				alert
(
'Server error: Something went wrong'
);

				$saveBtn
.
add
(
$sum
).
removeAttr
(
'disabled'
);

				$taWrap
.
attr
(
'noblock'
,
 1
).
unblock
();

			});

			svgEdit
.
block
(
$taWrap
);

		});


		$loadCodeEditorBtn
.
click
(
function
 ()
 {

			$
(
this
).
attr
(
'disabled'
,
 'disabled'
);

			svgEdit
.
loadCodeEditor
(
$ta
);

		});


		$previewBtn
.
click
(
function
 ()
 {

			var
 val
 =
 getCurrentValue
(),

				blob
,

				URL
,

				dataUrl
,

				typedArray
,

				v
,

				w
,

				h
,

				m
;

			URL
 =
 window
.
URL
 ||
 window
.
webkitURL
;


			blob
 =
 new
 Blob
([
val
],
 {
 type
:
 'image/svg+xml'
 });

			dataUrl
 =
 URL
.
createObjectURL
(
blob
);

			// Naive RegExp matching (avoids parsing the whole document)

			// and possible security or malformed SVG troubles

			v
 =
 val
.
slice
(
4
,
 5000
);

			m
 =
 v
.
match
(
/height\s*=\s*["']([\d.]+)["']/
);

			if
 (
!
(
m
 &&
 (
h
 =
 m
[
1
])
 &&
 (
h
 =
 Number
(
h
))
 &&
 h
 >
 15
))

				h
 =
 500
;


			m
 =
 v
.
match
(
/width\s*=\s*["']([\d.]+)["']/
);

			if
 (
!
(
m
 &&
 (
w
 =
 m
[
1
])
 &&
 (
w
 =
 Number
(
w
))
 &&
 w
 >
 15
))

				w
 =
 500
;


			$previewBtn
.
attr
(
'disabled'
,
 'disabled'
);


			$imgPreview2Container
.
show
();

			$imgPreviewContainer
.
css
({

				height
:
 500
,

				width
:
 500

			}).
show
();

			svgEdit
.
block
(
$imgPreviewContainer
);

			svgEdit
.
block
(
$imgPreview2Container
);


			$imgPreview2
.
one
(
'load'
,
 function
 ()
 {

				if
 (
$imgPreview2Container
.
unblock
)

					$imgPreview2Container
.
unblock
();


			}).
attr
(
'src'
,
 dataUrl
).
css
({

				width
:
 w
,

				height
:
 h

			});


			svgEdit

				.
fetchPreview
(
val
)

				.
done
(
function
 (
statusText
,
 response
)
 {

					typedArray
 =
 new
 Uint8Array
(
response
);

					blob
 =
 new
 Blob
([
typedArray
],
 {
 type
:
 'image/jpeg'
 });

					dataUrl
 =
 URL
.
createObjectURL
(
blob
);

					$imgPreviewContainer
.
css
({

						height
:
 'auto'
,

						width
:
 'auto'

					});

					$imgPreview
.
attr
(
'src'
,
 dataUrl
);

					setTimeout
(
function
 ()
 {

						$imgPreview2
.
css
({

							width
:
 $imgPreview
.
width
(),

							height
:
 $imgPreview
.
height
()

						});

					},
 1000
);

				})

				.
fail
(
function
 (
/* r*/
)
 {

					$imgPreview
.
attr
(
'src'
,
 '//upload.wikimedia.org/wikipedia/commons/thumb/5/55/Bug_blank.svg/200px-Bug_blank.svg.png'
);

				})

				.
always
(
function
 ()
 {

					$previewBtn
.
removeAttr
(
'disabled'
);

					$imgPreviewContainer
.
add
(
$imgPreview2Container
).
unblock
();

				});

		});

		$diffBtn
.
click
(
function
 ()
 {

			svgEdit
.
block
(
$diffContainer
.
show
());

			svgEdit
.
$usingScharkDiff
().
done
(
function
 ()
 {

				$diff
.
html
(
mw
.
libs
.
schnarkDiff
.
htmlDiff
(

					getOriginal
(),

					getCurrentValue
(),

					true
));

				$diffContainer
.
unblock
();

			});

		});

		$validateButton
.
click
(
function
 ()
 {

			if
 (
$validationDoctype
.
css
(
'display'
)
 ===
 'none'
)

				return
 $validationDoctype
.
fadeIn
(
'fast'
);


			svgEdit
.
block
(
$validationWrapper
.
show
());

			svgEdit
.
$validate
(
getCurrentValue
(),
 $validationDoctype
.
val
()).
done
(
function
 (
textStatus
,
 r
)
 {

				$validationWrapper
.
unblock
();

				$validationContainer
.
add
(
$validationContainer2
).
text
(
''
);

				try
 {

					r
 =
 JSON
.
parse
(
r
);

				}
 catch
 (
invalidJSON
)
 {}

				if
 (
r
.
source
)

					$validationDoctypeLabel
.
text
(
r
.
source
.
doctype
);


				if
 (
r
.
svgcheck
 &&
 r
.
svgcheck
.
length
)
 {

					$
.
each
(
r
.
svgcheck
,
 function
 (
i
,
 msg
)
 {

						$validationContainer2
.
append
(
svgEdit
.
$validationItem2
(
msg
));

					});

				}

				if
 (
r
.
messages
)
 {

					$
.
each
(
r
.
messages
,
 function
 (
i
,
 msg
)
 {

						$validationContainer
.
append
(
svgEdit
.
$validationItem
(
msg
));

					});

					if
 (
!
r
.
messages
.
length
)

						$validationContainer
.
append
(
$
(
'<li>Well done :)</li>'
));


				}
 else
 if
 (
r
.
response
)
 {

					$validationContainer
.
html
(
r
.
response
);

				}
 else
 {

					$validationContainer
.
text
(
JSON
.
stringify
(
r
));

				}

			});

		});

		$uploadButton
.
on
(
'change'
,
 function
 ()
 {

			var
 file
 =
 $uploadButton
[
0
].
files
[
0
];

			if
 (
!
file
)

				return
;


			var
 size
 =
 file
.
size
;

			if
 (
size
 >
 15
 *
 1024
 *
 1024
)

				return
 alert
(
'Selected file is > 15 MiB. Aborting.'
);


			var
 reader
 =
 new
 FileReader
();

			reader
.
onload
 =
 function
 ()
 {

				// Clear upload button

				$uploadButton
.
val
(
''
);

				if
 (
getCurrentValue
()
 !==
 $ta
.
data
(
'orignal-svg'
))
 {

					if
 (
!
confirm
(
'The editor contents changed from the stored revision. Are you sure you want to replace the editor contents with the contents loaded from the file selected?'
))
 {

						return
;
 // Cancel: Do nothing!

					}

				}

				setCurrentValue
(
reader
.
result
);

			};

			reader
.
readAsText
(
file
);

		});

		$gui
.
prependTo
(
'#mw-content-text'
);

	},

	block
:
 function
 (
$el
)
 {

		mw
.
loader
.
using
(
'ext.gadget.jquery.blockUI'
,
 function
 ()
 {

			if
 (
$el
.
attr
(
'noblock'
))

				return
;


			$el
.
block
({

				message
:
 '<img src="//upload.wikimedia.org/wikipedia/commons/1/10/Loading-special.gif" height="15" width="128">'
,

				css
:
 {

					border
:
 'none'
,

					background
:
 'none'

				}

			});

		});

	},

	$validationItem
:
 function
 (
validatorMsg
)
 {

		var
 p
 =
 'com-svgedit-validation-'
,

			$l
 =
 $
(
'<code>'
).
addClass
(
p
 +
 'line'
).
text
(
'L.'
 +
 validatorMsg
.
lastLine
),

			$col
 =
 validatorMsg
.
lastColumn
 ?
 $
(
'<code>'
).
addClass
(
p
 +
 'col'
)

				.
text
(
'col.'
 +
 validatorMsg
.
lastColumn
)
 :
 ''
,

			$msg
 =
 $
(
'<span>'
).
addClass
(
p
 +
 'message'
).
text
(
validatorMsg
.
message
),

			$msgId
 =
 $
(
'<span>'
).
addClass
(
p
 +
 'messageid'
).
text
(
validatorMsg
.
messageid
),

			$li
 =
 $
(
'<li>'
).
append
(
$l
,
 ' '
,
 $col
,
 ': '
,
 $msg
,
 ' ('
,
 $msgId
,
 ')'
);

		return
 $li
;

	},

	$validationItem2
:
 function
 (
validatorMsg
)
 {

		$
.
each
(
validatorMsg
.
issues
,
 function
 (
i
,
 issue
)
 {

			validatorMsg
.
issues
[
i
]
 =
 mw
.
html
.
escape
(
issue
)

				.
replace
(
/\*\*(.+?)\*\*/
,
 '<b><i>$1</i></b>'
)

				.
replace
(
/\*(.+?)\*/
,
 '<i>$1</i>'
);

		});

		var
 p
 =
 'com-svgedit-validation-'
,

			$l
 =
 $
(
'<code>'
).
addClass
(
p
 +
 'line'
).
text
(
'L.'
 +
 validatorMsg
.
line
),

			$msg
 =
 $
(
'<span>'
).
addClass
(
p
 +
 'message'
)

				.
html
(
validatorMsg
.
issues
.
join
(
', '
)),

			$li
 =
 $
(
'<li>'
).
append
(
$l
,
 ': '
,
 $msg
);

		return
 $li
;

	},

	$validate
:
 function
 (
svg
,
 doctype
)
 {

		return
 svgEdit
.
bot
.
multipartMessageForUTF8Files
()

			.
appendPart
(
'svgcheck'
,
 'on'
)

			.
appendPart
(
'doctype'
,
 doctype
)

			.
appendPart
(
'file'
,
 svg
,
 'input.svg'
)

			.
$send
(
'//validator.toolforge.org/w3.php'
);

	},

	$usingScharkDiff
:
 function
 ()
 {

		var
 $deferred
 =
 $
.
Deferred
();

		if
 (
mw
.
libs
.
schnarkDiff
 &&
 mw
.
libs
.
schnarkDiff
.
htmlDiff
)
 {

			$deferred
.
resolve
();

		}
 else
 {

			mw
.
hook
(
'userjs.load-script.diff-core'
).
add
(
function
 ()
 {

				mw
.
libs
.
schnarkDiff
.
style
.
set
(
'ins'
,
 'text-decoration: underline; font-weight: bold; font-size:1.2em; color: #020; background-color: #ABE; -moz-text-decoration-color:#474;'
);

				mw
.
libs
.
schnarkDiff
.
style
.
set
(
'del'
,
 'font-size:1.2em; color: #200; background-color: #FD9; text-decoration-color:#744;'
);

				mw
.
util
.
addCSS
(
mw
.
libs
.
schnarkDiff
.
getCSS
());

				mw
.
libs
.
schnarkDiff
.
config
.
set
(
'minMovedLength'
,
 20
);

				mw
.
libs
.
schnarkDiff
.
config
.
set
(
'tooShort'
,
 3
);

				$deferred
.
resolve
();

			});

			mw
.
loader
.
load
(
'//de.wikipedia.org/w/index.php?title=Benutzer:Schnark/js/diff.js/core.js&action=raw&ctype=text/javascript'
);

		}

		return
 $deferred
.
promise
();

	},

	failURL
:
 function
 (
err
)
 {

		err
 =
 err
 ||
 'Unable to extract file URL.'
;

		svgEdit
.
log
(
err
);

		throw
 new
 Error
(
err
);

	},

	$fetch
:
 function
 ()
 {

		// Fetch SVG source code

		svgEdit
.
bot
 =
 new
 MwJSBot
();


		if
 (
!
svgEdit
.
fileUrl
)

			return
 svgEdit
.
failURL
();


		// Assuming the SVG is UTF-8-encoded

		return
 $
.
ajax
({

			url
:
 svgEdit
.
fileUrl
,

			cache
:
 false
,

			beforeSend
:
 function
 (
xhr
)
 {

				xhr
.
overrideMimeType
(
'text/plain; charset=UTF-8'
);

			}

		});

	},

	loadCodeEditor
:
 function
 (
$textArea
/* , $parent*/
)
 {

		// Just in case someone complains about the license ...

		var
 mirrors
 =
 [

				'//commons.wikimedia.org/w/index.php?'
,

				'//tools-static.wmflabs.org/rillke/CodeMirror/'
,

				'//mol-static.wmflabs.org/CodeMirror/'

			],

			scripts
 =
 [
'lib/codemirror.js'
,
 'mode/xml/xml.js'
],

			styles
 =
 [
'lib/codemirror.css'
],

			params
 =
 {

				action
:
 'raw'
,
 ctype
:
 'text/javascript'
,
 title
:
 '?'

			},


			rlScripts
 =
 $
.
map
(
scripts
,
 function
 (
el
)
 {

				params
.
title
 =
 'User:Rillke/CodeMirror/'
 +
 el
;

				return
 mirrors
[
0
]
 +
 $
.
param
(
params
);

			});

		params
.
ctype
 =
 'text/css'
;

		var
 rlStyles
 =
 $
.
map
(
styles
,
 function
 (
el
)
 {

			params
.
title
 =
 'User:Rillke/CodeMirror/'
 +
 el
;

			return
 mirrors
[
0
]
 +
 $
.
param
(
params
);

		});


		if
 (
!
mw
.
loader
.
getState
(
'mediawiki.commons.CodeMirror'
))
 {

			mw
.
loader
.
implement
(
'mediawiki.commons.CodeMirror'
,

				rlScripts
,
 {
 url
:
 {
 screen
:
 rlStyles
 }
 },

				{
 /* no messages*/
 });

		}


		mw
.
loader
.
using
(
'mediawiki.commons.CodeMirror'
,
 function
 ()
 {

			var
 h
 =
 $textArea
.
parent
().
height
(),

				m
 =
 $textArea
.
val
()

					.
slice
(
0
,
 6000
)

					.
match
(
/.+\n([\t ]+)<\S+(?:.|\n)*\n\1</
),

				settings
 =
 {

					lineNumbers
:
 true
,

					mode
:
 'xml'
,

					viewportMargin
:
 120

				},

				l
;


			if
 (
m
)
 {

				l
 =
 m
[
1
].
length
;

				if
 (
l
 >
 0
 &&
 l
 <
 9
)
 {

					if
 (
/ /
.
test
(
m
[
1
]))
 {

						svgEdit
.
log
(
'Indention with spaces'
);

						$
.
extend
(
true
,
 settings
,
 {

							extraKeys
:
 {
 Tab
:
 function
 ()
 {

								svgEdit
.
CodeMirror
.
execCommand
(
'insertSoftTab'
);

							}
 },

							tabSize
:
 l

						});

					}
 else
 if
 (
/\t/
.
test
(
m
[
1
]))
 {

						svgEdit
.
log
(
'Indention with tabs'
);

						$
.
extend
(
true
,
 settings
,
 {

							indentWithTabs
:
 true
,

							tabSize
:
 2

						});

					}

				}

			}

			svgEdit
.
CodeMirror
 =
 CodeMirror
.
fromTextArea
(
$textArea
[
0
],
 settings
);

			$
(
svgEdit
.
CodeMirror
.
display
.
scroller
).
css
({
 height
:
 (
h
 -
 5
)
 +
 'px'
 });

			$
(
svgEdit
.
CodeMirror
.
display
.
wrapper
).
css
({

				border
:
 '1px solid #EEE'
,

				height
:
 'auto'

			});

		});

	},

	save
:
 function
 (
text
,
 summary
)
 {

		if
 (
summary
)

			summary
 +=
 ' // '
;


		var
 message
 =
 svgEdit
.
bot
.
multipartMessageForUTF8Files
()

			.
appendPart
(
'format'
,
 'json'
)

			.
appendPart
(
'action'
,
 'upload'
)

			.
appendPart
(
'filename'
,
 conf
.
wgTitle
)

			.
appendPart
(
'comment'
,
 summary
 +
 'Editing SVG source code using [[c:User:Rillke/SVGedit.js]]'
)

			.
appendPart
(
'file'
,
 text
,
 conf
.
wgTitle
)

			.
appendPart
(
'ignorewarnings'
,
 1
)

			.
appendPart
(
'token'
,
 mw
.
user
.
tokens
.
get
(
'csrfToken'
));

		if
 (
isCommonsWiki
)

			message
.
appendPart
(
'tags'
,
 'rillke-mw-js-bot'
);


		return
 message
.
$send
();

	},

	fetchPreview
:
 function
 (
svg
)
 {

		return
 svgEdit
.
bot
.
multipartMessageForUTF8Files
()

			.
appendPart
(
'file'
,
 svg
,
 'input.svg'
)

			.
$send
(
'//convert.toolforge.org/svg2png.php'
,
 'arraybuffer'
);

	},

	reload
:
 function
 ()
 {

		window
.
location
.
href
 =
 mw
.
util
.
getUrl
(
conf
.
wgPageName
);

	},

	log
:
 function
 ()
 {

		var
 args
 =
 Array
.
prototype
.
slice
.
call
(
arguments
);

		args
.
unshift
(
MYSELF
);

		mw
.
log
.
apply
(
mw
.
log
,
 args
);

	}

};


// Register globally

if
 (
!
isCommonsWiki
 ||
 conf
.
wgDBname
 !==
 'commonsarchivewiki'
)
 {

	// mw.loader.addSource has a check for source key uniqueness

	// that if it fails, throws an error.

	// Since I am offering many scripts, I would like to be able to register

	// a source from multiple code positions. However the loader has no

	// accessors to its internally maintained list of sources. Therefore

	// ensure with high probabiltiy that every source key added is unique.

	commonsWiki
[
commonwWikiKey
]
 =
 '//commons.wikimedia.org/w/load.php'
;

	mw
.
loader
.
addSource
(
commonsWiki
);


	// Register Commons RL modules

	for
 (
i
 =
 0
;
 i
 <
 modules
.
length
;
 i
++
)
 {

		if
 (
!
mw
.
loader
.
getState
(
modules
[
i
][
0
]))

			mw
.
loader
.
register
([
modules
[
i
]]);

	}

}


// Expose globally

mw
.
libs
.
svgRawEditor
 =
 svgEdit
;


mw
.
loader
.
using
([
'mediawiki.util'
,
 'mediawiki.user'
,
 'ext.gadget.editDropdown'
],
 svgEdit
.
init
);


}(
jQuery
,
 mediaWiki
));