Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki

This commit is contained in:
Jacques Distler 2010-10-15 10:50:31 -05:00
commit d208728065
73 changed files with 1090 additions and 867 deletions

View file

@ -5,6 +5,23 @@ N.B.: You *must* run
after installing the new software, to enjoy the benefits of this new version.
------------------------------------------------------------------------------
* 0.19.1
New Features:
* From the "All" or Category listings, you can export selected pages (in any
desired order) to a single LaTeX file.
* LaTeX export supports \array{} (with no options) and a LaTeX-style optional
argument for \sqrt[]{}. The latter requires itextomml 1.4.5 or later.
* Updated to itextomml 1.4.5 (a bunch of new itex language features).
* Updated to Rails 2.3.10. (There were security issues in 2.3.9 which, happily,
did NOT affect Instiki 0.19. But 2.3.10 has other improvements, as well.)
Bugs Fixed:
* Several SVG-Edit bugs fixed.
* Removed some superfluous junk, to slim down the distribution (cuts the size of
the compressed .tar.gz nearly in half).
------------------------------------------------------------------------------
* 0.19

View file

@ -1,6 +1,6 @@
source "http://rubygems.org"
gem "sqlite3-ruby", :require => "sqlite3"
gem "itextomml", ">=1.4.2"
gem "itextomml", ">=1.4.5"
gem "mongrel", ">=1.2.0.pre2"
gem "rubyzip"
gem "erubis"

View file

@ -282,7 +282,7 @@ module Instiki
module VERSION #:nodoc:
MAJOR = 0
MINOR = 19
TINY = 0
TINY = 1
SUFFIX = '(MML+)'
PRERELEASE = false
if PRERELEASE

View file

@ -427,7 +427,7 @@ padding-left:0;
ul#sortable_pages li {
border: 1px solid #FFF;
}
ul#sortable_pages li:hover {
ul#sortable_pages li:hover, ul#sortable_pages li:focus {
border: 1px solid blue;
}

View file

@ -14,6 +14,9 @@
// and the MIT License and is copyright A Beautiful Site, LLC.
//
if(jQuery)( function() {
var win = $(window);
var doc = $(document);
$.extend($.fn, {
contextMenu: function(o, callback) {
@ -28,75 +31,78 @@ if(jQuery)( function() {
$(this).each( function() {
var el = $(this);
var offset = $(el).offset();
var menu = $('#' + o.menu);
// Add contextMenu class
$('#' + o.menu).addClass('contextMenu');
menu.addClass('contextMenu');
// Simulate a true right click
$(this).mousedown( function(e) {
var evt = e;
$(this).mouseup( function(e) {
var srcElement = $(this);
$(this).unbind('mouseup');
if( evt.button == 2 || o.allowLeft) {
srcElement.unbind('mouseup');
if( evt.button === 2 || o.allowLeft) {
e.stopPropagation();
// Hide context menus that may be showing
$(".contextMenu").hide();
// Get this context menu
var menu = $('#' + o.menu);
if( $(el).hasClass('disabled') ) return false;
if( el.hasClass('disabled') ) return false;
// Detect mouse position
var d = {}, x = e.pageX, y = e.pageY;
var x_off = $(window).width() - menu.width(),
y_off = $(window).height() - menu.height();
if(x > x_off) x = x_off-15;
if(y > y_off) y = y_off-15;
var x_off = win.width() - menu.width(),
y_off = win.height() - menu.height();
if(x > x_off - 15) x = x_off-15;
if(y > y_off - 30) y = y_off-30; // 30 is needed to prevent scrollbars in FF
// Show the menu
$(document).unbind('click');
$(menu).css({ top: y, left: x }).fadeIn(o.inSpeed);
doc.unbind('click');
menu.css({ top: y, left: x }).fadeIn(o.inSpeed);
// Hover events
$(menu).find('A').mouseover( function() {
$(menu).find('LI.hover').removeClass('hover');
menu.find('A').mouseover( function() {
menu.find('LI.hover').removeClass('hover');
$(this).parent().addClass('hover');
}).mouseout( function() {
$(menu).find('LI.hover').removeClass('hover');
menu.find('LI.hover').removeClass('hover');
});
// Keyboard
$(document).keypress( function(e) {
doc.keypress( function(e) {
switch( e.keyCode ) {
case 38: // up
if( $(menu).find('LI.hover').size() == 0 ) {
$(menu).find('LI:last').addClass('hover');
if( !menu.find('LI.hover').length ) {
menu.find('LI:last').addClass('hover');
} else {
$(menu).find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:last').addClass('hover');
menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
if( !menu.find('LI.hover').length ) menu.find('LI:last').addClass('hover');
}
break;
case 40: // down
if( $(menu).find('LI.hover').size() == 0 ) {
$(menu).find('LI:first').addClass('hover');
if( menu.find('LI.hover').length == 0 ) {
menu.find('LI:first').addClass('hover');
} else {
$(menu).find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
if( $(menu).find('LI.hover').size() == 0 ) $(menu).find('LI:first').addClass('hover');
menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
if( !menu.find('LI.hover').length ) menu.find('LI:first').addClass('hover');
}
break;
case 13: // enter
$(menu).find('LI.hover A').trigger('click');
menu.find('LI.hover A').trigger('click');
break;
case 27: // esc
$(document).trigger('click');
doc.trigger('click');
break
}
});
// When items are selected
$('#' + o.menu).find('A').unbind('mouseup');
$('#' + o.menu).find('LI:not(.disabled) A').mouseup( function() {
$(document).unbind('click').unbind('keypress');
menu.find('A').unbind('mouseup');
menu.find('LI:not(.disabled) A').mouseup( function() {
doc.unbind('click').unbind('keypress');
$(".contextMenu").hide();
// Callback
if( callback ) callback( $(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y} );
@ -105,9 +111,9 @@ if(jQuery)( function() {
// Hide bindings
setTimeout( function() { // Delay for Mozilla
$(document).click( function() {
$(document).unbind('click').unbind('keypress');
$(menu).fadeOut(o.outSpeed);
doc.click( function() {
doc.unbind('click').unbind('keypress');
menu.fadeOut(o.outSpeed);
return false;
});
}, 0);

View file

@ -289,10 +289,11 @@ svgEditor.addExtension("imagelib", function() {
var leftBlock = $('<span>').css({position:'absolute',top:5,left:10}).appendTo(browser);
var back = $('<button>Show libraries</button>').appendTo(leftBlock).click(function() {
var back = $('<button hidden>Show library list</button>').appendTo(leftBlock).click(function() {
frame.attr('src', 'about:blank').hide();
lib_opts.show();
header.text(all_libs);
back.hide();
}).css({
'margin-right': 5
});
@ -321,6 +322,7 @@ svgEditor.addExtension("imagelib", function() {
frame.attr('src', opts.url).show();
header.text(opts.name);
lib_opts.hide();
back.show();
}).append('<span>' + opts.description + '</span>');
});

View file

@ -1,191 +0,0 @@
.jPicker_Picker {
display: inline-block;
height: 24px; /* change this value if using a different sized color picker icon */
position: relative; /* make this element an absolute positioning container */
text-align: left; /* make the zero width children position to the left of container */
width: 25px; /* change this value if using a different sized color picker icon */
}
.jPicker_Color, .jPicker_Alpha {
background-position: 2px 2px;
display: block;
height: 100%;
left: 0px;
position: absolute;
top: 0px;
width: 100%;
}
.jPicker_Icon {
background-repeat: no-repeat;
cursor: pointer;
display: block;
height: 100%;
left: 0px;
position: absolute;
top: 0px;
width: 100%;
}
.jPicker_Container {
display: none;
z-index: 10; /* make sure container draws above color picker icon in Firefox/Safari/Chrome/Opera/etc. -
IE calculates z-index so this won't work - we will hide all color picker icons placed after the selected one in code when shown in IE */
}
.jPicker_table {
background-color: #efefef;
border: 1px outset #666;
font-family: Arial, Helvetica, Sans-Serif;
font-size: 12px;
margin: 0px;
padding: 5px;
width: 550px;
}
.jPicker_table td {
margin: 0px;
padding: 0px;
vertical-align: top;
}
.jPicker_MoveBar {
background-color: #dddddd;
border: 1px outset #aaa;
cursor: move;
height: 12px;
}
.jPicker_Title {
font-size: 11px !important;
font-weight: bold;
margin: -2px 0px 0px 0px;
padding: 0px;
text-align: center;
width: 100%;
}
.jPicker_ColorMap {
border: 2px inset #eee;
cursor: crosshair;
height: 260px; /* IE 6 incorrectly draws border inside the width and height instead of outside - We will fix this to 256px later */
margin: 0px 5px 0px 5px;
overflow: hidden; /* hide the overdraw of the Color Map icon when at edge of viewing box */
padding: 0px;
position: relative; /* make this element an absolute positioning container */
width: 260px; /* IE 6 incorrectly draws border inside the width and height instead of outside - We will fix this to 256px later */
}
div[class="jPicker_ColorMap"] {
height: 256px; /* correct to 256px for browsers that support the "[class="xxx"]" selector (IE7+,Firefox,Safari,Chrome,Opera,etc.) */
width: 256px; /* correct to 256px for browsers that support the "[class="xxx"]" selector (IE7+,Firefox,Safari,Chrome,Opera,etc.) */
}
.jPicker_ColorBar {
border: 2px inset #eee;
cursor: n-resize;
height: 260px; /* IE 6 incorrectly draws border inside the width and height instead of outside - We will fix this to 256px later */
margin: 12px 10px 0px 5px;
padding: 0px;
position: relative;
width: 24px; /* IE 6 incorrectly draws border inside the width and height instead of outside - We will fix this to 20px later */
}
div[class="jPicker_ColorBar"] {
height: 256px; /* correct to 256px for browsers that support the "[class="xxx"]" selector (IE7+,Firefox,Safari,Chrome,Opera,etc.) */
width: 20px; /* correct to 20px for browsers that support the "[class="xxx"]" selector (IE7+,Firefox,Safari,Chrome,Opera,etc.) */
}
.jPicker_ColorMap_l1, .jPicker_ColorMap_l2, .jPicker_ColorMap_l3, .jPicker_ColorBar_l1, .jPicker_ColorBar_l2, .jPicker_ColorBar_l3, .jPicker_ColorBar_l4, .jPicker_ColorBar_l5, .jPicker_ColorBar_l6 {
background-color: transparent;
background-image: none;
display: block;
height: 256px; /* must specific pixel height. IE7/8 Quirks mode ignores opacity for an absolutely positioned item in a relative container with "overflow: visible". The marker in the colorBar
would not be drawn if its overflow is set to hidden. */
left: 0px;
position: absolute;
top: 0px;
}
.jPicker_ColorMap_l1, .jPicker_ColorMap_l2, .jPicker_ColorMap_l3 {
width: 256px; /* must specific pixel width. IE7/8 Quirks mode ignores opacity for an absolutely positioned item in a relative container with "overflow: visible". The marker in the colorBar
would not be drawn if its overflow is set to hidden. */
}
.jPicker_ColorBar_l1, .jPicker_ColorBar_l2, .jPicker_ColorBar_l3, .jPicker_ColorBar_l4, .jPicker_ColorBar_l5, .jPicker_ColorBar_l6 {
width: 20px; /* must specific pixel width. IE7/8 Quirks mode ignores opacity for an absolutely positioned item in a relative container with "overflow: visible". The marker in the colorBar
would not be drawn if its overflow is set to hidden. */
}
.jPicker_ColorMap_l1, .jPicker_ColorMap_l2, .jPicker_ColorBar_l6 {
background-repeat: no-repeat;
}
.jPicker_ColorMap_l3, .jPicker_ColorBar_l5 {
background-repeat: repeat;
}
.jPicker_ColorBar_l1, .jPicker_ColorBar_l2, .jPicker_ColorBar_l3, .jPicker_ColorBar_l4 {
background-repeat: repeat-x;
}
.jPicker_ColorMap_Arrow {
display: block;
position: absolute;
}
.jPicker_ColorBar_Arrow {
display: block;
left: -10px; /* (arrow width / 2) - (element width / 2) - position arrows' center in elements' center */
position: absolute;
}
.jPicker_Preview {
font-size: x-small;
text-align: center;
}
.jPicker_Preview div {
border: 2px inset #eee;
height: 62px;
margin: 0px auto;
padding: 0px;
width: 62px;
}
.jPicker_Preview div span {
border: 1px solid #000;
display: block;
height: 30px;
margin: 0px auto;
padding: 0px;
width: 60px;
}
.jPicker_Preview div span.jPicker_Active {
border-bottom-width: 0px;
}
.jPicker_Preview div span.jPicker_Current {
border-top-width: 0px;
cursor: pointer;
}
.jPicker_OkCancel {
text-align: center;
width: 120px;
}
.jPicker_OkCancel input {
width: 100px;
}
.jPicker_OkCancel input.jPicker_Ok {
margin: 12px 0px 5px 0px;
}
.jPicker_Text {
text-align: left;
}
.jPicker_HueText, .jPicker_SaturationText, .jPicker_BrightnessText, .jPicker_RedText, .jPicker_GreenText, .jPicker_BlueText, .jPicker_AlphaText {
background-color: #fff;
border: 1px inset #aaa;
margin: 0px 0px 0px 5px;
text-align: left;
width: 30px;
}
.jPicker_HexText {
background-color: #fff;
border: 1px inset #aaa;
margin: 0px 0px 0px 5px;
width: 65px;
}
.jPicker_Grid {
text-align: center;
}
span.jPicker_QuickColor {
border: 1px inset #aaa;
cursor: pointer;
display: block;
float: left;
height: 13px;
line-height: 13px;
margin: 2px 2px 1px 2px;
padding: 0px;
width: 15px;
}
span[class="jPicker_QuickColor"] {
width: 13px;
}

View file

@ -0,0 +1 @@
.jPicker .Icon{display:inline-block;height:24px;position:relative;text-align:left;width:25px}.jPicker .Icon span.Color,.jPicker .Icon span.Alpha{background-position:2px 2px;display:block;height:100%;left:0;position:absolute;top:0;width:100%}.jPicker .Icon span.Image{background-repeat:no-repeat;cursor:pointer;display:block;height:100%;left:0;position:absolute;top:0;width:100%}.jPicker.Container{z-index:10}table.jPicker{background-color:#efefef;border:1px outset #666;font-family:Arial,Helvetica,Sans-Serif;font-size:12px!important;margin:0;padding:5px;width:545px;z-index:20}.jPicker .Move{background-color:#ddd;border-color:#fff #666 #666 #fff;border-style:solid;border-width:1px;cursor:move;height:12px;padding:0}.jPicker .Title{font-size:11px!important;font-weight:bold;margin:-2px 0 0 0;padding:0;text-align:center;width:100%}.jPicker div.Map{border-bottom:2px solid #fff;border-left:2px solid #9a9a9a;border-right:2px solid #fff;border-top:2px solid #9a9a9a;cursor:crosshair;height:260px;margin:0 5px 0 5px;overflow:hidden;padding:0;position:relative;width:260px}.jPicker div[class="Map"]{height:256px;width:256px}.jPicker div.Bar{border-bottom:2px solid #fff;border-left:2px solid #9a9a9a;border-right:2px solid #fff;border-top:2px solid #9a9a9a;cursor:n-resize;height:260px;margin:12px 10px 0 5px;overflow:hidden;padding:0;position:relative;width:24px}.jPicker div[class="Bar"]{height:256px;width:20px}.jPicker .Map .Map1,.jPicker .Map .Map2,.jPicker .Map .Map3,.jPicker .Bar .Map1,.jPicker .Bar .Map2,.jPicker .Bar .Map3,.jPicker .Bar .Map4,.jPicker .Bar .Map5,.jPicker .Bar .Map6{background-color:transparent;background-image:none;display:block;left:0;position:absolute;top:0}.jPicker .Map .Map1,.jPicker .Map .Map2,.jPicker .Map .Map3{height:2596px;width:256px}.jPicker .Bar .Map1,.jPicker .Bar .Map2,.jPicker .Bar .Map3,.jPicker .Bar .Map4{height:3896px;width:20px}.jPicker .Bar .Map5,.jPicker .Bar .Map6{height:256px;width:20px}.jPicker .Map .Map1,.jPicker .Map .Map2,.jPicker .Bar .Map6{background-repeat:no-repeat}.jPicker .Map .Map3,.jPicker .Bar .Map5{background-repeat:repeat}.jPicker .Bar .Map1,.jPicker .Bar .Map2,.jPicker .Bar .Map3,.jPicker .Bar .Map4{background-repeat:repeat-x}.jPicker .Map .Arrow{display:block;position:absolute}.jPicker .Bar .Arrow{display:block;left:0;position:absolute}.jPicker .Preview{font-size:9px;text-align:center}.jPicker .Preview div{border:2px inset #eee;height:62px;margin:0 auto;padding:0;width:62px}.jPicker .Preview div span{border:1px solid #000;display:block;height:30px;margin:0 auto;padding:0;width:60px}.jPicker .Preview .Active{border-bottom-width:0}.jPicker .Preview .Current{border-top-width:0;cursor:pointer}.jPicker .Button{text-align:center;width:115px}.jPicker .Button input{width:100px}.jPicker .Button .Ok{margin:12px 0 5px 0}.jPicker td.Radio{margin:0;padding:0;width:31px}.jPicker td.Radio input{margin:0 5px 0 0;padding:0}.jPicker td.Text{font-size:12px!important;height:22px;margin:0;padding:0;text-align:left;width:70px}.jPicker tr.Hex td.Text{width:100px}.jPicker td.Text input{background-color:#fff;border:1px inset #aaa;height:19px;margin:0 0 0 5px;text-align:left;width:30px}.jPicker td[class="Text"] input{height:15px}.jPicker tr.Hex td.Text input.Hex{width:50px}.jPicker tr.Hex td.Text input.AHex{width:20px}.jPicker .Grid{text-align:center;width:114px}.jPicker .Grid span.QuickColor{border:1px inset #aaa;cursor:pointer;display:inline-block;height:15px;line-height:15px;margin:0;padding:0;width:19px}.jPicker .Grid span[class="QuickColor"]{width:17px}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 B

After

Width:  |  Height:  |  Size: 76 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -61,7 +61,7 @@ $.jGraduate = {
Paint:
function(opt) {
var options = opt || {};
this.alpha = options.alpha || 100;
this.alpha = isNaN(options.alpha) ? 100 : options.alpha;
// copy paint object
if (options.copy) {
this.type = options.copy.type;
@ -567,8 +567,8 @@ jQuery.fn.jGraduate =
images: { clientPath: $settings.images.clientPath },
color: { active: color, alphaSupport: true }
}, function(color){
beginColor = color.get_Hex() ? ('#'+color.get_Hex()) : "none";
beginOpacity = color.get_A() ? color.get_A()/100 : 1;
beginColor = color.val('hex') ? ('#'+color.val('hex')) : "none";
beginOpacity = color.val('ahex') ? color.val('ahex')/100 : 1;
colorbox.css('background', beginColor);
$('#'+id+'_jGraduate_beginOpacity').html(parseInt(beginOpacity*100)+'%');
stops[0].setAttribute('stop-color', beginColor);
@ -591,8 +591,8 @@ jQuery.fn.jGraduate =
images: { clientPath: $settings.images.clientPath },
color: { active: color, alphaSupport: true }
}, function(color){
endColor = color.get_Hex() ? ('#'+color.get_Hex()) : "none";
endOpacity = color.get_A() ? color.get_A()/100 : 1;
endColor = color.val('hex') ? ('#'+color.val('hex')) : "none";
endOpacity = color.val('ahex') ? color.val('ahex')/100 : 1;
colorbox.css('background', endColor);
$('#'+id+'_jGraduate_endOpacity').html(parseInt(endOpacity*100)+'%');
stops[1].setAttribute('stop-color', endColor);
@ -605,29 +605,8 @@ jQuery.fn.jGraduate =
});
});
// --------------
var thisAlpha = ($this.paint.alpha*255/100).toString(16);
while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; }
color = $this.paint.solidColor == "none" ? "" : $this.paint.solidColor + thisAlpha;
colPicker.jPicker(
{
window: { title: $settings.window.pickerTitle },
images: { clientPath: $settings.images.clientPath },
color: { active: color, alphaSupport: true }
},
function(color) {
$this.paint.type = "solidColor";
$this.paint.alpha = color.get_A() ? color.get_A() : 100;
$this.paint.solidColor = color.get_Hex() ? color.get_Hex() : "none";
$this.paint.linearGradient = null;
okClicked();
},
null,
function(){ cancelClicked(); }
);
}());
// Radial gradient
(function() {
var svg = document.getElementById(id + '_rg_jgraduate_svg');
@ -984,8 +963,8 @@ jQuery.fn.jGraduate =
images: { clientPath: $settings.images.clientPath },
color: { active: color, alphaSupport: true }
}, function(color){
centerColor = color.get_Hex() ? ('#'+color.get_Hex()) : "none";
centerOpacity = color.get_A() ? color.get_A()/100 : 1;
centerColor = color.val('hex') ? ('#'+color.val('hex')) : "none";
centerOpacity = color.val('ahex') ? color.val('ahex')/100 : 1;
colorbox.css('background', centerColor);
$('#'+id+'_rg_jGraduate_centerOpacity').html(parseInt(centerOpacity*100)+'%');
stops[0].setAttribute('stop-color', centerColor);
@ -1008,8 +987,8 @@ jQuery.fn.jGraduate =
images: { clientPath: $settings.images.clientPath },
color: { active: color, alphaSupport: true }
}, function(color){
outerColor = color.get_Hex() ? ('#'+color.get_Hex()) : "none";
outerOpacity = color.get_A() ? color.get_A()/100 : 1;
outerColor = color.val('hex') ? ('#'+color.val('hex')) : "none";
outerOpacity = color.val('ahex') ? color.val('ahex')/100 : 1;
colorbox.css('background', outerColor);
$('#'+id+'_jGraduate_outerOpacity').html(parseInt(outerOpacity*100)+'%');
stops[1].setAttribute('stop-color', outerColor);
@ -1025,7 +1004,14 @@ jQuery.fn.jGraduate =
// --------------
var thisAlpha = ($this.paint.alpha*255/100).toString(16);
while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; }
thisAlpha = thisAlpha.split(".")[0];
color = $this.paint.solidColor == "none" ? "" : $this.paint.solidColor + thisAlpha;
// This should be done somewhere else, probably
$.extend($.fn.jPicker.defaults.window, {
alphaSupport: true, effects: {type: 'show',speed: 0}
});
colPicker.jPicker(
{
window: { title: $settings.window.pickerTitle },
@ -1034,8 +1020,8 @@ jQuery.fn.jGraduate =
},
function(color) {
$this.paint.type = "solidColor";
$this.paint.alpha = color.get_A() ? color.get_A() : 100;
$this.paint.solidColor = color.get_Hex() ? color.get_Hex() : "none";
$this.paint.alpha = color.val('ahex') ? Math.round((color.val('a') / 255) * 100) : 100;
$this.paint.solidColor = color.val('hex') ? color.val('hex') : "none";
$this.paint.radialGradient = null;
okClicked();
},
@ -1051,7 +1037,6 @@ jQuery.fn.jGraduate =
$(idref + " > div").hide();
$(idref + ' .jGraduate_' + $(this).attr('data-type') + 'Pick').show();
});
$(idref + " > div").hide();
tabs.removeClass('jGraduate_tab_current');
var tab;
@ -1066,9 +1051,12 @@ jQuery.fn.jGraduate =
tab = $(idref + ' .jGraduate_tab_color');
break;
}
tab.addClass('jGraduate_tab_current').click();
$this.show();
// jPicker will try to show after a 0ms timeout, so need to fire this after that
setTimeout(function() {
tab.addClass('jGraduate_tab_current').click();
}, 10);
});
};
})();

View file

@ -1,173 +1,176 @@
[
{"id": "align_relative_to", "title": "Allineati alla ..."},
{"id": "bkgnd_color", "title": "Cambia il colore di sfondo / opacità"},
{"id": "circle_cx", "title": "Cx cerchio Modifica di coordinate"},
{"id": "circle_cy", "title": "Cambia&#39;s circle CY coordinare"},
{"id": "align_relative_to", "title": "Allineati a ..."},
{"id": "bkgnd_color", "title": "Cambia colore/opacità dello sfondo"},
{"id": "circle_cx", "title": "Cambia la coordinata Cx del cerchio"},
{"id": "circle_cy", "title": "Cambia la coordinata Cy del cerchio"},
{"id": "circle_r", "title": "Cambia il raggio del cerchio"},
{"id": "connector_no_arrow", "textContent": "No arrow"},
{"id": "connector_no_arrow", "textContent": "No freccia"},
{"id": "copyrightLabel", "textContent": "Powered by"},
{"id": "cornerRadiusLabel", "title": "Cambia Rectangle Corner Radius"},
{"id": "curve_segments", "textContent": "Curve"},
{"id": "ellipse_cx", "title": "Cambia dell&#39;ellisse cx coordinare"},
{"id": "ellipse_cy", "title": "Ellisse Cambia&#39;s CY coordinare"},
{"id": "ellipse_rx", "title": "Raggio x ellisse Cambia&#39;s"},
{"id": "ellipse_ry", "title": "Raggio y ellisse Cambia&#39;s"},
{"id": "cornerRadiusLabel", "title": "Cambia il raggio dell'angolo"},
{"id": "curve_segments", "textContent": "Curva"},
{"id": "ellipse_cx", "title": "Cambia la coordinata Cx dell'ellisse"},
{"id": "ellipse_cy", "title": "Cambia la coordinata Cy dell'ellisse"},
{"id": "ellipse_rx", "title": "Cambia l'asse x dell'ellisse"},
{"id": "ellipse_ry", "title": "Cambia l'asse y dell'ellisse"},
{"id": "fill_color", "title": "Cambia il colore di riempimento"},
{"id": "fitToContent", "textContent": "Adatta al contenuto"},
{"id": "fit_to_all", "textContent": "Adatta a tutti i contenuti"},
{"id": "fit_to_canvas", "textContent": "Adatta alla tela"},
{"id": "fit_to_layer_content", "textContent": "Adatta a livello di contenuti"},
{"id": "fit_to_canvas", "textContent": "Adatta all'area di disegno"},
{"id": "fit_to_layer_content", "textContent": "Adatta al contenuto del livello"},
{"id": "fit_to_sel", "textContent": "Adatta alla selezione"},
{"id": "font_family", "title": "Change Font Family"},
{"id": "icon_large", "textContent": "Large"},
{"id": "icon_medium", "textContent": "Medium"},
{"id": "icon_small", "textContent": "Small"},
{"id": "icon_xlarge", "textContent": "Extra Large"},
{"id": "image_height", "title": "Cambia l&#39;altezza dell&#39;immagine"},
{"id": "image_opt_embed", "textContent": "Embed data (local files)"},
{"id": "image_opt_ref", "textContent": "Use file reference"},
{"id": "font_family", "title": "Cambia il tipo di Font"},
{"id": "icon_large", "textContent": "Grande"},
{"id": "icon_medium", "textContent": "Medio"},
{"id": "icon_small", "textContent": "Piccolo"},
{"id": "icon_xlarge", "textContent": "Molto grande"},
{"id": "idLabel", "title": "Identifica l'elemento"},
{"id": "image_height", "title": "Cambia l'altezza dell'immagine"},
{"id": "image_opt_embed", "textContent": "Incorpora dati (file locali)"},
{"id": "image_opt_ref", "textContent": "Usa l'identificativo di riferimento"},
{"id": "image_url", "title": "Cambia URL"},
{"id": "image_width", "title": "Cambia la larghezza dell&#39;immagine"},
{"id": "includedImages", "textContent": "Included Images"},
{"id": "largest_object", "textContent": "il più grande oggetto"},
{"id": "layer_delete", "title": "Elimina livello"},
{"id": "layer_down", "title": "Move Layer Down"},
{"id": "image_width", "title": "Cambia la larghezza dell'immagine"},
{"id": "includedImages", "textContent": "Immagini incluse"},
{"id": "largest_object", "textContent": "Oggetto più grande"},
{"id": "layer_delete", "title": "Elimina il livello"},
{"id": "layer_down", "title": "Sposta indietro il livello"},
{"id": "layer_new", "title": "Nuovo livello"},
{"id": "layer_rename", "title": "Rinominare il livello"},
{"id": "layer_up", "title": "Move Layer Up"},
{"id": "layersLabel", "textContent": "Livelli:"},
{"id": "line_x1", "title": "Modifica la linea di partenza coordinata x"},
{"id": "line_x2", "title": "Modifica la linea di fine coordinata x"},
{"id": "line_y1", "title": "Modifica la linea di partenza coordinata y"},
{"id": "line_y2", "title": "Modifica la linea di fine coordinata y"},
{"id": "linecap_butt", "title": "Linecap: Butt"},
{"id": "linecap_round", "title": "Linecap: Round"},
{"id": "linecap_square", "title": "Linecap: Square"},
{"id": "linejoin_bevel", "title": "Linejoin: Bevel"},
{"id": "linejoin_miter", "title": "Linejoin: Miter"},
{"id": "linejoin_round", "title": "Linejoin: Round"},
{"id": "main_icon", "title": "Main Menu"},
{"id": "mode_connect", "title": "Connect two objects"},
{"id": "layer_rename", "title": "Rinomina il livello"},
{"id": "layer_up", "title": "Sposta avanti il livello"},
{"id": "layersLabel", "textContent": "Livello:"},
{"id": "line_x1", "title": "Modifica la coordinata iniziale x della linea"},
{"id": "line_x2", "title": "Modifica la coordinata finale x della linea"},
{"id": "line_y1", "title": "Modifica la coordinata iniziale y della linea"},
{"id": "line_y2", "title": "Modifica la coordinata finale y della linea"},
{"id": "linecap_butt", "title": "Inizio linea: Punto"},
{"id": "linecap_round", "title": "Inizio linea: Tondo"},
{"id": "linecap_square", "title": "Inizio linea: Quadrato"},
{"id": "linejoin_bevel", "title": "Giunzione: smussata"},
{"id": "linejoin_miter", "title": "Giunzione: spezzata"},
{"id": "linejoin_round", "title": "Giunzione: arrotondata"},
{"id": "main_icon", "title": "Menù principale"},
{"id": "mode_connect", "title": "Collega due oggetti"},
{"id": "page", "textContent": "Pagina"},
{"id": "palette", "title": "Fare clic per cambiare il colore di riempimento, shift-click per cambiare colore del tratto"},
{"id": "path_node_x", "title": "Change node's x coordinate"},
{"id": "path_node_y", "title": "Change node's y coordinate"},
{"id": "rect_height_tool", "title": "Cambia l&#39;altezza rettangolo"},
{"id": "path_node_x", "title": "Modifica la coordinata x del nodo"},
{"id": "path_node_y", "title": "Modifica la coordinata y del nodo"},
{"id": "rect_height_tool", "title": "Cambia l'altezza rettangolo"},
{"id": "rect_width_tool", "title": "Cambia la larghezza rettangolo"},
{"id": "relativeToLabel", "textContent": "rispetto al:"},
{"id": "seg_type", "title": "Change Segment type"},
{"id": "selLayerLabel", "textContent": "Move elements to:"},
{"id": "selLayerNames", "title": "Move selected elements to a different layer"},
{"id": "selectedPredefined", "textContent": "Seleziona predefinite:"},
{"id": "selected_objects", "textContent": "eletto oggetti"},
{"id": "selected_x", "title": "Change X coordinate"},
{"id": "selected_y", "title": "Change Y coordinate"},
{"id": "smallest_object", "textContent": "più piccolo oggetto"},
{"id": "straight_segments", "textContent": "Straight"},
{"id": "stroke_color", "title": "Cambia colore ictus"},
{"id": "stroke_style", "title": "Cambia lo stile dash ictus"},
{"id": "stroke_width", "title": "Cambia la larghezza ictus"},
{"id": "svginfo_bg_note", "textContent": "Note: Background will not be saved with image."},
{"id": "svginfo_change_background", "textContent": "Editor Background"},
{"id": "svginfo_dim", "textContent": "Canvas Dimensions"},
{"id": "svginfo_editor_prefs", "textContent": "Editor Preferences"},
{"id": "relativeToLabel", "textContent": "Rispetto a:"},
{"id": "seg_type", "title": "Cambia il tipo di segmento"},
{"id": "selLayerLabel", "textContent": "Sposta verso:"},
{"id": "selLayerNames", "title": "Sposta gli elementi in un diverso livello"},
{"id": "selectedPredefined", "textContent": "Selezioni predefinite:"},
{"id": "selected_objects", "textContent": "Oggetti selezionati"},
{"id": "selected_x", "title": "Modifica la coordinata x"},
{"id": "selected_y", "title": "Modifica la coordinata y"},
{"id": "smallest_object", "textContent": "Oggetto più piccolo"},
{"id": "straight_segments", "textContent": "Linea retta"},
{"id": "stroke_color", "title": "Cambia il colore del tratto"},
{"id": "stroke_style", "title": "Cambia lo stile del tratto"},
{"id": "stroke_width", "title": "Cambia la larghezza del tratto"},
{"id": "svginfo_bg_note", "textContent": "Nota: Lo sfondo non verrà salvato con l'immagine."},
{"id": "svginfo_change_background", "textContent": "Sfondo dell'editor"},
{"id": "svginfo_dim", "textContent": "Dimensioni dell'area di disegno"},
{"id": "svginfo_editor_prefs", "textContent": "Preferenze"},
{"id": "svginfo_height", "textContent": "Altezza:"},
{"id": "svginfo_icons", "textContent": "Icon size"},
{"id": "svginfo_image_props", "textContent": "Image Properties"},
{"id": "svginfo_lang", "textContent": "Language"},
{"id": "svginfo_title", "textContent": "Title"},
{"id": "svginfo_icons", "textContent": "Dimensione Icona"},
{"id": "svginfo_image_props", "textContent": "Proprietà Immagine"},
{"id": "svginfo_lang", "textContent": "Lingua"},
{"id": "svginfo_title", "textContent": "Titolo"},
{"id": "svginfo_width", "textContent": "Ampiezza:"},
{"id": "text", "title": "Cambia il contenuto del testo"},
{"id": "toggle_stroke_tools", "title": "Show/hide more stroke tools"},
{"id": "tool_add_subpath", "title": "Add sub-path"},
{"id": "toggle_stroke_tools", "title": "Mostra/nascondi strumenti per il tratto"},
{"id": "tool_add_subpath", "title": "Aggiungi sotto-percorso"},
{"id": "tool_alignbottom", "title": "Allinea in basso"},
{"id": "tool_aligncenter", "title": "Allinea al centro"},
{"id": "tool_alignleft", "title": "Allinea a sinistra"},
{"id": "tool_alignmiddle", "title": "Allinea al centro"},
{"id": "tool_alignright", "title": "Allinea a destra"},
{"id": "tool_aligntop", "title": "Allinea in alto"},
{"id": "tool_angle", "title": "Cambia l&#39;angolo di rotazione"},
{"id": "tool_blur", "title": "Change gaussian blur value"},
{"id": "tool_angle", "title": "Cambia l'angolo di rotazione"},
{"id": "tool_blur", "title": "Cambia l'intensità della sfocatura"},
{"id": "tool_bold", "title": "Grassetto"},
{"id": "tool_circle", "title": "Circle"},
{"id": "tool_clear", "textContent": "New Image"},
{"id": "tool_clone", "title": "Clone Element"},
{"id": "tool_clone_multi", "title": "Clone Elements"},
{"id": "tool_delete", "title": "Cancellare l&#39;elemento"},
{"id": "tool_delete_multi", "title": "Elimina elementi selezionati"},
{"id": "tool_circle", "title": "Cerchio"},
{"id": "tool_clear", "textContent": "Nuova immagine"},
{"id": "tool_clone", "title": "Clona l'elemento"},
{"id": "tool_clone_multi", "title": "Clona più elementi"},
{"id": "tool_delete", "title": "Cancella l'elemento"},
{"id": "tool_delete_multi", "title": "Elimina gli elementi selezionati"},
{"id": "tool_docprops", "textContent": "Proprietà del documento"},
{"id": "tool_docprops_cancel", "textContent": "Annulla"},
{"id": "tool_docprops_save", "textContent": "Salvare"},
{"id": "tool_ellipse", "title": "Ellipse"},
{"id": "tool_export", "textContent": "Export as PNG"},
{"id": "tool_eyedropper", "title": "Eye Dropper Tool"},
{"id": "tool_fhellipse", "title": "Free-Hand Ellipse"},
{"id": "tool_fhpath", "title": "Lo strumento matita"},
{"id": "tool_fhrect", "title": "Free-Hand Rectangle"},
{"id": "tool_docprops_save", "textContent": "Salva"},
{"id": "tool_ellipse", "title": "Ellisse"},
{"id": "tool_export", "textContent": "Esporta come PNG"},
{"id": "tool_eyedropper", "title": "Seleziona colore"},
{"id": "tool_fhellipse", "title": "Ellisse a mano libera"},
{"id": "tool_fhpath", "title": "Matita"},
{"id": "tool_fhrect", "title": "Rettangolo a mano libera"},
{"id": "tool_font_size", "title": "Modifica dimensione carattere"},
{"id": "tool_group", "title": "Group Elements"},
{"id": "tool_image", "title": "Image Tool"},
{"id": "tool_import", "textContent": "Import SVG"},
{"id": "tool_group", "title": "Raggruppa elementi"},
{"id": "tool_image", "title": "Immagine"},
{"id": "tool_import", "textContent": "Importa SVG"},
{"id": "tool_italic", "title": "Corsivo"},
{"id": "tool_line", "title": "Line Tool"},
{"id": "tool_move_bottom", "title": "Move to Bottom"},
{"id": "tool_move_top", "title": "Move to Top"},
{"id": "tool_node_clone", "title": "Clone Node"},
{"id": "tool_node_delete", "title": "Delete Node"},
{"id": "tool_node_link", "title": "Link Control Points"},
{"id": "tool_opacity", "title": "Cambia l&#39;opacità dell&#39;oggetto selezionato"},
{"id": "tool_line", "title": "Linea"},
{"id": "tool_move_bottom", "title": "Sposta in fondo"},
{"id": "tool_move_top", "title": "Sposta in cima"},
{"id": "tool_node_clone", "title": "Clona nodo"},
{"id": "tool_node_delete", "title": "Elimina nodo"},
{"id": "tool_node_link", "title": "Collegamento tra punti di controllo"},
{"id": "tool_opacity", "title": "Cambia l'opacità dell'oggetto selezionato"},
{"id": "tool_open", "textContent": "Apri immagine"},
{"id": "tool_path", "title": "Path Tool"},
{"id": "tool_openclose_path", "title": "Apri/chiudi spezzata"},
{"id": "tool_path", "title": "Spezzata"},
{"id": "tool_position", "title": "Allinea elementi alla pagina"},
{"id": "tool_rect", "title": "Rettangolo"},
{"id": "tool_redo", "title": "Redo"},
{"id": "tool_reorient", "title": "Reorient path"},
{"id": "tool_save", "textContent": "Salvare l&#39;immagine"},
{"id": "tool_select", "title": "Selezionare Tool"},
{"id": "tool_source", "title": "Edit Source"},
{"id": "tool_redo", "title": "Rifai"},
{"id": "tool_reorient", "title": "Riallinea"},
{"id": "tool_save", "textContent": "Salva"},
{"id": "tool_select", "title": "Seleziona"},
{"id": "tool_source", "title": "Modifica sorgente"},
{"id": "tool_source_cancel", "textContent": "Annulla"},
{"id": "tool_source_save", "textContent": "Salvare"},
{"id": "tool_square", "title": "Piazza"},
{"id": "tool_text", "title": "Strumento Testo"},
{"id": "tool_topath", "title": "Convert to Path"},
{"id": "tool_source_save", "textContent": "Salva"},
{"id": "tool_square", "title": "Quadrato"},
{"id": "tool_text", "title": "Testo"},
{"id": "tool_topath", "title": "Converti in tracciato"},
{"id": "tool_undo", "title": "Annulla"},
{"id": "tool_ungroup", "title": "Separa Elements"},
{"id": "tool_wireframe", "title": "Wireframe Mode"},
{"id": "tool_zoom", "title": "Zoom Tool"},
{"id": "url_notice", "title": "NOTE: This image cannot be embedded. It will depend on this path to be displayed"},
{"id": "tool_ungroup", "title": "Separa gli elementi"},
{"id": "tool_wireframe", "title": "Contorno"},
{"id": "tool_zoom", "title": "Zoom"},
{"id": "url_notice", "title": "NOTA: L'immagine non può essere incorporata: dipenderà dal percorso assoluto per essere vista"},
{"id": "zoom_panel", "title": "Cambia il livello di zoom"},
{"id": "sidepanel_handle", "textContent": "L a y e r s", "title": "Drag left/right to resize side panel"},
{"id": "sidepanel_handle", "textContent": "L a y e r s", "titolo": "Trascina a sinistra/destra per ridimensionare il pannello"},
{
"js_strings": {
"QerrorsRevertToSource": "There were parsing errors in your SVG source.\nRevert back to original SVG source?",
"QignoreSourceChanges": "Ignore changes made to SVG source?",
"QmoveElemsToLayer": "Move selected elements to layer '%s'?",
"QwantToClear": "Do you want to clear the drawing?\nThis will also erase your undo history!",
"cancel": "Cancel",
"defsFailOnSave": "NOTE: Due to a bug in your browser, this image may appear wrong (missing gradients or elements). It will however appear correct once actually saved.",
"dupeLayerName": "There is already a layer named that!",
"enterNewImgURL": "Enter the new image URL",
"enterNewLayerName": "Please enter the new layer name",
"enterUniqueLayerName": "Please enter a unique layer name",
"exportNoBlur": "Blurred elements will appear as un-blurred",
"exportNoDashArray": "Strokes will appear filled",
"exportNoImage": "Image elements will not appear",
"exportNoText": "Text may not appear as expected",
"exportNoforeignObject": "foreignObject elements will not appear",
"featNotSupported": "Feature not supported",
"invalidAttrValGiven": "Invalid value given",
"QerrorsRevertToSource": "Ci sono errori nel codice sorgente SVG.\nRitorno al codice originale?",
"QignoreSourceChanges": "Ignoro i cambiamenti nel sorgente SVG?",
"QmoveElemsToLayer": "Sposta gli elementi selezionali al livello '%s'?",
"QwantToClear": "Vuoi cancellare il disegno?\nVerrà eliminato anche lo storico delle modifiche!",
"cancel": "Annulla",
"defsFailOnSave": "NOTA: A causa dlle caratteristiche del tuo browser, l'immagine potrà apparire errata (senza elementi o gradazioni) finché non sarà salvata.",
"dupeLayerName": "C'è già un livello con questo nome!",
"enterNewImgURL": "Scrivi un nuovo URL per l'immagine",
"enterNewLayerName": "Assegna un nome al livello",
"enterUniqueLayerName": "Assegna un diverso nome a ciascun livello, grazie!",
"exportNoBlur": "Gli elementi sfocati appariranno non sfocati",
"exportNoDashArray": "I tratti appariranno pieni",
"exportNoImage": "Elementi dell'immagine non compariranno",
"exportNoText": "Il testo non apparirà come indicato",
"exportNoforeignObject": "Gli elementi dell'oggetto esterno non potranno essere visti",
"featNotSupported": "Caratteristica non supportata",
"invalidAttrValGiven": "Valore assegnato non valido",
"key_backspace": "backspace",
"key_del": "delete",
"key_down": "down",
"key_up": "up",
"layer": "Layer",
"layerHasThatName": "Layer already has that name",
"loadingImage": "Loading image, please wait...",
"noContentToFitTo": "No content to fit to",
"noteTheseIssues": "Also note the following issues: ",
"key_del": "Canc",
"key_down": "giù",
"key_up": "su",
"layer": "Livello",
"layerHasThatName": "Un livello ha già questo nome",
"loadingImage": "Sto caricando l'immagine. attendere prego...",
"noContentToFitTo": "Non c'è contenuto cui adeguarsi",
"noteTheseIssues": "Nota le seguenti particolarità: ",
"ok": "OK",
"pathCtrlPtTooltip": "Drag control point to adjust curve properties",
"pathNodeTooltip": "Drag node to move it. Double-click node to change segment type",
"saveFromBrowser": "Select \"Save As...\" in your browser to save this image as a %s file."
"pathCtrlPtTooltip": "Trascina il punto di controllo per assestare le proprietà della curva",
"pathNodeTooltip": "Trascina il nodo per spostarlo. Doppo click per cambiare i tipo di segmento",
"saveFromBrowser": "Seleziona \"Salva con nome...\" nel browser per salvare l'immagine con nome %s ."
}
}
]

View file

@ -5,7 +5,7 @@
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
<link rel="icon" type="image/png" href="images/logo.png"/>
<link rel="stylesheet" href="jgraduate/css/jPicker-1.0.12.css" type="text/css"/>
<link rel="stylesheet" href="jgraduate/css/jPicker.css" type="text/css"/>
<link rel="stylesheet" href="jgraduate/css/jgraduate.css" type="text/css"/>
<link rel="stylesheet" href="svg-editor.css" type="text/css"/>
<link rel="stylesheet" href="spinbtn/JQuerySpinBtn.css" type="text/css"/>
@ -37,7 +37,7 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<!-- always minified scripts -->
<script type="text/javascript" src="jquery-ui/jquery-ui-1.8.custom.min.js"></script>
<script type="text/javascript" src="jgraduate/jpicker-1.0.12.min.js"></script>
<script type="text/javascript" src="jgraduate/jpicker.min.js"></script>
<!-- feeds -->
<link rel="alternate" type="application/atom+xml" title="SVG-edit General Discussion" href="http://groups.google.com/group/svg-edit/feed/atom_v1_0_msgs.xml" />
@ -664,6 +664,7 @@ script type="text/javascript" src="locale/locale.min.js"></script-->
<option id="lang_fr" value="fr">Français</option>
<option id="lang_fy" value="fy">Frysk</option>
<option id="lang_hi" value="hi">&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;, &#2361;&#2367;&#2306;&#2342;&#2368;</option>
<option id="lang_it" value="it">Italiano</option>
<option id="lang_ja" value="ja">日本語</option>
<option id="lang_nl" value="nl">Nederlands</option>
<option id="lang_pt-BR" value="pt-BR">Português (BR)</option>

View file

@ -207,10 +207,15 @@
svgEditor.setConfig(urldata);
// FIXME: This is null if Data URL ends with '='.
var src = urldata.source;
var qstr = $.param.querystring();
if(!src) { // urldata.source may have been null if it ended with '='
if(qstr.indexOf('source=data:') >= 0) {
src = qstr.match(/source=(data:[^&]*)/)[1];
}
}
if(src) {
if(src.indexOf("data:") === 0) {
// plusses get replaced by spaces, so re-insert
@ -474,7 +479,9 @@
tool_scale = 1,
zoomInIcon = 'crosshair',
zoomOutIcon = 'crosshair',
ui_context = 'toolbars';
ui_context = 'toolbars',
orig_source = '',
paintBox = {fill: null, stroke:null};
// This sets up alternative dialog boxes. They mostly work the same way as
// their UI counterparts, expect instead of returning the result, a callback
@ -564,9 +571,6 @@
var cur_context = '';
var orig_title = $('title:first').text();
var fillPaint = new $.jGraduate.Paint({solidColor: curConfig.initFill.color});
var strokePaint = new $.jGraduate.Paint({solidColor: curConfig.initStroke.color});
var saveHandler = function(window,svg) {
show_save_warning = false;
@ -584,7 +588,6 @@
showSourceEditor(0,true);
return;
}
var win = window.open("data:image/svg+xml;base64," + Utils.encode64(svg));
// Alert will only appear the first time saved OR the first time the bug is encountered
@ -656,8 +659,7 @@
// upon creation of a text element the editor is switched into
// select mode and this event fires - we need our UI to be in sync
if (mode !== "multiselect" && !is_node) {
// FIXME: This also needs to fire if only one element is selected via multiselect
if (!is_node) {
updateToolbar();
}
@ -675,7 +677,8 @@
// called when any element has changed
var elementChanged = function(window,elems) {
if(svgCanvas.getMode() == "select") {
var mode = svgCanvas.getMode();
if(mode === "select") {
setSelectMode();
}
@ -683,7 +686,7 @@
var elem = elems[i];
// if the element changed was the svg, then it could be a resolution change
if (elem && elem.tagName == "svg") {
if (elem && elem.tagName === "svg") {
populateLayers();
updateCanvas();
}
@ -706,6 +709,12 @@
// text element was previously in focus
updateContextPanel();
// In the event a gradient was flipped:
if(selectedElement && mode === "select") {
paintBox.fill.update();
paintBox.stroke.update();
}
svgCanvas.runExtensions("elementChanged", {
elems: elems
});
@ -782,6 +791,12 @@
updateTitle();
}
// Makes sure the current selected paint is available to work with
var prepPaints = function() {
paintBox.fill.prep();
paintBox.stroke.prep();
}
var flyout_funcs = {};
var setupFlyouts = function(holders) {
@ -1266,15 +1281,21 @@
runCallback();
};
var getPaint = function(color, opac) {
var getPaint = function(color, opac, type) {
// update the editor's fill paint
var opts = null;
if (color.substr(0,5) == "url(#") {
var grad = document.getElementById(color.substr(5,color.length-6));
if (color.indexOf("url(#") === 0) {
var refElem = svgCanvas.getRefElem(color);
if(refElem) {
refElem = refElem.cloneNode(true);
} else {
refElem = $("#" + type + "_color defs *")[0];
}
opts = { alpha: opac };
opts[grad.tagName] = grad;
opts[refElem.tagName] = refElem;
}
else if (color.substr(0,1) == "#") {
else if (color.indexOf("#") === 0) {
opts = {
alpha: opac,
solidColor: color.substr(1)
@ -1294,61 +1315,11 @@
var updateToolbar = function() {
if (selectedElement != null && ['use', 'image', 'foreignObject', 'g', 'a'].indexOf(selectedElement.tagName) === -1) {
// get opacity values
var fillOpacity = parseFloat(selectedElement.getAttribute("fill-opacity"));
if (isNaN(fillOpacity)) {
fillOpacity = 1.0;
}
paintBox.fill.update(true);
paintBox.stroke.update(true);
var strokeOpacity = parseFloat(selectedElement.getAttribute("stroke-opacity"));
if (isNaN(strokeOpacity)) {
strokeOpacity = 1.0;
}
// update fill color and opacity
var fillColor = selectedElement.getAttribute("fill")||"black";
// prevent undo on these canvas changes
svgCanvas.setColor('fill', fillColor, true);
svgCanvas.setPaintOpacity('fill', fillOpacity, true);
// update stroke color and opacity
var strokeColor = selectedElement.getAttribute("stroke")||"none";
// prevent undo on these canvas changes
svgCanvas.setColor('stroke', strokeColor, true);
svgCanvas.setPaintOpacity('stroke', strokeOpacity, true);
// update the rect inside #fill_color
$("#stroke_color rect").attr({
fill: strokeColor,
opacity: strokeOpacity
});
// update the rect inside #fill_color
$("#fill_color rect").attr({
fill: fillColor,
opacity: fillOpacity
});
fillOpacity *= 100;
strokeOpacity *= 100;
fillPaint = getPaint(fillColor, fillOpacity);
strokePaint = getPaint(strokeColor, strokeOpacity);
fillOpacity = fillOpacity + " %";
strokeOpacity = strokeOpacity + " %";
// update fill color
if (fillColor == "none") {
fillOpacity = "N/A";
}
if (strokeColor == null || strokeColor == "" || strokeColor == "none") {
strokeColor = "none";
strokeOpacity = "N/A";
}
$('#stroke_width').val(selectedElement.getAttribute("stroke-width")||1).change();
$('#stroke_style').val(selectedElement.getAttribute("stroke-dasharray")||"none").change();
$('#stroke_width').val(selectedElement.getAttribute("stroke-width")||1);
$('#stroke_style').val(selectedElement.getAttribute("stroke-dasharray")||"none");
var attr = selectedElement.getAttribute("stroke-linejoin") || 'miter';
@ -1359,7 +1330,6 @@
if ($('#linecap_' + attr).length != 0)
setStrokeOpt($('#linecap_' + attr)[0]);
}
// All elements including image and group have opacity
@ -1820,26 +1790,23 @@
$('.palette_item').mousedown(function(evt){
var right_click = evt.button === 2;
var picker = ((evt.shiftKey || right_click) ? "stroke" : "fill");
var id = ((evt.shiftKey || right_click) ? '#stroke_' : '#fill_');
var isStroke = evt.shiftKey || right_click;
var picker = isStroke ? "stroke" : "fill";
var color = $(this).attr('data-rgb');
var rectbox = document.getElementById("gradbox_"+picker).parentNode.firstChild;
var paint = null;
// Webkit-based browsers returned 'initial' here for no stroke
if (color == 'transparent' || color == 'initial') {
if (color === 'transparent' || color === 'initial') {
color = 'none';
$(id + "opacity").html("N/A");
paint = new $.jGraduate.Paint();
}
else {
paint = new $.jGraduate.Paint({alpha: 100, solidColor: color.substr(1)});
}
rectbox.setAttribute("fill", color);
rectbox.setAttribute("opacity", 1);
if (evt.shiftKey) {
strokePaint = paint;
paintBox[picker].setPaint(paint);
if (isStroke) {
if (svgCanvas.getColor('stroke') != color) {
svgCanvas.setColor('stroke', color);
}
@ -1847,7 +1814,6 @@
svgCanvas.setPaintOpacity('stroke', 1.0);
}
} else {
fillPaint = paint;
if (svgCanvas.getColor('fill') != color) {
svgCanvas.setColor('fill', color);
}
@ -2445,6 +2411,7 @@
zoomImage();
populateLayers();
updateContextPanel();
prepPaints();
});
};
@ -2580,7 +2547,7 @@
$('#save_output_btns').toggle(!!forSaving);
$('#tool_source_back').toggle(!forSaving);
var str = svgCanvas.getSvgString();
var str = orig_source = svgCanvas.getSvgString();
$('#svg_source_textarea').val(str);
$('#svg_source_editor').fadeIn();
properlySourceSizeTextArea();
@ -2652,6 +2619,7 @@
zoomImage();
populateLayers();
updateTitle();
prepPaints();
}
if (!svgCanvas.setSvgString($('#svg_source_textarea').val())) {
@ -2668,9 +2636,11 @@
var updateTitle = function(title) {
title = title || svgCanvas.getDocumentTitle();
var new_title = orig_title + (title?': ' + title:'');
if(cur_context) {
new_title = new_title + cur_context;
}
// Remove title update with current context info, isn't really necessary
// if(cur_context) {
// new_title = new_title + cur_context;
// }
$('title:first').text(new_title);
}
@ -3039,8 +3009,7 @@
};
if (editingsource) {
var oldString = svgCanvas.getSvgString();
if (oldString != $('#svg_source_textarea').val()) {
if (orig_source !== $('#svg_source_textarea').val()) {
$.confirm(uiStrings.QignoreSourceChanges, function(ok) {
if(ok) hideSourceEditor();
});
@ -3156,12 +3125,12 @@
var colorPicker = function(elem) {
var picker = elem.attr('id') == 'stroke_color' ? 'stroke' : 'fill';
// var opacity = (picker == 'stroke' ? $('#stroke_opacity') : $('#fill_opacity'));
var paint = (picker == 'stroke' ? strokePaint : fillPaint);
var paint = paintBox[picker].paint;
var title = (picker == 'stroke' ? 'Pick a Stroke Paint and Opacity' : 'Pick a Fill Paint and Opacity');
var was_none = false;
var pos = elem.position();
$("#color_picker")
.draggable({cancel:'.jPicker_table,.jGraduate_lgPick,.jGraduate_rgPick'})
.draggable({cancel:'.jGraduate_tabs,.jGraduate_colPick,.jGraduate_lgPick,.jGraduate_rgPick'})
.css(curConfig.colorPickerCSS || {'left': pos.left, 'bottom': 50 - pos.top})
.jGraduate(
{
@ -3172,30 +3141,9 @@
function(p) {
paint = new $.jGraduate.Paint(p);
var oldgrad = document.getElementById("gradbox_"+picker);
var svgbox = oldgrad.parentNode;
var rectbox = svgbox.firstChild;
if (paint.type == "linearGradient" || paint.type == "radialGradient") {
svgbox.removeChild(oldgrad);
var newgrad = svgbox.appendChild(document.importNode(paint[paint.type], true));
newgrad.id = "gradbox_"+picker;
rectbox.setAttribute("fill", "url(#gradbox_" + picker + ")");
rectbox.setAttribute("opacity", paint.alpha/100);
}
else {
rectbox.setAttribute("fill", paint.solidColor != "none" ? "#" + paint.solidColor : "none");
rectbox.setAttribute("opacity", paint.alpha/100);
}
if (picker == 'stroke') {
svgCanvas.setPaint('stroke', paint);
strokePaint = paint;
}
else {
svgCanvas.setPaint('fill', paint);
fillPaint = paint;
}
updateToolbar();
paintBox[picker].setPaint(paint);
svgCanvas.setPaint(picker, paint);
$('#color_picker').hide();
},
function(p) {
@ -3260,39 +3208,103 @@
operaRepaint();
};
// set up gradients to be used for the buttons
var svgdocbox = new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%"\
fill="#' + curConfig.initFill.color + '" opacity="' + curConfig.initFill.opacity + '"/>\
<linearGradient id="gradbox_">\
<stop stop-color="#000" offset="0.0"/>\
<stop stop-color="#FF0000" offset="1.0"/>\
</linearGradient></svg>', 'text/xml');
var docElem = svgdocbox.documentElement;
var boxgrad = svgdocbox.getElementById('gradbox_');
boxgrad.id = 'gradbox_fill';
docElem.setAttribute('width',16.5);
$('#fill_color').append( document.importNode(docElem,true) );
boxgrad.id = 'gradbox_stroke';
docElem.setAttribute('width',16.5);
$('#stroke_color').append( document.importNode(docElem,true) );
$('#stroke_color rect').attr({
'fill': '#' + curConfig.initStroke.color,
'opacity': curConfig.initStroke.opacity
});
var PaintBox = function(container, type) {
var cur = curConfig[type === 'fill' ? 'initFill' : 'initStroke'];
// set up gradients to be used for the buttons
var svgdocbox = new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%"\
fill="#' + cur.color + '" opacity="' + cur.opacity + '"/>\
<defs><linearGradient id="gradbox_"/></defs></svg>', 'text/xml');
var docElem = svgdocbox.documentElement;
docElem = $(container)[0].appendChild(document.importNode(docElem, true));
docElem.setAttribute('width',16.5);
this.rect = docElem.firstChild;
this.defs = docElem.getElementsByTagName('defs')[0];
this.grad = this.defs.firstChild;
this.paint = new $.jGraduate.Paint({solidColor: cur.color});
this.type = type;
this.setPaint = function(paint, apply) {
this.paint = paint;
var fillAttr = "none";
var ptype = paint.type;
var opac = paint.alpha / 100;
switch ( ptype ) {
case 'solidColor':
fillAttr = "#" + paint[ptype];
break;
case 'linearGradient':
case 'radialGradient':
this.defs.removeChild(this.grad);
this.grad = this.defs.appendChild(paint[ptype]);
var id = this.grad.id = 'gradbox_' + this.type;
fillAttr = "url(#" + id + ')';
}
this.rect.setAttribute('fill', fillAttr);
this.rect.setAttribute('opacity', opac);
if(apply) {
svgCanvas.setColor(this.type, paintColor, true);
svgCanvas.setPaintOpacity(this.type, paintOpacity, true);
}
}
this.update = function(apply) {
if(!selectedElement) return;
var type = this.type;
var paintOpacity = parseFloat(selectedElement.getAttribute(type + "-opacity"));
if (isNaN(paintOpacity)) {
paintOpacity = 1.0;
}
var defColor = type === "fill" ? "black" : "none";
var paintColor = selectedElement.getAttribute(type) || defColor;
if(apply) {
svgCanvas.setColor(type, paintColor, true);
svgCanvas.setPaintOpacity(type, paintOpacity, true);
}
paintOpacity *= 100;
var paint = getPaint(paintColor, paintOpacity, type);
// update the rect inside #fill_color/#stroke_color
this.setPaint(paint);
}
this.prep = function() {
var ptype = this.paint.type;
switch ( ptype ) {
case 'linearGradient':
case 'radialGradient':
var paint = new $.jGraduate.Paint({copy: this.paint});
svgCanvas.setPaint(type, paint);
}
}
};
paintBox.fill = new PaintBox('#fill_color', 'fill');
paintBox.stroke = new PaintBox('#stroke_color', 'stroke');
$('#stroke_width').val(curConfig.initStroke.width);
$('#group_opacity').val(curConfig.initOpacity * 100);
// Use this SVG elem to test vectorEffect support
var test_el = docElem.firstChild;
var test_el = paintBox.fill.rect.cloneNode(false);
test_el.setAttribute('style','vector-effect:non-scaling-stroke');
var supportsNonSS = (test_el.style.vectorEffect === 'non-scaling-stroke');
test_el.removeAttribute('style');
var svgdocbox = paintBox.fill.rect.ownerDocument;
// Use this to test support for blur element. Seems to work to test support in Webkit
var blur_test = svgdocbox.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
if(typeof blur_test.stdDeviationX === "undefined") {
@ -4084,7 +4096,7 @@
if(f.files.length==1) {
var reader = new FileReader();
reader.onloadend = function(e) {
svgCanvas.setSvgString(e.target.result);
loadSvgString(e.target.result);
updateCanvas();
};
reader.readAsText(f.files[0]);
@ -4107,6 +4119,7 @@
}
var updateCanvas = Editor.updateCanvas = function(center, new_ctr) {
var w = workarea.width(), h = workarea.height();
var w_orig = w, h_orig = h;
var zoom = svgCanvas.getZoom();
@ -4290,7 +4303,7 @@
updateCanvas(true);
// });
// var revnums = "svg-editor.js ($Rev: 1778 $) ";
// var revnums = "svg-editor.js ($Rev: 1802 $) ";
// revnums += svgCanvas.getVersion();
// $('#copyright')[0].setAttribute("title", revnums);
@ -4399,6 +4412,18 @@
var callbacks = [];
function loadSvgString(str, callback) {
var success = svgCanvas.setSvgString(str) !== false;
callback = callback || $.noop;
if(success) {
callback(true);
} else {
$.alert('Error: Unable to load SVG data', function() {
callback(false);
});
}
}
Editor.ready = function(cb) {
if(!is_ready) {
callbacks.push(cb);
@ -4416,22 +4441,29 @@
Editor.loadFromString = function(str) {
Editor.ready(function() {
svgCanvas.setSvgString(str);
loadSvgString(str);
});
};
Editor.loadFromURL = function(url, cache) {
Editor.loadFromURL = function(url, opts) {
if(!opts) opts = {};
var cache = opts.cache;
var cb = opts.callback;
Editor.ready(function() {
$.ajax({
'url': url,
'dataType': 'text',
cache: !!cache,
success: svgCanvas.setSvgString,
success: function(str) {
loadSvgString(str, cb);
},
error: function(xhr, stat, err) {
if(xhr.responseText) {
svgCanvas.setSvgString(xhr.responseText);
if(xhr.status != 404 && xhr.responseText) {
loadSvgString(xhr.responseText, cb);
} else {
$.alert("Unable to load from URL. Error: \n"+err+'');
$.alert("Unable to load from URL. Error: \n"+err+'', cb);
}
}
});
@ -4440,10 +4472,9 @@
Editor.loadFromDataURI = function(str) {
Editor.ready(function() {
svgCanvas.setSvgString(str);
var pre = 'data:image/svg+xml;base64,';
var src = str.substring(pre.length);
svgCanvas.setSvgString(Utils.decode64(src));
loadSvgString(svgCanvas.Utils.decode64(src));
});
};

View file

@ -1,4 +1,4 @@
/*
/*
* svgcanvas.js
*
* Licensed under the Apache License, Version 2
@ -118,7 +118,7 @@ var userAgent = navigator.userAgent,
"mask": ["class", "height", "id", "maskContentUnits", "maskUnits", "width", "x", "y"],
"metadata": ["class", "id"],
"path": ["class", "clip-path", "clip-rule", "d", "fill", "fill-opacity", "fill-rule", "filter", "id", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"],
"pattern": ["class", "height", "id", "patternContentUnits", "patternTransform", "patternUnits", "requiredFeatures", "style", "systemLanguage", "width", "x", "xlink:href", "y"],
"pattern": ["class", "height", "id", "patternContentUnits", "patternTransform", "patternUnits", "requiredFeatures", "style", "systemLanguage", "viewBox", "width", "x", "xlink:href", "y"],
"polygon": ["class", "clip-path", "clip-rule", "id", "fill", "fill-opacity", "fill-rule", "filter", "id", "class", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "points", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"],
"polyline": ["class", "clip-path", "clip-rule", "id", "fill", "fill-opacity", "fill-rule", "filter", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "points", "requiredFeatures", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "systemLanguage", "transform"],
"radialGradient": ["class", "cx", "cy", "fx", "fy", "gradientTransform", "gradientUnits", "id", "r", "requiredFeatures", "spreadMethod", "systemLanguage", "xlink:href"],
@ -190,6 +190,7 @@ var userAgent = navigator.userAgent,
var visElems_arr = visElems.split(',');
// var hidElems = 'clipPath,defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
var ref_attrs = ["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"];
// Update config with new one if given
@ -712,6 +713,22 @@ var convertToNum, convertToUnit, setUnitAttr, unitConvertAttrs;
})();
var restoreRefElems = function(elem) {
// Look for missing reference elements, restore any found
var attrs = $(elem).attr(ref_attrs);
for(var o in attrs) {
var val = attrs[o];
if (val && val.indexOf('url(') === 0) {
var id = getUrlFromAttr(val).substr(1);
var ref = getElem(id);
if(!ref) {
findDefs().appendChild(removedElements[id]);
delete removedElements[id];
}
}
}
}
// Group: Undo/Redo history management
this.undoCmd = {};
@ -841,6 +858,9 @@ var InsertElementCommand = this.undoCmd.insertElement = function(elem, text) {
// Re-Inserts the new element
this.apply = function() {
this.elem = this.parent.insertBefore(this.elem, this.elem.nextSibling);
restoreRefElems(this.elem);
if (this.parent == svgcontent) {
identifyLayers();
}
@ -894,8 +914,11 @@ var RemoveElementCommand = this.undoCmd.removeElement = function(elem, parent, t
delete svgTransformLists[this.elem.id];
}
this.elem = this.parent.insertBefore(this.elem, this.elem.nextSibling);
if (this.parent == svgcontent) {
this.parent.insertBefore(this.elem, this.elem.nextSibling);
restoreRefElems(this.elem);
if (this.parent === svgcontent) {
identifyLayers();
}
};
@ -1272,6 +1295,7 @@ var SelectorManager;
if (selected.getAttribute("stroke") != "none" && !isNaN(sw)) {
offset += (sw/2);
}
if (selected.tagName == "text") {
offset += 2/current_zoom;
}
@ -1298,18 +1322,19 @@ var SelectorManager;
m.f *= current_zoom;
// apply the transforms
var l=bbox.x-offset, t=bbox.y-offset, w=bbox.width+(offset*2), h=bbox.height+(offset*2),
var l=bbox.x, t=bbox.y, w=bbox.width, h=bbox.height,
bbox = {x:l, y:t, width:w, height:h};
// we need to handle temporary transforms too
// if skewed, get its transformed box, then find its axis-aligned bbox
//*
var nbox = transformBox(l*current_zoom, t*current_zoom, w*current_zoom, h*current_zoom, m),
nbax = nbox.aabox.x,
nbay = nbox.aabox.y,
nbaw = nbox.aabox.width,
nbah = nbox.aabox.height;
nbax = nbox.aabox.x - offset,
nbay = nbox.aabox.y - offset,
nbaw = nbox.aabox.width + (offset * 2),
nbah = nbox.aabox.height + (offset * 2);
// now if the shape is rotated, un-rotate it
var cx = nbax + nbaw/2,
@ -1332,10 +1357,10 @@ var SelectorManager;
maxx = nbox.tl.x,
maxy = nbox.tl.y;
minx = Math.min(minx, Math.min(nbox.tr.x, Math.min(nbox.bl.x, nbox.br.x) ) );
miny = Math.min(miny, Math.min(nbox.tr.y, Math.min(nbox.bl.y, nbox.br.y) ) );
maxx = Math.max(maxx, Math.max(nbox.tr.x, Math.max(nbox.bl.x, nbox.br.x) ) );
maxy = Math.max(maxy, Math.max(nbox.tr.y, Math.max(nbox.bl.y, nbox.br.y) ) );
minx = Math.min(minx, Math.min(nbox.tr.x, Math.min(nbox.bl.x, nbox.br.x) ) ) - offset;
miny = Math.min(miny, Math.min(nbox.tr.y, Math.min(nbox.bl.y, nbox.br.y) ) ) - offset;
maxx = Math.max(maxx, Math.max(nbox.tr.x, Math.max(nbox.bl.x, nbox.br.x) ) ) + offset;
maxy = Math.max(maxy, Math.max(nbox.tr.y, Math.max(nbox.bl.y, nbox.br.y) ) ) + offset;
nbax = minx;
nbay = miny;
@ -1683,6 +1708,7 @@ var SVGEditTransformList = function(elem) {
var bits = x.split(/\s*\(/);
var name = bits[0];
var val_bits = bits[1].match(/\s*(.*?)\s*\)/);
val_bits[1] = val_bits[1].replace(/(\d)-/g, "$1 -");
var val_arr = val_bits[1].split(/[, ]+/);
var letters = 'abcdef'.split('');
var mtx = svgroot.createSVGMatrix();
@ -1833,11 +1859,11 @@ var assignAttributes = this.assignAttributes = function(node, attrs, suspendLeng
if(!suspendLength) suspendLength = 0;
// Opera has a problem with suspendRedraw() apparently
var handle = null;
if (!window.opera) svgroot.suspendRedraw(suspendLength);
if (!isOpera) svgroot.suspendRedraw(suspendLength);
for (var i in attrs) {
var ns = (i.substr(0,4) == "xml:" ? xmlns :
i.substr(0,6) == "xlink:" ? xlinkns : null);
var ns = (i.substr(0,4) === "xml:" ? xmlns :
i.substr(0,6) === "xlink:" ? xlinkns : null);
if(ns || !unitCheck) {
node.setAttributeNS(ns, i, attrs[i]);
@ -2036,7 +2062,10 @@ var cur_shape = all_properties.shape,
extensions = {},
// Canvas point for the most recent right click
lastClickPoint = null;
lastClickPoint = null,
// Map of deleted reference elements
removedElements = {}
// Clipboard for cut, copy&pasted elements
canvas.clipBoard = [];
@ -2204,12 +2233,13 @@ var getStrokedBBox = this.getStrokedBBox = function(elems) {
}
if(!good_bb) {
// Must use clone else FF freaks out
var clone = elem.cloneNode(true);
var g = document.createElementNS(svgns, "g");
var parent = elem.parentNode;
parent.replaceChild(g, elem);
g.appendChild(elem);
parent.appendChild(g);
g.appendChild(clone);
bb = g.getBBox();
parent.insertBefore(elem,g);
parent.removeChild(g);
}
@ -2418,11 +2448,6 @@ var copyElem = function(el) {
// Parameters:
// id - String with the element's new ID
function getElem(id) {
// if(svgroot.getElementById) {
// // getElementById lookup
// return svgroot.getElementById(id);
// } else
if(svgroot.querySelector) {
// querySelector lookup
return svgroot.querySelector('#'+id);
@ -2566,6 +2591,18 @@ var sanitizeSvg = this.sanitizeSvg = function(node) {
node.setAttribute('d',pathActions.convertPath(node));
pathActions.fixEnd(node);
}
// Add spaces before negative signs where necessary
if(isGecko) {
switch ( attrName ) {
case "transform":
case "gradientTransform":
case "patternTransform":
var val = attr.nodeValue.replace(/(\d)-/g, "$1 -");
node.setAttribute(attrName, val);
}
}
// for the style attribute, rewrite it in terms of XML presentational attributes
if (attrName == "style") {
var props = attr.nodeValue.split(";"),
@ -2612,7 +2649,7 @@ var sanitizeSvg = this.sanitizeSvg = function(node) {
if (val) {
val = getUrlFromAttr(val);
// simply check for first character being a '#'
if (val && val[0] != "#") {
if (val && val[0] !== "#") {
node.setAttribute(attr, "");
node.removeAttribute(attr);
}
@ -2657,20 +2694,29 @@ var sanitizeSvg = this.sanitizeSvg = function(node) {
var getUrlFromAttr = this.getUrlFromAttr = function(attrVal) {
if (attrVal) {
// url("#somegrad")
if (attrVal.indexOf('url("') == 0) {
if (attrVal.indexOf('url("') === 0) {
return attrVal.substring(5,attrVal.indexOf('"',6));
}
// url('#somegrad')
else if (attrVal.indexOf("url('") == 0) {
else if (attrVal.indexOf("url('") === 0) {
return attrVal.substring(5,attrVal.indexOf("'",6));
}
else if (attrVal.indexOf("url(") == 0) {
else if (attrVal.indexOf("url(") === 0) {
return attrVal.substring(4,attrVal.indexOf(')'));
}
}
return null;
};
// Function getRefElem
// Get the reference element associated with the given attribute value
//
// Parameters:
// attrVal - The attribute value as a string
var getRefElem = this.getRefElem = function(attrVal) {
return getElem(getUrlFromAttr(attrVal).substr(1));
}
// Function: getBBox
// Get the given/selected element's bounding box object, convert it to be more
// usable when necessary
@ -2934,6 +2980,10 @@ var getTransformList = this.getTransformList = function(elem) {
else if (elem.gradientTransform) {
return elem.gradientTransform.baseVal;
}
else if (elem.patternTransform) {
return elem.patternTransform.baseVal;
}
return null;
};
@ -2977,82 +3027,159 @@ var logMatrix = function(m) {
// changes - Object with changes to be remapped
// m - Matrix object to use for remapping coordinates
var remapElement = this.remapElement = function(selected,changes,m) {
var remap = function(x,y) { return transformPoint(x,y,m); },
scalew = function(w) { return m.a*w; },
scaleh = function(h) { return m.d*h; },
doSnapping = curConfig.gridSnapping && selected.parentNode.parentNode.localName === "svg",
finishUp = function() {
if(doSnapping) for(var o in changes) changes[o] = snapToGrid(changes[o]);
assignAttributes(selected, changes, 1000, true);
}
box = getBBox(selected);
for(var i = 0; i < 2; i++) {
var type = i === 0 ? 'fill' : 'stroke';
var attrVal = selected.getAttribute(type);
if(attrVal && attrVal.indexOf('url(') === 0) {
if(m.a < 0 || m.d < 0) {
var grad = getRefElem(attrVal);
var newgrad = grad.cloneNode(true);
if(m.a < 0) {
//flip x
var x1 = newgrad.getAttribute('x1');
var x2 = newgrad.getAttribute('x2');
newgrad.setAttribute('x1', -(x1 - 1));
newgrad.setAttribute('x2', -(x2 - 1));
}
if(m.d < 0) {
//flip y
var y1 = newgrad.getAttribute('y1');
var y2 = newgrad.getAttribute('y2');
newgrad.setAttribute('y1', -(y1 - 1));
newgrad.setAttribute('y2', -(y2 - 1));
}
newgrad.id = getNextId();
findDefs().appendChild(newgrad);
selected.setAttribute(type, 'url(#' + newgrad.id + ')');
}
}
}
switch (selected.tagName)
var elName = selected.tagName;
if(elName === "g" || elName === "text" || elName === "use") {
// if it was a translate, then just update x,y
if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 &&
(m.e != 0 || m.f != 0) )
{
// [T][M] = [M][T']
// therefore [T'] = [M_inv][T][M]
var existing = transformListToTransform(selected).matrix,
t_new = matrixMultiply(existing.inverse(), m, existing);
changes.x = parseFloat(changes.x) + t_new.e;
changes.y = parseFloat(changes.y) + t_new.f;
}
else {
// we just absorb all matrices into the element and don't do any remapping
var chlist = getTransformList(selected);
var mt = svgroot.createSVGTransform();
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix,m));
chlist.clear();
chlist.appendItem(mt);
}
}
// now we have a set of changes and an applied reduced transform list
// we apply the changes directly to the DOM
switch (elName)
{
case "line":
var pt1 = remap(changes["x1"],changes["y1"]),
pt2 = remap(changes["x2"],changes["y2"]);
changes["x1"] = pt1.x;
changes["y1"] = pt1.y;
changes["x2"] = pt2.x;
changes["y2"] = pt2.y;
break;
case "circle":
var c = remap(changes["cx"],changes["cy"]);
changes["cx"] = c.x;
changes["cy"] = c.y;
// take the minimum of the new selected box's dimensions for the new circle radius
var tbox = transformBox(box.x, box.y, box.width, box.height, m);
var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
changes["r"] = Math.min(w/2, h/2);
break;
case "ellipse":
var c = remap(changes["cx"],changes["cy"]);
changes["cx"] = c.x;
changes["cy"] = c.y;
changes["rx"] = scalew(changes["rx"]);
changes["ry"] = scaleh(changes["ry"]);
break;
case "foreignObject":
case "rect":
case "image":
var pt1 = remap(changes["x"],changes["y"]);
changes["x"] = pt1.x;
changes["y"] = pt1.y;
changes["width"] = scalew(changes["width"]);
changes["height"] = scaleh(changes["height"]);
break;
case "use":
// var pt1 = remap(changes["x"],changes["y"]);
// changes["x"] = pt1.x;
// changes["y"] = pt1.y;
// break;
case "g":
case "text":
// if it was a translate, then just update x,y
if (m.a == 1 && m.b == 0 && m.c == 0 && m.d == 1 &&
(m.e != 0 || m.f != 0) )
{
// [T][M] = [M][T']
// therefore [T'] = [M_inv][T][M]
var existing = transformListToTransform(selected).matrix,
t_new = matrixMultiply(existing.inverse(), m, existing);
changes["x"] = parseFloat(changes["x"]) + t_new.e;
changes["y"] = parseFloat(changes["y"]) + t_new.f;
}
else {
// we just absorb all matrices into the element and don't do any remapping
// Allow images to be inverted (give them matrix when flipped)
if(elName === 'image' && (m.a < 0 || m.d < 0)) {
// Convert to matrix
var chlist = getTransformList(selected);
var mt = svgroot.createSVGTransform();
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix,m));
chlist.clear();
chlist.appendItem(mt);
} else {
var pt1 = remap(changes.x,changes.y);
changes.width = scalew(changes.width);
changes.height = scaleh(changes.height);
changes.x = pt1.x + Math.min(0,changes.width);
changes.y = pt1.y + Math.min(0,changes.height);
changes.width = Math.abs(changes.width);
changes.height = Math.abs(changes.height);
}
finishUp();
break;
case "ellipse":
var c = remap(changes.cx,changes.cy);
changes.cx = c.x;
changes.cy = c.y;
changes.rx = scalew(changes.rx);
changes.ry = scaleh(changes.ry);
changes.rx = Math.abs(changes.rx);
changes.ry = Math.abs(changes.ry);
finishUp();
break;
case "circle":
var c = remap(changes.cx,changes.cy);
changes.cx = c.x;
changes.cy = c.y;
// take the minimum of the new selected box's dimensions for the new circle radius
var tbox = transformBox(box.x, box.y, box.width, box.height, m);
var w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
changes.r = Math.min(w/2, h/2);
if(changes.r) changes.r = Math.abs(changes.r);
finishUp();
break;
case "line":
var pt1 = remap(changes.x1,changes.y1),
pt2 = remap(changes.x2,changes.y2);
changes.x1 = pt1.x;
changes.y1 = pt1.y;
changes.x2 = pt2.x;
changes.y2 = pt2.y;
case "text":
case "use":
finishUp();
break;
case "g":
var gsvg = $(selected).data('gsvg');
if(gsvg) {
assignAttributes(gsvg, changes, 1000, true);
}
break;
case "polygon":
case "polyline":
var len = changes["points"].length;
case "polygon":
var len = changes.points.length;
for (var i = 0; i < len; ++i) {
var pt = changes["points"][i];
var pt = changes.points[i];
pt = remap(pt.x,pt.y);
changes["points"][i].x = pt.x;
changes["points"][i].y = pt.y;
changes.points[i].x = pt.x;
changes.points[i].y = pt.y;
}
var len = changes.points.length;
var pstr = "";
for (var i = 0; i < len; ++i) {
var pt = changes.points[i];
pstr += pt.x + "," + pt.y + " ";
}
selected.setAttribute("points", pstr);
break;
case "path":
var segList = selected.pathSegList;
@ -3076,13 +3203,13 @@ var remapElement = this.remapElement = function(selected,changes,m) {
};
}
var len = changes["d"].length,
firstseg = changes["d"][0],
var len = changes.d.length,
firstseg = changes.d[0],
currentpt = remap(firstseg.x,firstseg.y);
changes["d"][0].x = currentpt.x;
changes["d"][0].y = currentpt.y;
changes.d[0].x = currentpt.x;
changes.d[0].y = currentpt.y;
for (var i = 1; i < len; ++i) {
var seg = changes["d"][i];
var seg = changes.d[i];
var type = seg.type;
// if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
// if relative, we want to scalew, scaleh
@ -3115,81 +3242,11 @@ var remapElement = this.remapElement = function(selected,changes,m) {
if (seg.x) currentpt.x = seg.x;
if (seg.y) currentpt.y = seg.y;
} // for each segment
break;
} // switch on element type to get initial values
// now we have a set of changes and an applied reduced transform list
// we apply the changes directly to the DOM
// TODO: merge this switch with the above one and optimize
switch (selected.tagName)
{
case "foreignObject":
case "rect":
case "image":
changes.x = changes.x-0 + Math.min(0,changes.width);
changes.y = changes.y-0 + Math.min(0,changes.height);
changes.width = Math.abs(changes.width);
changes.height = Math.abs(changes.height);
if(curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){
changes.x = snapToGrid(changes.x);
changes.y = snapToGrid(changes.y);
changes.width = snapToGrid(changes.width);
changes.height = snapToGrid(changes.height);
}
assignAttributes(selected, changes, 1000, true);
break;
case "ellipse":
changes.rx = Math.abs(changes.rx);
changes.ry = Math.abs(changes.ry);
if(curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){
changes.cx = snapToGrid(changes.cx);
changes.cy = snapToGrid(changes.cy);
changes.rx = snapToGrid(changes.rx);
changes.ry = snapToGrid(changes.ry);
}
case "circle":
if(changes.r) changes.r = Math.abs(changes.r);
if(curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){
changes.cx = snapToGrid(changes.cx);
changes.cy = snapToGrid(changes.cy);
changes.r = snapToGrid(changes.r);
}
case "line":
if(curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){
changes.x1 = snapToGrid(changes.x1);
changes.y1 = snapToGrid(changes.y1);
changes.x2 = snapToGrid(changes.x2);
changes.y2 = snapToGrid(changes.y2);
}
case "text":
if(curConfig.gridSnapping && selected.parentNode.parentNode.localName == "svg"){
changes.x = snapToGrid(changes.x);
changes.y = snapToGrid(changes.y);
}
case "use":
assignAttributes(selected, changes, 1000, true);
break;
case "g":
var gsvg = $(selected).data('gsvg');
if(gsvg) {
assignAttributes(gsvg, changes, 1000, true);
}
break;
case "polyline":
case "polygon":
var len = changes["points"].length;
var pstr = "";
for (var i = 0; i < len; ++i) {
var pt = changes["points"][i];
pstr += pt.x + "," + pt.y + " ";
}
selected.setAttribute("points", pstr);
break;
case "path":
var dstr = "";
var len = changes["d"].length;
var len = changes.d.length;
for (var i = 0; i < len; ++i) {
var seg = changes["d"][i];
var seg = changes.d[i];
var type = seg.type;
dstr += pathMap[type];
switch(type) {
@ -3242,8 +3299,7 @@ var remapElement = this.remapElement = function(selected,changes,m) {
// tx - The translation's x value
// ty - The translation's y value
var updateClipPath = function(attr, tx, ty) {
var id = getUrlFromAttr(attr).substr(1);
var path = getElem(id).firstChild;
var path = getRefElem(attr).firstChild;
var cp_xform = getTransformList(path);
@ -3324,8 +3380,8 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) {
if(k === 2 && tlist.getItem(0).type === 1 && tlist.getItem(1).type === 2) {
var mt = svgroot.createSVGTransform();
logMatrix(tlist.getItem(0).matrix);
logMatrix(transformListToTransform(tlist).matrix);
// logMatrix(tlist.getItem(0).matrix);
// logMatrix(transformListToTransform(tlist).matrix);
mt.setMatrix(transformListToTransform(tlist).matrix);
tlist.clear();
@ -3532,8 +3588,8 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) {
childxforms.push(translateBack);
childxforms.push(scale);
childxforms.push(translateOrigin);
logMatrix(translateBack.matrix);
logMatrix(scale.matrix);
// logMatrix(translateBack.matrix);
// logMatrix(scale.matrix);
} // not rotated
batchCmd.addSubCommand( recalculateDimensions(child) );
// TODO: If any <use> have this group as a parent and are
@ -3667,6 +3723,15 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) {
batchCmd.addSubCommand( recalculateDimensions(child) );
start_transform = old_start_transform;
// Convert stroke
// TODO: Find out if this should actually happen somewhere else
var sw = child.getAttribute("stroke-width");
if (child.getAttribute("stroke") !== "none" && !isNaN(sw)) {
var avg = (Math.abs(em.a) + Math.abs(em.d)) / 2;
child.setAttribute('stroke-width', sw * avg);
}
}
}
tlist.clear();
@ -3806,17 +3871,18 @@ var recalculateDimensions = this.recalculateDimensions = function(selected) {
if(!isWebkit) {
var fill = selected.getAttribute('fill');
if(fill && fill.indexOf('url(') === 0) {
var grad = getElem(getUrlFromAttr(fill).substr(1));
if(grad.getAttribute('gradientUnits') === 'userSpaceOnUse') {
var paint = getRefElem(fill);
var type = 'pattern';
if(paint.tagName !== type) type = 'gradient';
var attrVal = paint.getAttribute(type + 'Units');
if(attrVal === 'userSpaceOnUse') {
//Update the userSpaceOnUse element
var grad = $(grad);
m = transformListToTransform(tlist).matrix;
var gtlist = getTransformList(grad[0]);
var gtlist = getTransformList(paint);
var gmatrix = transformListToTransform(gtlist).matrix;
m = matrixMultiply(m, gmatrix);
var m_str = "matrix(" + [m.a,m.b,m.c,m.d,m.e,m.f].join(",") + ")";
grad.attr('gradientTransform', m_str);
paint.setAttribute(type + 'Transform', m_str);
}
}
}
@ -4579,6 +4645,14 @@ var getMouseTarget = this.getMouseTarget = function(evt) {
tlist.appendItem(svgroot.createSVGTransform());
tlist.appendItem(svgroot.createSVGTransform());
tlist.appendItem(svgroot.createSVGTransform());
if(support.nonScalingStroke) {
mouse_target.style.vectorEffect = 'non-scaling-stroke';
var all = mouse_target.getElementsByTagName('*'), len = all.length;
for(var i = 0; i < all.length; i++) {
all[i].style.vectorEffect = 'non-scaling-stroke';
}
}
}
break;
case "fhellipse":
@ -4708,7 +4782,8 @@ var getMouseTarget = this.getMouseTarget = function(evt) {
"font-size": cur_text.font_size,
"font-family": cur_text.font_family,
"text-anchor": "middle",
"xml:space": "preserve"
"xml:space": "preserve",
"opacity": cur_shape.opacity
}
});
// newText.textContent = "text";
@ -5227,6 +5302,16 @@ var getMouseTarget = this.getMouseTarget = function(evt) {
}
}
} // no change in mouse position
// Remove non-scaling stroke
if(support.nonScalingStroke) {
var elem = selectedElements[0];
elem.removeAttribute('style');
walkTree(elem, function(elem) {
elem.removeAttribute('style');
});
}
}
// we return immediately from select so that the obj_num is not incremented
return;
@ -5452,8 +5537,14 @@ var getMouseTarget = this.getMouseTarget = function(evt) {
}
if(getRotationAngle(mouse_target)) {
// Don't do for rotated groups for now
return;
// TODO: Allow method of in-group editing without having to do
// this (similar to editing rotated paths)
// Ungroup and regroup
canvas.ungroupSelectedElement();
canvas.groupSelectedElements();
mouse_target = selectedElements[0];
clearSelection(true);
}
// Reset context
if(current_group) {
@ -7762,19 +7853,20 @@ var removeUnusedDefElems = this.removeUnusedDefElems = function() {
// gradients can refer to other gradients
var href = getHref(el);
if (href && href.indexOf('#') == 0) {
if (href && href.indexOf('#') === 0) {
defelem_uses.push(href.substr(1));
}
};
var defelems = $(svgcontent).find("linearGradient, radialGradient, filter, marker, svg");
var defelems = $(svgcontent).find("linearGradient, radialGradient, filter, marker, svg, symbol");
defelem_ids = [],
i = defelems.length;
while (i--) {
var defelem = defelems[i];
var id = defelem.id;
if(defelem_uses.indexOf(id) == -1) {
// Not found, so remove
if(defelem_uses.indexOf(id) < 0) {
// Not found, so remove (but remember)
removedElements[id] = defelem;
defelem.parentNode.removeChild(defelem);
numRemoved++;
}
@ -8124,7 +8216,6 @@ this.randomizeIds = function() {
// g - The parent element of the tree to give unique IDs
var uniquifyElems = this.uniquifyElems = function(g) {
var ids = {};
var ref_attrs = ["clip-path", "fill", "filter", "marker-end", "marker-mid", "marker-start", "mask", "stroke"];
var ref_elems = ["filter", "linearGradient", "pattern", "radialGradient", "textPath", "use"];
walkTree(g, function(n) {
@ -8227,16 +8318,33 @@ var convertGradients = this.convertGradients = function(elem) {
// get object's bounding box
var bb = getBBox(elems[0]);
// This will occur if the element is inside a <defs> or a <symbol>,
// in which we shouldn't need to convert anyway.
if(!bb) return;
if(grad.tagName === 'linearGradient') {
var g_coords = $(grad).attr(['x1', 'y1', 'x2', 'y2']);
// If has transform, convert
var tlist = grad.gradientTransform.baseVal;
if(tlist && tlist.numberOfItems > 0) {
var m = transformListToTransform(tlist).matrix;
var pt1 = transformPoint(g_coords.x1, g_coords.y1, m);
var pt2 = transformPoint(g_coords.x2, g_coords.y2, m);
g_coords.x1 = pt1.x;
g_coords.y1 = pt1.y;
g_coords.x2 = pt2.x;
g_coords.y2 = pt2.y;
grad.removeAttribute('gradientTransform');
}
$(grad).attr({
x1: (g_coords.x1 - bb.x) / bb.width,
y1: (g_coords.y1 - bb.y) / bb.height,
x2: (g_coords.x2 - bb.x) / bb.width,
y2: (g_coords.y2 - bb.y) / bb.height
});
grad.removeAttribute('gradientUnits');
} else {
// Note: radialGradient elements cannot be easily converted
@ -8325,7 +8433,6 @@ var convertToGroup = this.convertToGroup = function(elem) {
// g.appendChild(elem.firstChild.cloneNode(true));
if (ts)
g.setAttribute("transform", ts);
console.log('t',g.getAttribute('transform'));
var parent = elem.parentNode;
@ -8444,9 +8551,9 @@ this.setSvgString = function(xmlString) {
}
});
// For Firefox: Put all gradients in defs
// For Firefox: Put all paint elems in defs
if(isGecko) {
content.find('linearGradient, radialGradient').appendTo(findDefs());
content.find('linearGradient, radialGradient, pattern').appendTo(findDefs());
}
@ -8574,6 +8681,8 @@ this.importSvgString = function(xmlString) {
// import new svg document into our document
var svg = svgdoc.importNode(newDoc.documentElement, true);
uniquifyElems(svg);
var innerw = convertToNum('width', svg.getAttribute("width")),
innerh = convertToNum('height', svg.getAttribute("height")),
innervb = svg.getAttribute("viewBox"),
@ -8597,17 +8706,17 @@ this.importSvgString = function(xmlString) {
// Hack to make recalculateDimensions understand how to scale
ts = "translate(0) " + ts + " translate(0)";
// Uncomment this once Firefox has fixed their symbol bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=353575
var symbol = svgdoc.createElementNS(svgns, "symbol");
var defs = findDefs();
if(isGecko) {
// Move all gradients into root for Firefox, workaround for this bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=353575
$(svg).find('linearGradient, radialGradient, pattern').appendTo(defs);
}
while (svg.firstChild) {
var first = svg.firstChild;
if(isGecko && first.tagName === 'defs') {
// Move all gradients into root for Firefox
$(first).find('linearGradient, radialGradient').appendTo(defs);
}
symbol.appendChild(first);
}
var attrs = svg.attributes;
@ -8621,7 +8730,7 @@ this.importSvgString = function(xmlString) {
var use_el = svgdoc.createElementNS(svgns, "use");
setHref(use_el, "#" + symbol.id);
findDefs().appendChild(symbol);
(current_group || current_layer).appendChild(use_el);
use_el.id = getNextId();
clearSelection();
@ -9253,7 +9362,7 @@ this.getZoom = function(){return current_zoom;};
// Function: getVersion
// Returns a string which describes the revision number of SvgCanvas.
this.getVersion = function() {
return "svgcanvas.js ($Rev: 1777 $)";
return "svgcanvas.js ($Rev: 1804 $)";
};
// Function: setUiStrings
@ -10702,6 +10811,7 @@ this.ungroupSelectedElement = function() {
var anchor = g.nextSibling;
var children = new Array(g.childNodes.length);
var xform = g.getAttribute("transform");
// get consolidated matrix
var glist = getTransformList(g);
var m = transformListToTransform(glist).matrix;
@ -10755,14 +10865,14 @@ this.ungroupSelectedElement = function() {
if(!orig_cblur) {
// Set group's filter to use first child's ID
if(!gfilter) {
gfilter = getElem(getUrlFromAttr(gattrs.filter).substr(1));
gfilter = getRefElem(gattrs.filter);
} else {
// Clone the group's filter
gfilter = copyElem(gfilter);
findDefs().appendChild(gfilter);
}
} else {
gfilter = getElem(getUrlFromAttr(elem.getAttribute('filter')).substr(1));
gfilter = getRefElem(elem.getAttribute('filter'));
}
// Change this in future for different filters
@ -11412,6 +11522,10 @@ function disableAdvancedTextEdit() {
unit_types.pt = inch / 72;
unit_types.pc = inch / 6;
unit_types['%'] = 0;
rect.setAttribute('style','vector-effect:non-scaling-stroke');
support.nonScalingStroke = (rect.style.vectorEffect === 'non-scaling-stroke');
svgcontent.removeChild(rect);
}());

View file

@ -2,6 +2,7 @@
<head>
<link rel="stylesheet" href="qunit/qunit.css" type="text/css"/>
<script src="../editor/jquery.js"></script>
<script type="text/javascript" src="../editor/jquery-ui/jquery-ui-1.8.custom.min.js"></script>
<script type="text/javascript" src="../editor/svgicons/jquery.svgicons.js"></script>
<script type="text/javascript" src="../editor/locale/locale.js"></script>
<script type="text/javascript" src="../editor/svgcanvas.js"></script>
@ -67,7 +68,7 @@
// "m 0,0 l 200,0 l 0,100 L 0,100"
svgCanvas.setSvgString("<svg xmlns='http://www.w3.org/2000/svg' width='400' x='300'>" +
"<path id='p1' d='M100,100 L200,100 Z'/>" +
"<path id='p1' d='M100,100 L200,100 L100,100Z'/>" +
"<path id='p2' d='m 0,0 l 200,0 l 0,100 L 0,100'/>" +
"</svg>");
@ -79,19 +80,19 @@
equal(p1.nodeName, "path", "Expected 'path', got");
equal(seglist.numberOfItems, 3, "Number of segments before conversion");
equal(seglist.numberOfItems, 4, "Number of segments before conversion");
// verify segments before conversion
curseg = seglist.getItem(0);
equal(curseg.pathSegTypeAsLetter, "M", "Before conversion, segment #1 type");
equal(curseg.pathSegTypeAsLetter.toUpperCase(), "M", "Before conversion, segment #1 type");
curseg = seglist.getItem(1);
equal(curseg.pathSegTypeAsLetter, "L", "Before conversion, segment #2 type");
curseg = seglist.getItem(2);
equal(curseg.pathSegTypeAsLetter, "Z", "Before conversion, segment #3 type" + d_abs);
equal(curseg.pathSegTypeAsLetter.toUpperCase(), "L", "Before conversion, segment #2 type");
curseg = seglist.getItem(3);
equal(curseg.pathSegTypeAsLetter.toUpperCase(), "Z", "Before conversion, segment #3 type" + d_abs);
// convert and verify segments
var d = convert(p1, true);
equal(d, "m100,100l100,0z", "Converted path to relative string");
equal(d, "m100,100l100,0l-100,0z", "Converted path to relative string");
// TODO: see why this isn't working in SVG-edit
d = convert(p2, true);
@ -188,15 +189,19 @@
// This test makes sure import/export properly handles namespaced attributes
test("Test importing/exporting namespaced attributes", function() {
expect(5);
var set = svgCanvas.setSvgString('<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:se="http://svg-edit.googlecode.com" xmlns:foo="http://example.com">'+
var setStr = svgCanvas.setSvgString('<svg width="640" height="480" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:se="http://svg-edit.googlecode.com" xmlns:foo="http://example.com">'+
'<image xlink:href="../editor/images/logo.png"/>' +
'<polyline id="se_test_elem" se:foo="bar"/>' +
'<polyline id="se_test_elem" se:foo="bar" foo:bar="baz"/>' +
'</svg>');
var attrVal = document.getElementById('se_test_elem').getAttributeNS("http://svg-edit.googlecode.com", "foo");
equal(attrVal === "bar", true, "Preserved namespaced attribute on import");
//
// console.log('getSvgString' in svgCanvas)
var output = svgCanvas.getSvgString();
// } catch(e) {console.log(e)}
// console.log('output',output);
var has_xlink = output.indexOf('xmlns:xlink="http://www.w3.org/1999/xlink"') !== -1;
var has_se = output.indexOf('xmlns:se=') !== -1;
var has_foo = output.indexOf('xmlns:foo=') !== -1;
@ -233,9 +238,9 @@
test("Test escaping XML entities", function() {
expect(3);
equal(svgCanvas.getPrivateMethods().toXml("<"), "&lt;", "Escaped < properly");
equal(svgCanvas.getPrivateMethods().toXml(">"), "&gt;", "Escaped > properly");
equal(svgCanvas.getPrivateMethods().toXml("&"), "&amp;", "Escaped & properly");
equal(svgCanvas.Utils.toXml("<"), "&lt;", "Escaped < properly");
equal(svgCanvas.Utils.toXml(">"), "&gt;", "Escaped > properly");
equal(svgCanvas.Utils.toXml("&"), "&amp;", "Escaped & properly");
// TODO: what about &quot; and &apos; ?
});

View file

@ -1,5 +1,5 @@
unless $gems_rake_task
if Rails.version <= "2.3.7"
if !(Rails::VERSION::MAJOR.to_i >= 2 && Rails::VERSION::MINOR.to_i >=3 && Rails::VERSION::TINY.to_i >=8)
$stderr.puts "rails_xss requires Rails 2.3.8 or later. Please upgrade to enable automatic HTML safety."
else
require 'rails_xss'

View file

@ -1,3 +1,4 @@
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)*
*2.3.7 (May 24, 2010)*

View file

@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.3.9' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'

View file

@ -2,7 +2,7 @@ module ActionMailer
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,3 +1,5 @@
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
* Version bump.

View file

@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('rack', '~> 1.1.0')
s.require_path = 'lib'

View file

@ -1088,6 +1088,9 @@ module ActionController #:nodoc:
# redirect_to post_url(@post), :status => 301
# redirect_to :action=>'atom', :status => 302
#
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
# integer, or a symbol representing the downcased, underscored and symbolized description.
#
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
@ -1097,8 +1100,7 @@ module ActionController #:nodoc:
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
#
# When using <tt>redirect_to :back</tt>, if there is no referrer,
# RedirectBackError will be raised. You may specify some fallback
# When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback
# behavior for this case by rescuing RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?

View file

@ -287,7 +287,6 @@ module ActionController
"REMOTE_ADDR" => remote_addr,
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
"HTTP_COOKIE" => encode_cookies,
"HTTP_ACCEPT" => accept,
"rack.version" => [0,1],
@ -298,6 +297,8 @@ module ActionController
"rack.run_once" => false
)
env['HTTP_COOKIE'] = encode_cookies if cookies.any?
(headers || {}).each do |key, value|
key = key.to_s.upcase.gsub(/-/, "_")
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
@ -535,7 +536,7 @@ EOF
if self.class.respond_to?(:fixture_table_names)
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
next unless respond_to?(name, true)
extras.__send__(:define_method, name) { |*args|
delegate.send(name, *args)
}

View file

@ -180,6 +180,10 @@ module ActionController
options = env[ENV_SESSION_OPTIONS_KEY]
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
request = ActionController::Request.new(env)
return response if (options[:secure] && !request.ssl?)
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid
@ -188,7 +192,9 @@ module ActionController
return response
end
if (env["rack.request.cookie_hash"] && env["rack.request.cookie_hash"][@key] != sid) || options[:expire_after]
request_cookies = env["rack.request.cookie_hash"]
if (request_cookies.nil? || request_cookies[@key] != sid) || options[:expire_after]
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
cookie << "; domain=#{options[:domain]}" if options[:domain]
cookie << "; path=#{options[:path]}" if options[:path]
@ -196,7 +202,7 @@ module ActionController
expiry = Time.now + options[:expire_after]
cookie << "; expires=#{expiry.httpdate}"
end
cookie << "; Secure" if options[:secure]
cookie << "; secure" if options[:secure]
cookie << "; HttpOnly" if options[:httponly]
headers = response[1]

View file

@ -101,8 +101,9 @@ module ActionController
session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY]
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
request = ActionController::Request.new(env)
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
persistent_session_id!(session_data)

View file

@ -1,3 +1,5 @@
require 'uri'
module ActionController
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.

View file

@ -2,7 +2,7 @@ module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -877,9 +877,9 @@ module ActionView
def value_before_type_cast(object, method_name)
unless object.nil?
object.respond_to?(method_name) ?
object.send(method_name) :
object.send(method_name + "_before_type_cast")
object.respond_to?(method_name + "_before_type_cast") ?
object.send(method_name + "_before_type_cast") :
object.send(method_name)
end
end

View file

@ -42,6 +42,10 @@ class CookieTest < ActionController::TestCase
cookies["user_name"] = { :value => "david", :httponly => true }
end
def authenticate_with_secure
cookies["user_name"] = { :value => "david", :secure => true }
end
def set_permanent_cookie
cookies.permanent[:user_name] = "Jamie"
end
@ -94,6 +98,12 @@ class CookieTest < ActionController::TestCase
assert_equal ["user_name=david; path=/; HttpOnly"], @response.headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_cookie_with_secure
get :authenticate_with_secure
assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_multiple_cookies
get :set_multiple_cookies

View file

@ -227,6 +227,24 @@ class IntegrationTestTest < Test::Unit::TestCase
end
end
require 'active_record_unit'
# Tests that fixtures are accessible in the integration test sessions
class IntegrationTestWithFixtures < ActiveRecordTestCase
include ActionController::Integration::Runner
fixtures :companies
def test_fixtures_in_new_session
sym = :thirty_seven_signals
# fixtures are accessible in main session
assert_not_nil companies(sym)
# create a new session and the fixtures should be accessible in it as well
session1 = open_session { |sess| }
assert_not_nil session1.companies(sym)
end
end
# Tests that integration tests don't call Controller test methods for processing.
# Integration tests have their own setup and teardown.
class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest

View file

@ -6,7 +6,6 @@ class CookieStoreTest < ActionController::IntegrationTest
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
DispatcherApp = ActionController::Dispatcher.new
CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
@ -62,10 +61,6 @@ class CookieStoreTest < ActionController::IntegrationTest
def rescue_action(e) raise end
end
def setup
@integration_session = open_session(CookieStoreApp)
end
def test_raises_argument_error_if_missing_session_key
assert_raise(ArgumentError, nil.inspect) {
ActionController::Session::CookieStore.new(nil,
@ -152,6 +147,23 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
def test_does_not_set_secure_cookies_over_http
with_test_route_set(:secure => true) do
get '/set_session_value'
assert_response :success
assert_equal nil, headers['Set-Cookie']
end
end
def test_does_set_secure_cookies_over_https
with_test_route_set(:secure => true) do
get '/set_session_value', nil, 'HTTPS' => 'on'
assert_response :success
assert_equal ["_myapp_session=#{response.body}; path=/; secure; HttpOnly"],
headers['Set-Cookie']
end
end
def test_close_raises_when_data_overflows
with_test_route_set do
assert_raise(ActionController::Session::CookieStore::CookieOverflow) {
@ -272,13 +284,17 @@ class CookieStoreTest < ActionController::IntegrationTest
end
private
def with_test_route_set
def with_test_route_set(options = {})
with_routing do |set|
set.draw do |map|
map.with_options :controller => "cookie_store_test/test" do |c|
c.connect "/:action"
end
end
options = { :key => SessionKey, :secret => SessionSecret }.merge!(options)
@integration_session = open_session(ActionController::Session::CookieStore.new(DispatcherApp, options))
yield
end
end

View file

@ -37,13 +37,6 @@ class MemCacheStoreTest < ActionController::IntegrationTest
begin
DispatcherApp = ActionController::Dispatcher.new
MemCacheStoreApp = ActionController::Session::MemCacheStore.new(
DispatcherApp, :key => '_session_id')
def setup
@integration_session = open_session(MemCacheStoreApp)
end
def test_setting_and_getting_session_value
with_test_route_set do
@ -177,14 +170,18 @@ class MemCacheStoreTest < ActionController::IntegrationTest
end
private
def with_test_route_set
def with_test_route_set(options = {})
with_routing do |set|
set.draw do |map|
map.with_options :controller => "mem_cache_store_test/test" do |c|
c.connect "/:action"
end
end
options = { :key => '_session_id' }.merge!(options)
@integration_session = open_session(ActionController::Session::MemCacheStore.new(DispatcherApp, options))
yield
end
end
end
end

View file

@ -91,16 +91,6 @@ end
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
class Developer
def name_before_type_cast
"David"
end
def name
"Santiago"
end
end
def setup
super
@ -266,13 +256,6 @@ class FormHelperTest < ActionView::TestCase
assert_equal object_name, "post[]"
end
def test_text_field_from_a_user_defined_method
@developer = Developer.new
assert_dom_equal(
'<input id="developer_name" name="developer[name]" size="30" type="text" value="Santiago" />', text_field("developer", "name")
)
end
def test_hidden_field
assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title")

View file

@ -1,3 +1,7 @@
*2.3.10 (October 15, 2010)*
* Security Release to fix CVE-2010-3933
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)*
*2.3.7 (May 24, 2010)*

View file

@ -192,7 +192,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"

View file

@ -27,7 +27,7 @@ conn[:socket] = Pathname.glob(%w[
/tmp/mysql.sock
/var/mysql/mysql.sock
/var/run/mysqld/mysqld.sock
]).find { |path| path.socket? }
]).find { |path| path.socket? }.to_s
ActiveRecord::Base.establish_connection(conn)
@ -60,7 +60,7 @@ end
sqlfile = "#{__DIR__}/performance.sql"
if File.exists?(sqlfile)
mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 }
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
else
puts 'Generating data...'
@ -90,7 +90,7 @@ else
)
end
mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
mysqldump_bin = %w[mysqldump mysqldump5].detect { |bin| `which #{bin}`.length > 0 }
`#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
end
@ -157,6 +157,40 @@ RBench.run(TIMES) do
ar { Exhibit.transaction { Exhibit.new } }
end
report 'Model.find(id)' do
id = Exhibit.first.id
ar { Exhibit.find(id) }
end
report 'Model.find_by_sql' do
ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first }
end
report 'Model.log', (TIMES * 10) do
ar { Exhibit.connection.send(:log, "hello", "world") {} }
end
report 'AR.execute(query)', (TIMES / 2) do
ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
end
report 'Model.find(id)' do
id = Exhibit.first.id
ar { Exhibit.find(id) }
end
report 'Model.find_by_sql' do
ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first }
end
report 'Model.log', (TIMES * 10) do
ar { Exhibit.connection.send(:log, "hello", "world") {} }
end
report 'AR.execute(query)', (TIMES / 2) do
ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
end
summary 'Total'
end

View file

@ -283,7 +283,7 @@ module ActiveRecord
through_records.flatten!
else
options = {}
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] || reflection.options[:order]
options[:order] = reflection.options[:order]
options[:conditions] = reflection.options[:conditions]
records.first.class.preload_associations(records, through_association, options)

View file

@ -332,6 +332,7 @@ module ActiveRecord
def include?(record)
return false unless record.is_a?(@reflection.klass)
return include_in_memory?(record) if record.new_record?
load_target if @reflection.options[:finder_sql] && !loaded?
return @target.include?(record) if loaded?
exists?(record)
@ -491,8 +492,8 @@ module ActiveRecord
def callbacks_for(callback_name)
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
end
end
def ensure_owner_is_not_new
if @owner.new_record?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
@ -503,6 +504,18 @@ module ActiveRecord
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
@target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
end
def include_in_memory?(record)
if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
@owner.send(proxy_reflection.through_reflection.name.to_sym).each do |source|
source_reflection_target = source.send(proxy_reflection.source_reflection.name)
return true if source_reflection_target.respond_to?(:include?) ? source_reflection_target.include?(record) : source_reflection_target == record
end
false
else
@target.include?(record)
end
end
end
end
end

View file

@ -274,7 +274,7 @@ module ActiveRecord
if Hash === options # legacy support, since this param was a string
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name] || index_name
index_name = options[:name].to_s if options[:name]
else
index_type = options
end
@ -347,6 +347,7 @@ module ActiveRecord
# as there's no way to determine the correct answer in that case.
def index_exists?(table_name, index_name, default)
return default unless respond_to?(:indexes)
index_name = index_name.to_s
indexes(table_name).detect { |i| i.name == index_name }
end

View file

@ -120,7 +120,7 @@ module ActiveRecord
options ||= {}
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
extend Module.new(&block) if block_given?
unless Scope === proxy_scope
unless (Scope === proxy_scope || ActiveRecord::Associations::AssociationCollection === proxy_scope)
@current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
end
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)

View file

@ -286,9 +286,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
elsif attributes['id']
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
self.send(association_name.to_s+'=', existing_record)
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@ -358,16 +356,11 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_records.size == 0 # Existing record but not yet associated
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
@ -387,7 +380,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
# Determines if a new record should be built by checking for
# Determines if a new record should be build by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
@ -403,5 +396,9 @@ module ActiveRecord
end
end
def raise_nested_attributes_record_not_found(association_name, record_id)
reflection = self.class.reflect_on_association(association_name)
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
end

View file

@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -363,7 +363,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal post_tags, eager_post_tags
end
def test_eager_with_has_many_through_association_with_order
author_comments = Author.find(authors(:david).id).comments_desc
eager_author_comments = Author.find(authors(:david).id, :include => :comments_desc).comments_desc
assert_equal eager_author_comments, author_comments
end
def test_eager_with_has_many_through_join_model_with_include
author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
assert_no_queries do

View file

@ -819,4 +819,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_queries(0) { david.projects.columns; david.projects.columns }
end
def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build
project = Project.new
developer = project.developers.build
assert project.developers.include?(developer)
end
end

View file

@ -1221,5 +1221,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
EOF
end
end
def test_include_method_in_has_many_association_should_return_true_for_instance_added_with_build
post = Post.new
comment = post.comments.build
assert post.comments.include?(comment)
end
end

View file

@ -343,4 +343,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
def test_include_method_in_association_through_should_return_true_for_instance_added_with_build
person = Person.new
reference = person.references.build
job = reference.build_job
assert person.jobs.include?(job)
end
def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds
author = Author.new
post = author.posts.build
comment = post.comments.build
assert author.comments.include?(comment)
end
end

View file

@ -119,6 +119,13 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
def test_index_symbol_names
assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name }
assert Person.connection.index_exists?(:people, :symbol_index_name, true)
assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name }
assert_equal true, !Person.connection.index_exists?(:people, :symbol_index_name, false)
end
def test_add_index_length_limit
good_index_name = 'x' * Person.connection.index_name_length
too_long_index_name = good_index_name + 'x'

View file

@ -146,6 +146,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
end
def test_nested_named_scopes_doesnt_duplicate_conditions_on_child_scopes
comments_scope = posts(:welcome).comments.send(:construct_sql)
named_scope_sql_conditions = posts(:welcome).comments.containing_the_letter_e.send(:current_scoped_methods)[:find][:conditions]
assert_no_match /#{comments_scope}.*#{comments_scope}/i, named_scope_sql_conditions
end
def test_has_many_through_associations_have_access_to_named_scopes
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
assert !Comment.containing_the_letter_e.empty?

View file

@ -175,6 +175,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
@pirate.ship_attributes = { :id => 1234567890 }
end
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
@ -324,13 +330,10 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert_equal 'Arr', @ship.pirate.catchphrase
end
def test_should_associate_with_record_if_parent_record_is_not_saved
@ship.destroy
@pirate = Pirate.create(:catchphrase => 'Arr')
@ship = Ship.new(:name => 'Nights Dirty Lightning', :pirate_attributes => { :id => @pirate.id, :catchphrase => @pirate.catchphrase})
assert_equal @ship.name, 'Nights Dirty Lightning'
assert_equal @pirate, @ship.pirate
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
@ship.pirate_attributes = { :id => 1234567890 }
end
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@ -434,11 +437,6 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
end
def test_should_assign_existing_children_if_parent_is_new
@pirate = Pirate.new({:catchphrase => "Don' botharr talkin' like one, savvy?"}.merge(@alternate_params))
assert_equal ['Grace OMalley', 'Privateers Greed'], [@pirate.send(@association_name)[0].name, @pirate.send(@association_name)[1].name]
end
def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
@pirate.send(association_setter, @alternate_params[association_getter].values)
@pirate.save
@ -508,8 +506,8 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end
def test_should_not_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_nothing_raised ActiveRecord::RecordNotFound do
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end
end

View file

@ -1,3 +1,4 @@
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)*
*2.3.7 (May 24, 2010)*

View file

@ -66,7 +66,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'active_resource'

View file

@ -2,7 +2,7 @@ module ActiveResource
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,3 +1,6 @@
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
* i18n: bundle i18n 0.4.1 for forward compatibility with Rails 3. Deprecates {{foo}} interpolation syntax in favor of 1.9-native %{foo}.

View file

@ -38,7 +38,7 @@ class Object
#
# foo # => ['bar', 'baz']
def returning(value)
ActiveSupport::Deprecation.warn('Object#returning has been deprecated in favor of Object#tap.', caller)
ActiveSupport::Deprecation.warn('Kernel#returning has been deprecated in favor of Object#tap.', caller)
yield(value)
value
end

View file

@ -92,6 +92,8 @@ module ActiveSupport
#
if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type)
private #{symbol.inspect} # private :mime_type
elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type)
protected #{symbol.inspect} # protected :mime_type
end # end
EOS
end

View file

@ -23,12 +23,12 @@ module ActiveSupport
run_callbacks :setup
result = super
rescue Exception => e
result = runner.puke(self.class, self.name, e)
result = runner.puke(self.class, __name__, e)
ensure
begin
run_callbacks :teardown, :enumerator => :reverse_each
rescue Exception => e
result = runner.puke(self.class, self.name, e)
result = runner.puke(self.class, __name__, e)
end
end
result

View file

@ -2,7 +2,7 @@ module ActiveSupport
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -34,6 +34,13 @@ class MemoizableTest < Test::Unit::TestCase
memoize :name, :age
protected
def memoize_protected_test
'protected'
end
memoize :memoize_protected_test
private
def is_developer?
@ -234,6 +241,13 @@ class MemoizableTest < Test::Unit::TestCase
assert_raise(RuntimeError) { company.memoize :name }
end
def test_protected_method_memoization
person = Person.new
assert_raise(NoMethodError) { person.memoize_protected_test }
assert_equal "protected", person.send(:memoize_protected_test)
end
def test_private_method_memoization
person = Person.new

View file

@ -0,0 +1,57 @@
require 'abstract_unit'
module ActiveSupport
class TestCaseTest < ActiveSupport::TestCase
class FakeRunner
attr_reader :puked
def initialize
@puked = []
end
def puke(klass, name, e)
@puked << [klass, name, e]
end
end
if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
def test_callback_with_exception
tc = Class.new(TestCase) do
setup :bad_callback
def bad_callback; raise 'oh noes' end
def test_true; assert true end
end
test_name = 'test_true'
fr = FakeRunner.new
test = tc.new test_name
test.run fr
klass, name, exception = *fr.puked.first
assert_equal tc, klass
assert_equal test_name, name
assert_equal 'oh noes', exception.message
end
def test_teardown_callback_with_exception
tc = Class.new(TestCase) do
teardown :bad_callback
def bad_callback; raise 'oh noes' end
def test_true; assert true end
end
test_name = 'test_true'
fr = FakeRunner.new
test = tc.new test_name
test.run fr
klass, name, exception = *fr.puked.first
assert_equal tc, klass
assert_equal test_name, name
assert_equal 'oh noes', exception.message
end
end
end
end

View file

@ -1,3 +1,5 @@
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
* Deprecates config.load_(once_)paths in favor of autolaod_(once_)paths. [fxn]

View file

@ -313,11 +313,11 @@ spec = Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.3')
s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.9' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.9' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.9' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.10' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.10' + PKG_BUILD)
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false

View file

@ -31,8 +31,10 @@ module Rails
def self.from_directory_name(directory_name, load_spec=true)
directory_name_parts = File.basename(directory_name).split('-')
name = directory_name_parts[0..-2].join('-')
version = directory_name_parts.last
version = directory_name_parts.find { |s| s.match(/^\d(\.\d|\.\w+)*$/) }
name = directory_name_parts[0..directory_name_parts.index(version)-1].join('-') if version
result = self.new(name, :version => version)
spec_filename = File.join(directory_name, '.specification')
if load_spec

View file

@ -101,8 +101,8 @@ module Rails
end
def version_for_dir(d)
matches = /-([^-]+)$/.match(d)
Gem::Version.new(matches[1]) if matches
version = d.split('-').find { |s| s.match(/^\d(\.\d|\.\w+)*$/) }
Gem::Version.new(version)
end
def load_specification(gem_dir)

View file

@ -2,7 +2,7 @@ module Rails
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 9
TINY = 10
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -46,14 +46,14 @@ namespace :db do
$stderr.puts "Couldn't create database for #{config.inspect}"
end
end
return # Skip the else clause of begin/rescue
return # Skip the else clause of begin/rescue
else
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection
end
rescue
case config['adapter']
when 'mysql'
when /^mysql/
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
begin
@ -159,7 +159,7 @@ namespace :db do
task :charset => :environment do
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
case config['adapter']
when 'mysql'
when /^mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
when 'postgresql'
@ -174,7 +174,7 @@ namespace :db do
task :collation => :environment do
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
case config['adapter']
when 'mysql'
when /^mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.collation
else
@ -274,7 +274,7 @@ namespace :db do
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[RAILS_ENV]["adapter"]
when "mysql", "oci", "oracle"
when /^mysql/, "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
@ -320,7 +320,7 @@ namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
when "mysql"
when /^mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
@ -354,14 +354,14 @@ namespace :db do
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
when "mysql"
when /^mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
ActiveRecord::Base.clear_active_connections!
drop_database(abcs['test'])
create_database(abcs['test'])
when "sqlite","sqlite3"
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
File.delete(dbfile) if File.exist?(dbfile)
when "sqlserver"
@ -408,7 +408,7 @@ end
def drop_database(config)
begin
case config['adapter']
when 'mysql'
when /^mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/

View file

@ -113,6 +113,14 @@ class GemDependencyTest < Test::Unit::TestCase
assert_not_nil DUMMY_GEM_C_VERSION
assert_equal '0.6.0', DUMMY_GEM_C_VERSION
end
def test_gem_load_frozen_when_platform_string_is_present
dummy_gem = Rails::GemDependency.new "dummy-gem-l"
dummy_gem.add_load_paths
dummy_gem.load
assert_not_nil DUMMY_GEM_L_VERSION
assert_equal "1.0.0", DUMMY_GEM_L_VERSION
end
def test_gem_load_missing_specification
dummy_gem = Rails::GemDependency.new "dummy-gem-d"

View file

@ -0,0 +1,28 @@
--- !ruby/object:Gem::Specification
name: dummy-gem-l
version: !ruby/object:Gem::Version
version: 1.0.0
platform: mswin32
authors:
- "Nobody"
date: 2008-10-03 00:00:00 -04:00
files:
- lib
- lib/dummy-gem-l.rb
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
specification_version: 2
summary: Dummy Gem L

View file

@ -0,0 +1 @@
DUMMY_GEM_L_VERSION="1.0.0"