<commit_before>svgEditor.readLang({
	lang: "zh-CN",
	dir : "ltr",
	common: {
		"ok": "保存",
		"cancel": "取消",
		"key_backspace": "backspace",
		"key_del": "delete",
		"key_down": "down",
		"key_up": "up",
		"more_opts": "More Options",
		"url": "URL",
		"width": "Width",
		"height": "Height"
	},
	misc: {
		"powered_by": "Powered by"
	},
	ui: {
		"toggle_stroke_tools": "Show/hide more stroke tools",
		"palette_info": "点击更改填充颜色，按住Shift键单击更改颜色中风",
		"zoom_level": "更改缩放级别",
		"panel_drag": "Drag left/right to resize side panel"
	},
	properties: {
		"id": "Identify the element",
		"fill_color": "更改填充颜色",
		"stroke_color": "中风的颜色变化",
		"stroke_style": "更改行程冲刺风格",
		"stroke_width": "笔划宽度的变化",
		"pos_x": "Change X coordinate",
		"pos_y": "Change Y coordinate",
		"linecap_butt": "Linecap: Butt",
		"linecap_round": "Linecap: Round",
		"linecap_square": "Linecap: Square",
		"linejoin_bevel": "Linejoin: Bevel",
		"linejoin_miter": "Linejoin: Miter",
		"linejoin_round": "Linejoin: Round",
		"angle": "旋转角度的变化",
		"blur": "Change gaussian blur value",
		"opacity": "更改所选项目不透明",
		"circle_cx": "改变循环的CX坐标",
		"circle_cy": "改变循环的赛扬坐标",
		"circle_r": "改变圆的半径",
		"ellipse_cx": "改变椭圆的CX坐标",
		"ellipse_cy": "改变椭圆的赛扬坐标",
		"ellipse_rx": "改变椭圆的x半径",
		"ellipse_ry": "改变椭圆的y半径",
		"line_x1": "更改行的起点的x坐标",
		"line_x2": "更改行的结束x坐标",
		"line_y1": "更改行的起点的y坐标",
		"line_y2": "更改行的结束y坐标",
		"rect_height": "更改矩形的高度",
		"rect_width": "更改矩形的宽度",
		"corner_radius": "角半径：",
		"image_width": "更改图像的宽度",
		"image_height": "更改图像高度",
		"image_url": "更改网址",
		"node_x": "Change node's x coordinate",
		"node_y": "Change node's y coordinate",
		"seg_type": "Change Segment type",
		"straight_segments": "Straight",
		"curve_segments": "Curve",
		"text_contents": "更改文字内容",
		"font_family": "更改字体家族",
		"font_size": "更改字体大小",
		"bold": "粗体",
		"italic": "斜体文本"
	},
	tools: {
		"main_menu": "Main Menu",
		"bkgnd_color_opac": "更改背景颜色/不透明",
		"connector_no_arrow": "No arrow",
		"fitToContent": "适合内容",
		"fit_to_all": "适合于所有的内容",
		"fit_to_canvas": "适合画布",
		"fit_to_layer_content": "适合层内容",
		"fit_to_sel": "适合选择",
		"align_relative_to": "相对对齐 ...",
		"relativeTo": "相对于:",
		"网页": "网页",
		"largest_object": "最大对象",
		"selected_objects": "选对象",
		"smallest_object": "最小的对象",
		"new_doc": "新形象",
		"open_doc": "打开图像",
		"export_png": "Export as PNG",
		"save_doc": "保存图像",
		"import_doc": "Import SVG",
		"align_to_page": "Align Element to Page",
		"align_bottom": "底部对齐",
		"align_center": "居中对齐",
		"align_left": "左对齐",
		"align_middle": "中间对齐",
		"align_right": "右对齐",
		"align_top": "顶端对齐",
		"mode_select": "选择工具",
		"mode_fhpath": "铅笔工具",
		"mode_line": "线工具",
		"mode_connect": "Connect two objects",
		"mode_rect": "Rectangle Tool",
		"mode_square": "Square Tool",
		"mode_fhrect": "免费手矩形",
		"mode_ellipse": "椭圆",
		"mode_circle": "圈",
		"mode_fhellipse": "免费手椭圆",
		"mode_path": "Path Tool",
		"mode_shapelib": "Shape library",
		"mode_text": "文字工具",
		"mode_image": "图像工具",
		"mode_zoom": "缩放工具",
		"mode_eyedropper": "Eye Dropper Tool",
		"no_embed": "NOTE: This image cannot be embedded. It will depend on this path to be displayed",
		"undo": "撤消",
		"redo": "重做",
		"tool_source": "编辑源",
		"wireframe_mode": "Wireframe Mode",
		"toggle_grid": "Show/Hide Grid",
		"clone": "Clone Element(s)",
		"del": "Delete Element(s)",
		"group": "族元素",
		"make_link": "Make (hyper)link",
		"set_link_url": "Set link URL (leave empty to remove)",
		"to_path": "Convert to Path",
		"reorient_path": "Reorient path",
		"ungroup": "取消组合元素",
		"docprops": "文档属性",
		"imagelib": "Image Library",
		"move_bottom": "移至底部",
		"move_top": "移动到顶部",
		"node_clone": "Clone Node",
		"node_delete": "Delete Node",
		"node_link": "Link Control Points",
		"add_subpath": "Add sub-path",
		"openclose_path": "Open/close sub-path",
		"source_save": "保存",
		"cut": "Cut",
		"copy": "Copy",
		"paste": "Paste",
		"paste_in_place": "Paste in Place",
		"delete": "Delete",
		"group": "Group",
		"move_front": "Bring to Front",
		"move_up": "Bring Forward",
		"move_down": "Send Backward",
		"move_back": "Send to Back"
	},
	layers: {
		"layer":"Layer",
		"layers": "Layers",
		"del": "删除层",
		"move_down": "层向下移动",
		"new": "新层",
		"rename": "重命名层",
		"move_up": "移动层最多",
		"dupe": "Duplicate Layer",
		"merge_down": "Merge Down",
		"merge_all": "Merge All",
		"move_elems_to": "Move elements to:",
		"move_selected": "Move selected elements to a different layer"
	},
	config: {
		"image_props": "Image Properties",
		"doc_title": "Title",
		"doc_dims": "Canvas Dimensions",
		"included_images": "Included Images",
		"image_opt_embed": "Embed data (local files)",
		"image_opt_ref": "Use file reference",
		"editor_prefs": "Editor Preferences",
		"icon_size": "Icon size",
		"language": "Language",
		"background": "Editor Background",
		"editor_img_url": "Image URL",
		"editor_bg_note": "Note: Background will not be saved with image.",
		"icon_large": "Large",
		"icon_medium": "Medium",
		"icon_small": "Small",
		"icon_xlarge": "Extra Large",
		"select_predefined": "选择预定义:",
		"units_and_rulers": "Units & Rulers",
		"show_rulers": "Show rulers",
		"base_unit": "Base Unit:",
		"grid": "Grid",
		"snapping_onoff": "Snapping on/off",
		"snapping_stepsize": "Snapping Step-Size:"
	},
	shape_cats: {
		"basic": "Basic",
		"object": "Objects",
		"symbol": "Symbols",
		"arrow": "Arrows",
		"flowchart": "Flowchart",
		"animal": "Animals",
		"game": "Cards & Chess",
		"dialog_balloon": "Dialog balloons",
		"electronics": "Electronics",
		"math": "Mathematical",
		"music": "Music",
		"misc": "Miscellaneous",
		"raphael_1": "raphaeljs.com set 1",
		"raphael_2": "raphaeljs.com set 2"
	},
	imagelib: {
		"select_lib": "Select an image library",
		"show_list": "Show library list",
		"import_single": "Import single",
		"import_multi": "Import multiple",
		"open": "Open as new document"
	},
	notification: {
		"invalidAttrValGiven":"Invalid value given",
		"noContentToFitTo":"No content to fit to",
		"dupeLayerName":"There is already a layer named that!",
		"enterUniqueLayerName":"Please enter a unique layer name",
		"enterNewLayerName":"Please enter the new layer name",
		"layerHasThatName":"Layer already has that name",
		"QmoveElemsToLayer":"Move selected elements to layer '%s'?",
		"QwantToClear":"Do you want to clear the drawing?\nThis will also erase your undo history!",
		"QwantToOpen":"Do you want to open a new file?\nThis will also erase your undo history!",
		"QerrorsRevertToSource":"There were parsing errors in your SVG source.\nRevert back to original SVG source?",
		"QignoreSourceChanges":"Ignore changes made to SVG source?",
		"featNotSupported":"Feature not supported",
		"enterNewImgURL":"Enter the new image URL",
		"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.",
		"loadingImage":"Loading image, please wait...",
		"saveFromBrowser": "Select \"Save As...\" in your browser to save this image as a %s file.",
		"noteTheseIssues": "Also note the following issues: ",
		"unsavedChanges": "There are unsaved changes.",
		"enterNewLinkURL": "Enter the new hyperlink URL",
		"errorLoadingSVG": "Error: Unable to load SVG data",
		"URLloadFail": "Unable to load from URL",
		"retrieving": "Retrieving \"%s\"..."
	}
});<commit_msg>correct some mistake<commit_after>svgEditor.readLang({
	lang: "zh-CN",
	dir : "ltr",
	common: {
		"ok": "保存",
		"cancel": "取消",
		"key_backspace": "退格",
		"key_del": "删除",
		"key_down": "下",
		"key_up": "上",
		"more_opts": "More Options",
		"url": "URL",
		"width": "宽度",
		"height": "高度"
	},
	misc: {
		"powered_by": "Powered by"
	},
	ui: {
		"toggle_stroke_tools": "显示/隐藏 更多边线工具",
		"palette_info": "点击更改填充颜色，按住Shift键单击更改边线颜色",
		"zoom_level": "更改缩放级别",
		"panel_drag": "左右拖拽调整面板大小"
	},
	properties: {
		"id": "Identify the element",
		"fill_color": "更改填充颜色",
		"stroke_color": "更改边线颜色",
		"stroke_style": "更改行程冲刺风格",
		"stroke_width": "笔划宽度的变化",
		"pos_x": "Change X coordinate",
		"pos_y": "Change Y coordinate",
		"linecap_butt": "Linecap: Butt",
		"linecap_round": "Linecap: Round",
		"linecap_square": "Linecap: Square",
		"linejoin_bevel": "Linejoin: Bevel",
		"linejoin_miter": "Linejoin: Miter",
		"linejoin_round": "Linejoin: Round",
		"angle": "旋转角度的变化",
		"blur": "Change gaussian blur value",
		"opacity": "更改所选项目不透明",
		"circle_cx": "改变循环的CX坐标",
		"circle_cy": "改变循环的赛扬坐标",
		"circle_r": "改变圆的半径",
		"ellipse_cx": "改变椭圆的CX坐标",
		"ellipse_cy": "改变椭圆的赛扬坐标",
		"ellipse_rx": "改变椭圆的x半径",
		"ellipse_ry": "改变椭圆的y半径",
		"line_x1": "更改行的起点的x坐标",
		"line_x2": "更改行的结束x坐标",
		"line_y1": "更改行的起点的y坐标",
		"line_y2": "更改行的结束y坐标",
		"rect_height": "更改矩形的高度",
		"rect_width": "更改矩形的宽度",
		"corner_radius": "角半径：",
		"image_width": "更改图像的宽度",
		"image_height": "更改图像高度",
		"image_url": "更改网址",
		"node_x": "Change node's x coordinate",
		"node_y": "Change node's y coordinate",
		"seg_type": "Change Segment type",
		"straight_segments": "Straight",
		"curve_segments": "Curve",
		"text_contents": "更改文字内容",
		"font_family": "更改字体家族",
		"font_size": "更改字体大小",
		"bold": "粗体",
		"italic": "斜体文本"
	},
	tools: {
		"main_menu": "Main Menu",
		"bkgnd_color_opac": "更改背景颜色/不透明",
		"connector_no_arrow": "No arrow",
		"fitToContent": "适合内容",
		"fit_to_all": "适合于所有的内容",
		"fit_to_canvas": "适合画布",
		"fit_to_layer_content": "适合层内容",
		"fit_to_sel": "适合选择",
		"align_relative_to": "相对对齐 ...",
		"relativeTo": "相对于:",
		"网页": "网页",
		"largest_object": "最大对象",
		"selected_objects": "选对象",
		"smallest_object": "最小的对象",
		"new_doc": "新形象",
		"open_doc": "打开图像",
		"export_png": "Export as PNG",
		"save_doc": "保存图像",
		"import_doc": "Import SVG",
		"align_to_page": "Align Element to Page",
		"align_bottom": "底部对齐",
		"align_center": "居中对齐",
		"align_left": "左对齐",
		"align_middle": "中间对齐",
		"align_right": "右对齐",
		"align_top": "顶端对齐",
		"mode_select": "选择工具",
		"mode_fhpath": "铅笔工具",
		"mode_line": "线工具",
		"mode_connect": "Connect two objects",
		"mode_rect": "Rectangle Tool",
		"mode_square": "Square Tool",
		"mode_fhrect": "免费手矩形",
		"mode_ellipse": "椭圆",
		"mode_circle": "圈",
		"mode_fhellipse": "免费手椭圆",
		"mode_path": "Path Tool",
		"mode_shapelib": "Shape library",
		"mode_text": "文字工具",
		"mode_image": "图像工具",
		"mode_zoom": "缩放工具",
		"mode_eyedropper": "Eye Dropper Tool",
		"no_embed": "NOTE: This image cannot be embedded. It will depend on this path to be displayed",
		"undo": "撤消",
		"redo": "重做",
		"tool_source": "编辑源",
		"wireframe_mode": "Wireframe Mode",
		"toggle_grid": "Show/Hide Grid",
		"clone": "Clone Element(s)",
		"del": "Delete Element(s)",
		"group": "族元素",
		"make_link": "Make (hyper)link",
		"set_link_url": "Set link URL (leave empty to remove)",
		"to_path": "Convert to Path",
		"reorient_path": "Reorient path",
		"ungroup": "取消组合元素",
		"docprops": "文档属性",
		"imagelib": "Image Library",
		"move_bottom": "移至底部",
		"move_top": "移动到顶部",
		"node_clone": "Clone Node",
		"node_delete": "Delete Node",
		"node_link": "Link Control Points",
		"add_subpath": "Add sub-path",
		"openclose_path": "Open/close sub-path",
		"source_save": "保存",
		"cut": "Cut",
		"copy": "Copy",
		"paste": "Paste",
		"paste_in_place": "Paste in Place",
		"delete": "Delete",
		"group": "Group",
		"move_front": "Bring to Front",
		"move_up": "Bring Forward",
		"move_down": "Send Backward",
		"move_back": "Send to Back"
	},
	layers: {
		"layer":"Layer",
		"layers": "Layers",
		"del": "删除层",
		"move_down": "层向下移动",
		"new": "新层",
		"rename": "重命名层",
		"move_up": "移动层最多",
		"dupe": "Duplicate Layer",
		"merge_down": "Merge Down",
		"merge_all": "Merge All",
		"move_elems_to": "Move elements to:",
		"move_selected": "Move selected elements to a different layer"
	},
	config: {
		"image_props": "Image Properties",
		"doc_title": "Title",
		"doc_dims": "Canvas Dimensions",
		"included_images": "Included Images",
		"image_opt_embed": "Embed data (local files)",
		"image_opt_ref": "Use file reference",
		"editor_prefs": "Editor Preferences",
		"icon_size": "Icon size",
		"language": "Language",
		"background": "Editor Background",
		"editor_img_url": "Image URL",
		"editor_bg_note": "Note: Background will not be saved with image.",
		"icon_large": "Large",
		"icon_medium": "Medium",
		"icon_small": "Small",
		"icon_xlarge": "Extra Large",
		"select_predefined": "选择预定义:",
		"units_and_rulers": "Units & Rulers",
		"show_rulers": "Show rulers",
		"base_unit": "Base Unit:",
		"grid": "Grid",
		"snapping_onoff": "Snapping on/off",
		"snapping_stepsize": "Snapping Step-Size:"
	},
	shape_cats: {
		"basic": "Basic",
		"object": "Objects",
		"symbol": "Symbols",
		"arrow": "Arrows",
		"flowchart": "Flowchart",
		"animal": "Animals",
		"game": "Cards & Chess",
		"dialog_balloon": "Dialog balloons",
		"electronics": "Electronics",
		"math": "Mathematical",
		"music": "Music",
		"misc": "Miscellaneous",
		"raphael_1": "raphaeljs.com set 1",
		"raphael_2": "raphaeljs.com set 2"
	},
	imagelib: {
		"select_lib": "Select an image library",
		"show_list": "Show library list",
		"import_single": "Import single",
		"import_multi": "Import multiple",
		"open": "Open as new document"
	},
	notification: {
		"invalidAttrValGiven":"Invalid value given",
		"noContentToFitTo":"No content to fit to",
		"dupeLayerName":"There is already a layer named that!",
		"enterUniqueLayerName":"Please enter a unique layer name",
		"enterNewLayerName":"Please enter the new layer name",
		"layerHasThatName":"Layer already has that name",
		"QmoveElemsToLayer":"Move selected elements to layer '%s'?",
		"QwantToClear":"Do you want to clear the drawing?\nThis will also erase your undo history!",
		"QwantToOpen":"Do you want to open a new file?\nThis will also erase your undo history!",
		"QerrorsRevertToSource":"There were parsing errors in your SVG source.\nRevert back to original SVG source?",
		"QignoreSourceChanges":"Ignore changes made to SVG source?",
		"featNotSupported":"Feature not supported",
		"enterNewImgURL":"Enter the new image URL",
		"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.",
		"loadingImage":"Loading image, please wait...",
		"saveFromBrowser": "Select \"Save As...\" in your browser to save this image as a %s file.",
		"noteTheseIssues": "Also note the following issues: ",
		"unsavedChanges": "There are unsaved changes.",
		"enterNewLinkURL": "Enter the new hyperlink URL",
		"errorLoadingSVG": "Error: Unable to load SVG data",
		"URLloadFail": "Unable to load from URL",
		"retrieving": "Retrieving \"%s\"..."
	}
});<|endoftext|>


<commit_before>import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
import Layout from '@theme/Layout'
import React from 'react'

function IntroHeading({ ...siteConfig }) {
  return (

ts-jest

{siteConfig.tagline}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

        It supports all features of TypeScript including type-checking.{' '}

          Read more about Babel 7preset-typescript vs TypeScript(and ts-jest)

  )
}

function IntroDescription() {
  return (

          Ask for some help in the ts-jest community of Slack

          Before reporting any issue, be sure to check the troubleshooting page

          Looking for collaborators. Want to help improve `ts-jest`?

  )
}

function Home() {
  const context = useDocusaurusContext()
  const { siteConfig = {} } = context

  return (
    <Layout title={siteConfig.title} description="ts-jest official documentation site">

        <IntroHeading {...siteConfig} />
        <IntroDescription />

Usage

            Refer to the{' '}

              {' '}
              installation{' '}

            and{' '}

              {' '}
              configuration{' '}
            {' '}
            instructions.

Built With

                TypeScript
              {' '}
              - JavaScript that scales

                Jest
              {' '}
              - Delightful JavaScript Testing

                Jest
              {' '}
              - Jest processor for TypeScript{' '}

                (yes, ts-jest uses itself for its tests)

Contributing

            Please read{' '}

              contributing
            {' '}
            for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

            We DO NOT use{' '}

              SemVer

            . Our Major version follows Jest, which means that our **minor changes can be breaking.** Please make sure
            to pin your version of ts-jest to a specific version if this is a problem. For the versions
            available, see the{' '}

              tags on this repository.

Authors/Maintainers

                Kulshekhar Kabra
              {' '}
              -{' '}

                kulshekhar

                Gustav Wengel
              {' '}
              -{' '}

                GeeWee

                Ahn
              {' '}
              -{' '}

                ahnpnl

                Huafu Gandon
              {' '}
              -{' '}

                huafu

            See also the list of{' '}

              contributors
            {' '}
            who participated in this project.

Supporters

                  JetBrains
                {' '}
                has been kind enough to support ts-jest with an{' '}

                  open source license

                .

License

            This project is licensed under the MIT License - see the{' '}

              {' '}
              LICENSE.md
            {' '}
            file for details

    </Layout>
  )
}

export default Home
<commit_msg>docs(devs-infra): correct some mistakes on doc homepage (#2190)<commit_after>import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
import Layout from '@theme/Layout'
import React from 'react'

function IntroHeading({ ...siteConfig }) {
  return (

ts-jest

{siteConfig.tagline}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

          {' '}

        It supports all features of TypeScript including type-checking.{' '}

          Read more about Babel 7 preset-typescript vs TypeScript(and ts-jest)

  )
}

function IntroDescription() {
  return (

          Ask for some help in the ts-jest community of Slack

          Before reporting any issue, be sure to check the troubleshooting page

          Looking for collaborators. Want to help improve ts-jest ?

  )
}

function Home() {
  const context = useDocusaurusContext()
  const { siteConfig = {} } = context

  return (
    <Layout title={siteConfig.title} description="ts-jest official documentation site">

        <IntroHeading {...siteConfig} />
        <IntroDescription />

Usage

            Refer to the{' '}

              {' '}
              installation{' '}

            and{' '}

              {' '}
              configuration{' '}
            {' '}
            instructions.

Built With

                TypeScript
              {' '}
              - JavaScript that scales

                Jest
              {' '}
              - Delightful JavaScript Testing

                ts-jest
              {' '}
              - Jest processor for TypeScript{' '}

                (yes, ts-jest uses itself for its tests)

Contributing

            Please read{' '}

              contributing
            {' '}
            for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

            We DO NOT use{' '}

              SemVer

            . Our Major version follows Jest, which means that our minor changes can be breaking. Please make sure
            to pin your version of ts-jest to a specific version if this is a problem. For the versions
            available, see the{' '}

              tags on this repository.

Authors/Maintainers

                Kulshekhar Kabra
              {' '}
              -{' '}

                kulshekhar

                Gustav Wengel
              {' '}
              -{' '}

                GeeWee

                Ahn
              {' '}
              -{' '}

                ahnpnl

                Huafu Gandon
              {' '}
              -{' '}

                huafu

            See also the list of{' '}

              contributors
            {' '}
            who participated in this project.

Supporters

                  JetBrains
                {' '}
                has been kind enough to support ts-jest with an{' '}

                  open source license

                .

License

            This project is licensed under the MIT License - see the{' '}

              {' '}
              LICENSE.md
            {' '}
            file for details

    </Layout>
  )
}

export default Home
<|endoftext|>


<commit_before>/* List screen to view, add, or delete plots */

//get transectID from calling window
var args = arguments[0];
var transectID = args.transectID;
$.tbl.transectID = transectID;
var siteID = args.siteID;

populateTable();
toggleAddBtn();

/* Nav Bar Label */

// Build title label
var parkName = "";
var transectName ="";

try {
	var db = Ti.Database.open('ltemaDB');

	resultRow = db.execute (	'SELECT p.park_name, t.transect_name \
								FROM park p, transect t, site_survey s \
								WHERE s.site_id = t.site_id \
								AND p.park_id = s.park_id \
								AND t.transect_id = ?' , transectID);
	parkName = resultRow.fieldByName('park_name');
	transectName = resultRow.fieldByName('transect_name');
} catch (e) {
	var errorMessage = e.message;
	Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
} finally {
	resultRow.close();
	db.close();
}

var labelText = parkName + ' - ' + transectName;

var titleLabel = Titanium.UI.createLabel({
	top:10,
	text: labelText,
	textAlign:'center',
	font:{fontSize:20,fontWeight:'bold'},
});

// Associate label to title
$.plotsWin.setTitleControl(titleLabel);

/* Event Listeners */

Ti.App.addEventListener("app:refreshPlots", function(e) {
	populateTable();
	toggleAddBtn();
});

//Plot TableView - event listener
$.tbl.addEventListener('click', function(e){
	//info button clicked, display modal
	if(e.source.toString() == '[object TiUIButton]') {
		var modal = Alloy.createController("plotsModal", {plotID:e.rowData.plotID, title:e.rowData.title}).getView();
		modal.open({
			modal : true,
			modalTransitionStyle : Ti.UI.iPhone.MODAL_TRANSITION_STYLE_COVER_VERTICAL,
			modalStyle : Ti.UI.iPhone.MODAL_PRESENTATION_PAGESHEET,
			navBarHidden : false
		});
	//row clicked, get transect view
	}else{
		//open plot observations
		var observations = Alloy.createController("plotObservations", {plotID:e.rowData.plotID}).getView();
		var nav = Alloy.Globals.navMenu;
		nav.openWindow(observations);
	}
});

//Delete - event listener
$.tbl.addEventListener('delete', function(e) {
	//get the plot_id of the current row to be deleted
	var currentPlotID = e.rowData.plotID;
	try{
		//open database
		var db = Ti.Database.open('ltemaDB');

		//GET FOLDER NAME - Retrieve site survery, year, park
		var rows = db.execute('SELECT year, protocol_name, park_name \
							FROM site_survey s, protocol p, park prk \
							WHERE s.protocol_id = p.protocol_id \
							AND s.park_id = prk.park_id \
							AND site_id = ?', siteID);

		//Name the directory
		var year = rows.fieldByName('year');
		var protocolName = rows.fieldByName('protocol_name');
		var parkName = rows.fieldByName('park_name');

		var folder = year + ' - ' + protocolName + ' - ' + parkName;

		//delete associated media files
		var plotFiles = db.execute('SELECT media_name \
												FROM media m, plot p \
												WHERE m.media_id = p.media_id \
												AND p.plot_id = ? ', currentPlotID);

		var fileName = plotFiles.fieldByName('media_name');
		deleteImage(fileName, folder);

		var plotObservationFiles = db.execute('SELECT media_name \
												FROM media m, plot_observation po \
												WHERE m.media_id = po.media_id \
												AND po.plot_id = ? ', currentPlotID);

		while (plotObservationFiles.isValidRow()) {
			var fileName = plotObservationFiles.fieldByName('media_name');
			deleteImage(fileName, folder);
			plotObservationFiles.next();
		}

		//delete current row from the database
		db.execute('DELETE FROM plot WHERE plot_id = ?', currentPlotID);

	} catch(e) {
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		//rows.close();
		//plotFiles.close();
		//plotObservationFiles.close();
		db.close();
		toggleEditBtn();
	}
});

/* Functions */

function populateTable() {
	//Clear the table if there is anything in it
	var rd = [];
	$.tbl.data = rd;
	try {
		//open database
		var db = Ti.Database.open('ltemaDB');

		//Query - Retrieve existing plots from database
		var rows = db.execute('SELECT plot_id, plot_name \
							FROM plot \
							WHERE transect_id = ?', $.tbl.transectID);

		//Get requested data from each row in table
		while (rows.isValidRow()) {
			var plotID = rows.fieldByName('plot_id');
			var plotName = rows.fieldByName('plot_name');

			var groundCoverRows = db.execute('SELECT sum(ground_cover) \
											FROM plot_observation \
											WHERE plot_id = ?', plotID);

			var totalGroundCover = groundCoverRows.fieldByName('sum(ground_cover)');

			//create a new row
				var newRow = Ti.UI.createTableViewRow({
					title : plotName,
					plotID : plotID,
					height: 60,
					font: {fontSize: 20}
				});

			//add the total ground cover label
				var groundCoverLabel = Ti.UI.createLabel({
					text: totalGroundCover + '%',
					right: 55,
					font: {fontSize: 20}
				});
				newRow.add(groundCoverLabel);

				//create and add info icon for the row
				var infoButton = Ti.UI.createButton({
					style : Titanium.UI.iPhone.SystemButton.DISCLOSURE,
					right : 15,
					height: 60,
					width: 60
				});
				newRow.add(infoButton);

				//Add row to the table view
				$.tbl.appendRow(newRow);

			rows.next();
		}
	} catch(e){
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		rows.close();
		db.close();
		toggleEditBtn();
	}
}

//EDIT BUTTON - toggle edit mode
function editBtn(e){

	//enable or disable edit mode
	if (e.source.title == "Edit") {
		$.tbl.editing = true;
		e.source.title = "Done";
		//disable the add button during edit mode
		$.addPlot.enabled = false;

	} else {
		$.tbl.editing = false;
		e.source.title = "Edit";
		toggleAddBtn();
	}
}

//ADD BUTTON - add a new plot
function addBtn(){
	//Navigation to addPlot
	var addPlot = Alloy.createController("addPlot", {transectID: $.tbl.transectID}).getView();
	var nav = Alloy.Globals.navMenu;
	nav.openWindow(addPlot);
}

function toggleAddBtn(){
	//check if any rows exists
	if(showTotalRowNumber() > 0){

		//get the total ground cover of the last plot entry and name
		var totalGroundCover = getTotalGroundCover().totalGroundCover;
		var lastPlotEntryName = getTotalGroundCover().lastPlotEntryName;

			//check the total ground cover of last plot entry
			if(totalGroundCover < 100){
				//show error message
				var addErrorMessage = "* " + lastPlotEntryName + "'s " + "Total Ground Coverage is less than 100%";
				$.addPlotError.text = addErrorMessage;
				$.addPlotError.visible = true;

				//disable add plot button
				$.addPlot.enabled = false;

				//set the last plot entry to red text
				var lastRow = showTotalRowNumber() - 1;
				$.tbl.data[0].rows[lastRow].color = "red";

			}else{
				//if the total ground cover is 100% or greater
				$.addPlot.enabled = true;
				$.addPlotError.visible = false;
			}
	}
}

// get the total ground cover of the last plot entry
function getTotalGroundCover(){
	try{
		//open database
		var db = Ti.Database.open('ltemaDB');

		// get the name & plot_id of the last plot entry added
		var lastRowAdded = db.execute('SELECT MAX(utc), plot_id, plot_name \
									FROM plot \
									WHERE transect_id = ?', $.tbl.transectID);

		var lastEntryPlotID = lastRowAdded.fieldByName('plot_id');
		var lastEntryPlotName = lastRowAdded.fieldByName('plot_name');

		//delete current row from the database
		var rows = db.execute('SELECT sum(ground_cover) \
							FROM plot_observation \
							WHERE plot_id = ?',  lastEntryPlotID);

		var totalGroundCover = rows.fieldByName('sum(ground_cover)');

	} catch(e) {
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		db.close();
		var lastPlotEntryName = lastEntryPlotName;
		return {totalGroundCover:totalGroundCover, lastPlotEntryName:lastPlotEntryName};
	}
}

//Enable or Disable the Edit button
function toggleEditBtn(){
	//get the number of total rows
	var numRows = showTotalRowNumber();
	//if no rows exist
	if(numRows <= 0){
		//disable Edit Button
		$.editPlot.enabled = false;
		$.editPlot.title = "Edit";
		$.addPlot.enabled = true;
		$.tbl.editing = false;
		$.addPlotError.visible = false;
	}else{
		//enable Edit Button
		$.editPlot.enabled = true;
	}
}

//Function to get total number of rows (plots)
function showTotalRowNumber(){
	// Variable to get all section
	var allSection = $.tbl.data;

	var sectionNumber = 0;
	var totalRows = 0;

	for(sectionNumber = 0; sectionNumber < allSection.length; sectionNumber++){
		// Get rows for each section
		totalRows += allSection[sectionNumber].rowCount;
	}
	return totalRows;
}

// Delete a file from the application data directory
function deleteImage(fileName, folderName) {
	var imageDir = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, folderName);

	if (imageDir.exists()) {
		// .resolve() provides the resolved native path for the directory.
		var imageFile = Ti.Filesystem.getFile(imageDir.resolve(), fileName);
		if (imageFile.exists()) {
			imageFile.deleteFile();
		}
	}
}<commit_msg>Added condition to test for null on total ground coverage. Closes issue #31.<commit_after>/* List screen to view, add, or delete plots */

//get transectID from calling window
var args = arguments[0];
var transectID = args.transectID;
$.tbl.transectID = transectID;
var siteID = args.siteID;

populateTable();
toggleAddBtn();

/* Nav Bar Label */

// Build title label
var parkName = "";
var transectName ="";

try {
	var db = Ti.Database.open('ltemaDB');

	resultRow = db.execute (	'SELECT p.park_name, t.transect_name \
								FROM park p, transect t, site_survey s \
								WHERE s.site_id = t.site_id \
								AND p.park_id = s.park_id \
								AND t.transect_id = ?' , transectID);
	parkName = resultRow.fieldByName('park_name');
	transectName = resultRow.fieldByName('transect_name');
} catch (e) {
	var errorMessage = e.message;
	Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
} finally {
	resultRow.close();
	db.close();
}

var labelText = parkName + ' - ' + transectName;

var titleLabel = Titanium.UI.createLabel({
	top:10,
	text: labelText,
	textAlign:'center',
	font:{fontSize:20,fontWeight:'bold'},
});

// Associate label to title
$.plotsWin.setTitleControl(titleLabel);

/* Event Listeners */

Ti.App.addEventListener("app:refreshPlots", function(e) {
	populateTable();
	toggleAddBtn();
});

//Plot TableView - event listener
$.tbl.addEventListener('click', function(e){
	//info button clicked, display modal
	if(e.source.toString() == '[object TiUIButton]') {
		var modal = Alloy.createController("plotsModal", {plotID:e.rowData.plotID, title:e.rowData.title}).getView();
		modal.open({
			modal : true,
			modalTransitionStyle : Ti.UI.iPhone.MODAL_TRANSITION_STYLE_COVER_VERTICAL,
			modalStyle : Ti.UI.iPhone.MODAL_PRESENTATION_PAGESHEET,
			navBarHidden : false
		});
	//row clicked, get transect view
	}else{
		//open plot observations
		var observations = Alloy.createController("plotObservations", {plotID:e.rowData.plotID}).getView();
		var nav = Alloy.Globals.navMenu;
		nav.openWindow(observations);
	}
});

//Delete - event listener
$.tbl.addEventListener('delete', function(e) {
	//get the plot_id of the current row to be deleted
	var currentPlotID = e.rowData.plotID;
	try{
		//open database
		var db = Ti.Database.open('ltemaDB');

		//GET FOLDER NAME - Retrieve site survery, year, park
		var rows = db.execute('SELECT year, protocol_name, park_name \
							FROM site_survey s, protocol p, park prk \
							WHERE s.protocol_id = p.protocol_id \
							AND s.park_id = prk.park_id \
							AND site_id = ?', siteID);

		//Name the directory
		var year = rows.fieldByName('year');
		var protocolName = rows.fieldByName('protocol_name');
		var parkName = rows.fieldByName('park_name');

		var folder = year + ' - ' + protocolName + ' - ' + parkName;

		//delete associated media files
		var plotFiles = db.execute('SELECT media_name \
												FROM media m, plot p \
												WHERE m.media_id = p.media_id \
												AND p.plot_id = ? ', currentPlotID);

		var fileName = plotFiles.fieldByName('media_name');
		deleteImage(fileName, folder);

		var plotObservationFiles = db.execute('SELECT media_name \
												FROM media m, plot_observation po \
												WHERE m.media_id = po.media_id \
												AND po.plot_id = ? ', currentPlotID);

		while (plotObservationFiles.isValidRow()) {
			var fileName = plotObservationFiles.fieldByName('media_name');
			deleteImage(fileName, folder);
			plotObservationFiles.next();
		}

		//delete current row from the database
		db.execute('DELETE FROM plot WHERE plot_id = ?', currentPlotID);

	} catch(e) {
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		//rows.close();
		//plotFiles.close();
		//plotObservationFiles.close();
		db.close();
		toggleEditBtn();
	}
});

/* Functions */

function populateTable() {
	//Clear the table if there is anything in it
	var rd = [];
	$.tbl.data = rd;
	try {
		//open database
		var db = Ti.Database.open('ltemaDB');

		//Query - Retrieve existing plots from database
		var rows = db.execute('SELECT plot_id, plot_name \
							FROM plot \
							WHERE transect_id = ?', $.tbl.transectID);

		//Get requested data from each row in table
		while (rows.isValidRow()) {
			var plotID = rows.fieldByName('plot_id');
			var plotName = rows.fieldByName('plot_name');

			var groundCoverRows = db.execute('SELECT sum(ground_cover) \
											FROM plot_observation \
											WHERE plot_id = ?', plotID);

			var totalGroundCover = groundCoverRows.fieldByName('sum(ground_cover)');

			if (totalGroundCover == null) {
				totalGroundCover = 0;
			}

			//create a new row
				var newRow = Ti.UI.createTableViewRow({
					title : plotName,
					plotID : plotID,
					height: 60,
					font: {fontSize: 20}
				});

			//add the total ground cover label
				var groundCoverLabel = Ti.UI.createLabel({
					text: totalGroundCover + '%',
					right: 55,
					font: {fontSize: 20}
				});
				newRow.add(groundCoverLabel);

				//create and add info icon for the row
				var infoButton = Ti.UI.createButton({
					style : Titanium.UI.iPhone.SystemButton.DISCLOSURE,
					right : 15,
					height: 60,
					width: 60
				});
				newRow.add(infoButton);

				//Add row to the table view
				$.tbl.appendRow(newRow);

			rows.next();
		}
	} catch(e){
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		rows.close();
		db.close();
		toggleEditBtn();
	}
}

//EDIT BUTTON - toggle edit mode
function editBtn(e){

	//enable or disable edit mode
	if (e.source.title == "Edit") {
		$.tbl.editing = true;
		e.source.title = "Done";
		//disable the add button during edit mode
		$.addPlot.enabled = false;

	} else {
		$.tbl.editing = false;
		e.source.title = "Edit";
		toggleAddBtn();
	}
}

//ADD BUTTON - add a new plot
function addBtn(){
	//Navigation to addPlot
	var addPlot = Alloy.createController("addPlot", {transectID: $.tbl.transectID}).getView();
	var nav = Alloy.Globals.navMenu;
	nav.openWindow(addPlot);
}

function toggleAddBtn(){
	//check if any rows exists
	if(showTotalRowNumber() > 0){

		//get the total ground cover of the last plot entry and name
		var totalGroundCover = getTotalGroundCover().totalGroundCover;
		var lastPlotEntryName = getTotalGroundCover().lastPlotEntryName;

			//check the total ground cover of last plot entry
			if(totalGroundCover < 100){
				//show error message
				var addErrorMessage = "* " + lastPlotEntryName + "'s " + "Total Ground Coverage is less than 100%";
				$.addPlotError.text = addErrorMessage;
				$.addPlotError.visible = true;

				//disable add plot button
				$.addPlot.enabled = false;

				//set the last plot entry to red text
				var lastRow = showTotalRowNumber() - 1;
				$.tbl.data[0].rows[lastRow].color = "red";

			}else{
				//if the total ground cover is 100% or greater
				$.addPlot.enabled = true;
				$.addPlotError.visible = false;
			}
	}
}

// get the total ground cover of the last plot entry
function getTotalGroundCover(){
	try{
		//open database
		var db = Ti.Database.open('ltemaDB');

		// get the name & plot_id of the last plot entry added
		var lastRowAdded = db.execute('SELECT MAX(utc), plot_id, plot_name \
									FROM plot \
									WHERE transect_id = ?', $.tbl.transectID);

		var lastEntryPlotID = lastRowAdded.fieldByName('plot_id');
		var lastEntryPlotName = lastRowAdded.fieldByName('plot_name');

		//delete current row from the database
		var rows = db.execute('SELECT sum(ground_cover) \
							FROM plot_observation \
							WHERE plot_id = ?',  lastEntryPlotID);

		var totalGroundCover = rows.fieldByName('sum(ground_cover)');

	} catch(e) {
		var errorMessage = e.message;
		Ti.App.fireEvent("app:dataBaseError", {error: errorMessage});
	} finally {
		db.close();
		var lastPlotEntryName = lastEntryPlotName;
		return {totalGroundCover:totalGroundCover, lastPlotEntryName:lastPlotEntryName};
	}
}

//Enable or Disable the Edit button
function toggleEditBtn(){
	//get the number of total rows
	var numRows = showTotalRowNumber();
	//if no rows exist
	if(numRows <= 0){
		//disable Edit Button
		$.editPlot.enabled = false;
		$.editPlot.title = "Edit";
		$.addPlot.enabled = true;
		$.tbl.editing = false;
		$.addPlotError.visible = false;
	}else{
		//enable Edit Button
		$.editPlot.enabled = true;
	}
}

//Function to get total number of rows (plots)
function showTotalRowNumber(){
	// Variable to get all section
	var allSection = $.tbl.data;

	var sectionNumber = 0;
	var totalRows = 0;

	for(sectionNumber = 0; sectionNumber < allSection.length; sectionNumber++){
		// Get rows for each section
		totalRows += allSection[sectionNumber].rowCount;
	}
	return totalRows;
}

// Delete a file from the application data directory
function deleteImage(fileName, folderName) {
	var imageDir = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, folderName);

	if (imageDir.exists()) {
		// .resolve() provides the resolved native path for the directory.
		var imageFile = Ti.Filesystem.getFile(imageDir.resolve(), fileName);
		if (imageFile.exists()) {
			imageFile.deleteFile();
		}
	}
}<|endoftext|>


<commit_before>;( function() {

	"use strict";

	App.Views.ChartView = Backbone.View.extend({

		el: "#chart-view",
		events: {
			"click .chart-save-png-btn": "onSavePng",
			"click .chart-save-svg-btn": "onSaveSvg"
		},

		initialize: function( options ) {

			this.dispatcher = options.dispatcher;

			this.header = new App.Views.Chart.Header( { dispatcher: this.dispatcher } );

			this.render();

			//setup events
			App.ChartModel.on( "change", this.onChartModelChange, this );

		},

		render: function() {

			this.$el.find( ".chart-name" ).text( App.ChartModel.get( "chart-name" ) );
			this.$el.find( ".chart-subname" ).text( App.ChartModel.get( "chart-subname" ) );
			this.$el.find( ".chart-description" ).html( App.ChartModel.get( "chart-description" ) );

			//data tab
			this.$dataTab = this.$el.find( "#data-chart-tab" );
			this.$downloadBtn = this.$dataTab.find( ".download-data-btn" );
			this.$dataTableWrapper = this.$dataTab.find( ".data-table-wrapper" );

			//sources tab
			this.$sourcesTab = this.$el.find( "#sources-chart-tab" );

			var dimensionsString = App.ChartModel.get( "chart-dimensions" );
			if( App.ChartModel.get( "chart-data" ) ) {

				this.updateChart( App.ChartModel.get( "chart-data" ) );

			} else if( dimensionsString ) {

				var that = this;
				$.ajax( {
					url: Global.rootUrl + "/data/dimensions",
					data: { "dimensions": dimensionsString, "chartType": App.ChartModel.get( "chart-type" ) },
					success: function( response ) {
						if( response.data ) {
							that.updateChart( response.data, response.timeType );
						}
						if( response.datasources ) {
							that.updateSourceTab( response.datasources );
						}
						if( response.exportData ) {
							that.updateDataTab( response.exportData );
						}
					}
				} );

			}

			/*if( App.ChartModel.get( "chart-variable" ) ) {
				var that = this;
				$.ajax( {
					url: Global.rootUrl + "/variables/" + App.ChartModel.get( "chart-variable" ),
					success: function( response ) {
						that.updateChart( response.data.data );
					}
				});
			} else {
				this.updateChart( App.ChartModel.get( "chart-data" ) );
			}*/

		},

		onChartModelChange: function( evt ) {

			this.render();

		},

		onSavePng: function( evt ) {

			evt.preventDefault();
			//App.Utils.encodeSvgToPng( $( ".nvd3-svg" ).get( 0 ).innerHTML );
			var $svgCanvas = $( ".nvd3-svg" );
			if( $svgCanvas.length ) {
				saveSvgAsPng( $( ".nvd3-svg" ).get( 0 ), "chart.png");
			}

		},

		onSaveSvg: function( evt ) {

			var $btn = $( evt.currentTarget ),
				//grab all svg
				$svg = this.$el.find( "svg" ),
				svg = $svg.get(0),
				svgString = svg.outerHTML;

			var $hiddenInput = this.$el.find( "[name='export-svg']" );
			$hiddenInput.val( svgString );

		},

		updateChart: function( data, timeType ) {

			if( !data ) {
				return;
			}

			//make local copy of data for our filtering needs
			var localData = $.extend( true, localData, data );

			//filter data for selected countries
			var selectedCountries = App.ChartModel.get( "selected-countries" ),
				selectedCountriesNames = _.map( selectedCountries, function(v) { return v.name; } );

			if( selectedCountries && selectedCountriesNames.length ) {
				localData = _.filter( localData, function( value, key, list ) {
					//set color while in the loop
					var country = selectedCountries[ value.key ];
					if( country && country.color ) {
						value.color = country.color;
					}
					//actual filtering
					return ( _.indexOf( selectedCountriesNames, value.key ) > -1 );
				} );
			} else {
				//TODO - nonsense? just convert associative array to array
				localData = _.map( localData, function( value ) { return value; } );
			}

			//filter by chart time
			var chartTime = App.ChartModel.get( "chart-time" );
			if( chartTime && chartTime.length == 2 ) {

				var timeFrom = chartTime[ 0 ],
					timeTo = chartTime[ 1 ];

				_.each( localData, function( singleData, key, list ) {
					var values = _.clone( singleData.values );
					values = _.filter( values, function( value ) {
						return ( value.x >= timeFrom && value.x <= timeTo );
					} );
					singleData.values = values
				} );

			}

			//get axis configs
			var xAxis = App.ChartModel.get( "x-axis" ),
				yAxis = App.ChartModel.get( "y-axis" ),
				xAxisPrefix = ( xAxis[ "axis-prefix" ] || "" ),
				xAxisSuffix = ( xAxis[ "axis-suffix" ] || "" ),
				yAxisPrefix = ( yAxis[ "axis-prefix" ] || "" ),
				yAxisSuffix = ( yAxis[ "axis-suffix" ] || "" ),
				xAxisLabelDistance = ( +xAxis[ "axis-label-distance" ] || 0 ),
				yAxisLabelDistance = ( +yAxis[ "axis-label-distance" ] || 0 ),
				xAxisMin = ( xAxis[ "axis-min" ] || null ),
				xAxisMax = ( xAxis[ "axis-max" ] || null ),
				yAxisMin = ( yAxis[ "axis-min" ] || 0 ),
				yAxisMax = ( yAxis[ "axis-max" ] || null ),
				xAxisScale = ( xAxis[ "axis-scale" ] || "linear" ),
				yAxisScale = ( yAxis[ "axis-scale" ] || "linear" );

			var that = this;
			nv.addGraph(function() {

				function tooltipContent(key, y, e, graph) {
					return "
" + key + "

" + e + App.ChartModel.get( "unit" ) + "
";
				}

				var chartOptions = {
					transitionDuration: 300,
					margin: App.ChartModel.get( "margins" ),
					tooltipContent: tooltipContent/*,
					defined: function( d ) { return d.y != 0; }*/
				};

				//line type
				var lineType = App.ChartModel.get( "line-type" );
				if( lineType == 2 ) {
					chartOptions.defined = function( d ) { return d.y !== 0; }
				}
				if( lineType == 0 ) {
					that.$el.addClass( "line-dots" );
				} else {
					that.$el.removeClass( "line-dots" );
				}

				//depending on chart type create chart
				var chartType = App.ChartModel.get( "chart-type" );
				if( chartType == "1" ) {
					that.chart = nv.models.lineChart().options( chartOptions );
				} else if( chartType == "2" ) {
					that.chart = nv.models.scatterChart().showDistX(true).showDistY(true);
				}

				that.chart.xAxis
					.axisLabel( xAxis[ "axis-label" ] )
					.staggerLabels( true )
					.axisLabelDistance( xAxisLabelDistance )
					.tickFormat( function(d) { return that.formatTimeLabel( timeType, d, xAxisPrefix, xAxisSuffix ); });

				//get extend
				var allValues = [];
				_.each( localData, function( v, i ) {
					allValues = allValues.concat( v.values );
				} );

				var xDomain = d3.extent( allValues.map( function( d ) { return d.x; } ) ),
					yDomain = d3.extent( allValues.map( function( d ) { return d.y; } ) ),
					isClamped = false;
				if( xAxisMin && !isNaN( xAxisMin ) ) {
					xDomain[ 0 ] = xAxisMin;
					isClamped = true;
				}
				if( xAxisMax && !isNaN( xAxisMax ) ) {
					xDomain[ 1 ] = xAxisMax;
					isClamped = true;
				}
				if( yAxisMin && !isNaN( yAxisMin ) ) {
					yDomain[ 0 ] = yAxisMin;
					isClamped = true;
				}
				if( yAxisMax && !isNaN( yAxisMax ) ) {
					yDomain[ 1 ] = yAxisMax;
					isClamped = true;
				}

				//manually clamp values
				if( isClamped ) {
					that.chart.xDomain( xDomain );
					that.chart.yDomain( yDomain );
					that.chart.xScale().clamp( true );
					that.chart.yScale().clamp( true );
				}

				//set scales
				if( xAxisScale === "linear" ) {
					that.chart.xScale( d3.scale.linear() );
				} else if( xAxisScale === "log" ) {
					that.chart.xScale( d3.scale.log() );
				}
				if( yAxisScale === "linear" ) {
					that.chart.yScale( d3.scale.linear() );
				} else if( yAxisScale === "log" ) {
					that.chart.yScale( d3.scale.log() );
				}

				that.chart.yAxis
					.axisLabel( yAxis[ "axis-label" ] )
					.axisLabelDistance( yAxisLabelDistance )
					.tickFormat( function(d) { return yAxisPrefix + d + yAxisSuffix; });
				var svgSelection = d3.select( "svg" )
					.datum(localData)
					.call(that.chart);

				nv.utils.windowResize(that.chart.update);

			});

		},

		updateDataTab: function( data ) {

			this.$dataTableWrapper.empty();

			//update link
			var dimensionsString = App.ChartModel.get( "chart-dimensions" ),
				chartType = App.ChartModel.get( "chart-type" ),
				baseUrl = this.$downloadBtn.attr( "data-base-url" ),
				dimensionsUrl = encodeURIComponent( dimensionsString );
			this.$downloadBtn.attr( "href", baseUrl + "?dimensions=" + dimensionsUrl + "&chartType=" + chartType + "&export=csv" );

			var tableString = "
";

			_.each( data, function( rowData, rowIndex ) {

				var tr = "
";
				if( rowIndex == 0) {

					//create header file from cell information
					_.each( rowData, function( value ) {
						var th = "" + value + "	";
						tr += th;
					} );

				} else {

					_.each( rowData, function( value, index ) {
						var td = ( index == 0 )? "" + value + "	": "" + value + "	";
						tr += td;
					} );

				}
				tr += "
";
				tableString += tr;

			} );

			tableString += "
";

			var $table = $( tableString );
			this.$dataTableWrapper.append( $table );

		},

		updateSourceTab: function( data ) {

			var footerHtml = "",
				tabHtml = "",
				descriptiontionHtml = App.ChartModel.get( "chart-description" ),
				sourcesShortHtml = "Data sources: ",
				sourcesLongHtml = "Data sources: ";

			//construct source html
			_.each( data, function( sourceData, sourceIndex ) {
				if( sourceIndex > 0 ) {
					sourcesHtml += ", ";
				}
				sourcesShortHtml += sourceData.name;
				sourcesLongHtml += sourceData.description;
			} );

			footerHtml = descriptiontionHtml + "
" + sourcesShortHtml;
			tabHtml = descriptiontionHtml + "

" + sourcesLongHtml;

			//append to DOM
			this.$el.find( ".chart-description" ).html( footerHtml );
			this.$sourcesTab.html( tabHtml );

		},

		formatTimeLabel: function( type, d, xAxisPrefix, xAxisSuffix ) {
			//depending on type format label
			var label;
			switch( type ) {

				case "Decade":

					var decadeString = d.toString();
					decadeString = decadeString.substring( 0, decadeString.length - 1);
					decadeString = decadeString + "0s";
					label = decadeString;

					break;

				case "Quarter Century":

					var quarterString = "",
						quarter = d % 100;

					if( quarter < 25 ) {
						quarterString = "1st quarter of the";
					} else if( quarter < 50 ) {
						quarterString = "half of the";
					} else if( quarter < 75 ) {
						quarterString = "3rd quarter of the";
					} else {
						quarterString = "4th quarter of the";
					}

					var centuryString = App.Utils.centuryString( d );

					label = quarterString + " " + centuryString;

					break;

				case "Half Century":

					var halfString = "",
						half = d % 100;

					if( half < 50 ) {
						halfString = "1st half of the";
					} else {
						halfString = "2nd half of the";
					}

					var centuryString = App.Utils.centuryString( d );

					label = halfString + " " + centuryString;

					break;

				case "Century":

					label = App.Utils.centuryString( d );

					break;

				default:
					label = d.toString();
					break;
			}
			return xAxisPrefix + label + xAxisSuffix;
		}

	});

})();<commit_msg>link from sources<commit_after>;( function() {

	"use strict";

	App.Views.ChartView = Backbone.View.extend({

		el: "#chart-view",
		events: {
			"click .chart-save-png-btn": "onSavePng",
			"click .chart-save-svg-btn": "onSaveSvg"
		},

		initialize: function( options ) {

			this.dispatcher = options.dispatcher;

			this.header = new App.Views.Chart.Header( { dispatcher: this.dispatcher } );

			this.render();

			//setup events
			App.ChartModel.on( "change", this.onChartModelChange, this );

		},

		render: function() {

			this.$el.find( ".chart-name" ).text( App.ChartModel.get( "chart-name" ) );
			this.$el.find( ".chart-subname" ).text( App.ChartModel.get( "chart-subname" ) );
			this.$el.find( ".chart-description" ).html( App.ChartModel.get( "chart-description" ) );

			//data tab
			this.$dataTab = this.$el.find( "#data-chart-tab" );
			this.$downloadBtn = this.$dataTab.find( ".download-data-btn" );
			this.$dataTableWrapper = this.$dataTab.find( ".data-table-wrapper" );

			//sources tab
			this.$sourcesTab = this.$el.find( "#sources-chart-tab" );

			var dimensionsString = App.ChartModel.get( "chart-dimensions" );
			if( App.ChartModel.get( "chart-data" ) ) {

				this.updateChart( App.ChartModel.get( "chart-data" ) );

			} else if( dimensionsString ) {

				var that = this;
				$.ajax( {
					url: Global.rootUrl + "/data/dimensions",
					data: { "dimensions": dimensionsString, "chartType": App.ChartModel.get( "chart-type" ) },
					success: function( response ) {
						if( response.data ) {
							that.updateChart( response.data, response.timeType );
						}
						if( response.datasources ) {
							that.updateSourceTab( response.datasources );
						}
						if( response.exportData ) {
							that.updateDataTab( response.exportData );
						}
					}
				} );

			}

			/*if( App.ChartModel.get( "chart-variable" ) ) {
				var that = this;
				$.ajax( {
					url: Global.rootUrl + "/variables/" + App.ChartModel.get( "chart-variable" ),
					success: function( response ) {
						that.updateChart( response.data.data );
					}
				});
			} else {
				this.updateChart( App.ChartModel.get( "chart-data" ) );
			}*/

		},

		onChartModelChange: function( evt ) {

			this.render();

		},

		onSavePng: function( evt ) {

			evt.preventDefault();
			//App.Utils.encodeSvgToPng( $( ".nvd3-svg" ).get( 0 ).innerHTML );
			var $svgCanvas = $( ".nvd3-svg" );
			if( $svgCanvas.length ) {
				saveSvgAsPng( $( ".nvd3-svg" ).get( 0 ), "chart.png");
			}

		},

		onSaveSvg: function( evt ) {

			var $btn = $( evt.currentTarget ),
				//grab all svg
				$svg = this.$el.find( "svg" ),
				svg = $svg.get(0),
				svgString = svg.outerHTML;

			var $hiddenInput = this.$el.find( "[name='export-svg']" );
			$hiddenInput.val( svgString );

		},

		updateChart: function( data, timeType ) {

			if( !data ) {
				return;
			}

			//make local copy of data for our filtering needs
			var localData = $.extend( true, localData, data );

			//filter data for selected countries
			var selectedCountries = App.ChartModel.get( "selected-countries" ),
				selectedCountriesNames = _.map( selectedCountries, function(v) { return v.name; } );

			if( selectedCountries && selectedCountriesNames.length ) {
				localData = _.filter( localData, function( value, key, list ) {
					//set color while in the loop
					var country = selectedCountries[ value.key ];
					if( country && country.color ) {
						value.color = country.color;
					}
					//actual filtering
					return ( _.indexOf( selectedCountriesNames, value.key ) > -1 );
				} );
			} else {
				//TODO - nonsense? just convert associative array to array
				localData = _.map( localData, function( value ) { return value; } );
			}

			//filter by chart time
			var chartTime = App.ChartModel.get( "chart-time" );
			if( chartTime && chartTime.length == 2 ) {

				var timeFrom = chartTime[ 0 ],
					timeTo = chartTime[ 1 ];

				_.each( localData, function( singleData, key, list ) {
					var values = _.clone( singleData.values );
					values = _.filter( values, function( value ) {
						return ( value.x >= timeFrom && value.x <= timeTo );
					} );
					singleData.values = values
				} );

			}

			//get axis configs
			var xAxis = App.ChartModel.get( "x-axis" ),
				yAxis = App.ChartModel.get( "y-axis" ),
				xAxisPrefix = ( xAxis[ "axis-prefix" ] || "" ),
				xAxisSuffix = ( xAxis[ "axis-suffix" ] || "" ),
				yAxisPrefix = ( yAxis[ "axis-prefix" ] || "" ),
				yAxisSuffix = ( yAxis[ "axis-suffix" ] || "" ),
				xAxisLabelDistance = ( +xAxis[ "axis-label-distance" ] || 0 ),
				yAxisLabelDistance = ( +yAxis[ "axis-label-distance" ] || 0 ),
				xAxisMin = ( xAxis[ "axis-min" ] || null ),
				xAxisMax = ( xAxis[ "axis-max" ] || null ),
				yAxisMin = ( yAxis[ "axis-min" ] || 0 ),
				yAxisMax = ( yAxis[ "axis-max" ] || null ),
				xAxisScale = ( xAxis[ "axis-scale" ] || "linear" ),
				yAxisScale = ( yAxis[ "axis-scale" ] || "linear" );

			var that = this;
			nv.addGraph(function() {

				function tooltipContent(key, y, e, graph) {
					return "
" + key + "

" + e + App.ChartModel.get( "unit" ) + "
";
				}

				var chartOptions = {
					transitionDuration: 300,
					margin: App.ChartModel.get( "margins" ),
					tooltipContent: tooltipContent/*,
					defined: function( d ) { return d.y != 0; }*/
				};

				//line type
				var lineType = App.ChartModel.get( "line-type" );
				if( lineType == 2 ) {
					chartOptions.defined = function( d ) { return d.y !== 0; }
				}
				if( lineType == 0 ) {
					that.$el.addClass( "line-dots" );
				} else {
					that.$el.removeClass( "line-dots" );
				}

				//depending on chart type create chart
				var chartType = App.ChartModel.get( "chart-type" );
				if( chartType == "1" ) {
					that.chart = nv.models.lineChart().options( chartOptions );
				} else if( chartType == "2" ) {
					that.chart = nv.models.scatterChart().showDistX(true).showDistY(true);
				}

				that.chart.xAxis
					.axisLabel( xAxis[ "axis-label" ] )
					.staggerLabels( true )
					.axisLabelDistance( xAxisLabelDistance )
					.tickFormat( function(d) { return that.formatTimeLabel( timeType, d, xAxisPrefix, xAxisSuffix ); });

				//get extend
				var allValues = [];
				_.each( localData, function( v, i ) {
					allValues = allValues.concat( v.values );
				} );

				var xDomain = d3.extent( allValues.map( function( d ) { return d.x; } ) ),
					yDomain = d3.extent( allValues.map( function( d ) { return d.y; } ) ),
					isClamped = false;
				if( xAxisMin && !isNaN( xAxisMin ) ) {
					xDomain[ 0 ] = xAxisMin;
					isClamped = true;
				}
				if( xAxisMax && !isNaN( xAxisMax ) ) {
					xDomain[ 1 ] = xAxisMax;
					isClamped = true;
				}
				if( yAxisMin && !isNaN( yAxisMin ) ) {
					yDomain[ 0 ] = yAxisMin;
					isClamped = true;
				}
				if( yAxisMax && !isNaN( yAxisMax ) ) {
					yDomain[ 1 ] = yAxisMax;
					isClamped = true;
				}

				//manually clamp values
				if( isClamped ) {
					that.chart.xDomain( xDomain );
					that.chart.yDomain( yDomain );
					that.chart.xScale().clamp( true );
					that.chart.yScale().clamp( true );
				}

				//set scales
				if( xAxisScale === "linear" ) {
					that.chart.xScale( d3.scale.linear() );
				} else if( xAxisScale === "log" ) {
					that.chart.xScale( d3.scale.log() );
				}
				if( yAxisScale === "linear" ) {
					that.chart.yScale( d3.scale.linear() );
				} else if( yAxisScale === "log" ) {
					that.chart.yScale( d3.scale.log() );
				}

				that.chart.yAxis
					.axisLabel( yAxis[ "axis-label" ] )
					.axisLabelDistance( yAxisLabelDistance )
					.tickFormat( function(d) { return yAxisPrefix + d + yAxisSuffix; });
				var svgSelection = d3.select( "svg" )
					.datum(localData)
					.call(that.chart);

				nv.utils.windowResize(that.chart.update);

			});

		},

		updateDataTab: function( data ) {

			this.$dataTableWrapper.empty();

			//update link
			var dimensionsString = App.ChartModel.get( "chart-dimensions" ),
				chartType = App.ChartModel.get( "chart-type" ),
				baseUrl = this.$downloadBtn.attr( "data-base-url" ),
				dimensionsUrl = encodeURIComponent( dimensionsString );
			this.$downloadBtn.attr( "href", baseUrl + "?dimensions=" + dimensionsUrl + "&chartType=" + chartType + "&export=csv" );

			var tableString = "
";

			_.each( data, function( rowData, rowIndex ) {

				var tr = "
";
				if( rowIndex == 0) {

					//create header file from cell information
					_.each( rowData, function( value ) {
						var th = "" + value + "	";
						tr += th;
					} );

				} else {

					_.each( rowData, function( value, index ) {
						var td = ( index == 0 )? "" + value + "	": "" + value + "	";
						tr += td;
					} );

				}
				tr += "
";
				tableString += tr;

			} );

			tableString += "
";

			var $table = $( tableString );
			this.$dataTableWrapper.append( $table );

		},

		updateSourceTab: function( data ) {

			var footerHtml = "",
				tabHtml = "",
				descriptiontionHtml = App.ChartModel.get( "chart-description" ),
				sourcesShortHtml = "Data sources: ",
				sourcesLongHtml = "Data sources: ";

			//construct source html
			_.each( data, function( sourceData, sourceIndex ) {
				if( sourceIndex > 0 ) {
					sourcesHtml += ", ";
				}
				if( sourceData.link ) {
					sourcesShortHtml += "" + sourceData.name + "";
				} else {
					sourcesShortHtml += sourceData.name;
				}
				sourcesLongHtml += sourceData.description;
			} );

			footerHtml = descriptiontionHtml + "
" + sourcesShortHtml;
			tabHtml = descriptiontionHtml + "

" + sourcesLongHtml;

			//append to DOM
			this.$el.find( ".chart-description" ).html( footerHtml );
			this.$sourcesTab.html( tabHtml );

		},

		formatTimeLabel: function( type, d, xAxisPrefix, xAxisSuffix ) {
			//depending on type format label
			var label;
			switch( type ) {

				case "Decade":

					var decadeString = d.toString();
					decadeString = decadeString.substring( 0, decadeString.length - 1);
					decadeString = decadeString + "0s";
					label = decadeString;

					break;

				case "Quarter Century":

					var quarterString = "",
						quarter = d % 100;

					if( quarter < 25 ) {
						quarterString = "1st quarter of the";
					} else if( quarter < 50 ) {
						quarterString = "half of the";
					} else if( quarter < 75 ) {
						quarterString = "3rd quarter of the";
					} else {
						quarterString = "4th quarter of the";
					}

					var centuryString = App.Utils.centuryString( d );

					label = quarterString + " " + centuryString;

					break;

				case "Half Century":

					var halfString = "",
						half = d % 100;

					if( half < 50 ) {
						halfString = "1st half of the";
					} else {
						halfString = "2nd half of the";
					}

					var centuryString = App.Utils.centuryString( d );

					label = halfString + " " + centuryString;

					break;

				case "Century":

					label = App.Utils.centuryString( d );

					break;

				default:
					label = d.toString();
					break;
			}
			return xAxisPrefix + label + xAxisSuffix;
		}

	});

})();<|endoftext|>


<commit_before>/* global $ */

class AgentDetection {

  isUA(text){
    return navigator.userAgent.toLowerCase().indexOf(text) > -1;
  }

  isTouch() {
    return ('ontouchstart' in window);
  }

  isLegacyAndroid() {
    return this.isUA("android 2") ||
           this.isUA("android 3") ||
           this.isUA("android 4");
  }

  isAndroid() {
    this.android = this.isUA("android") && !this.isLegacyAndroid();
  }

  isLegacyIOS() {
    return this.isUA("iphone os 4") || this.isUA("cpu os 4") ||
           this.isUA("iphone os 5") || this.isUA("cpu os 5") ||
           this.isUA("iphone os 6") || this.isUA("cpu os 6") ||
           this.isUA("iphone os 7") || this.isUA("cpu os 7");
  }

  isIOS() {
    return (this.isUA("iphone os") || this.isUA("cpu os")) && !this.isLegacyIOS();
  }

  isLegacyTouch() {
    return  this.isLegacyAndroid() || this.isLegacyIOS();
  }

  isMobileIE() {
    return this.isUA("iemobile");
  }

  isIE8() {
    return this.isUA("trident/4") && !this.isMobileIE();
  }

  isIE9() {
    return this.isUA("trident/5") && !this.isMSIE10() && !this.isMobileIE();
  }

  isMSIE10() {
    return this.isUA("msie 10");
  }

  isIE10() {
    return this.isUA("trident/6") || this.isMSIE10() && !this.isMobileIE();
  }

  isIE11() {
    return this.isUA("trident/7") && !this.isMobileIE();
  }

  // Internet Explorer 6-11
  isIE() {
    return /*@cc_on!@*/false || !!document.documentMode;
  }

  isWindows() {
    return this.isUA("windows");
  }

  // Firefox 1.0+
  isFirefox() {
    return typeof window.InstallTrigger !== 'undefined';
  }

  //Opera 8.0+
  isOpera() {
    return (!!window.opr && !!opr.addons) || !!window.opera || this.isUA(' OPR/'); // eslint-disable-line
  }

  // Edge 20+
  isIEEdge() {
    return !this.isIE() && !!window.StyleMedia;
  }

  isIOSChrome() {
    return this.isUA("CriOS");
  }

  isChrome() {
    return !!window.chrome && !!window.chrome.webstore;
  }

  // Safari 3.0+ "[object HTMLElementConstructor]"
  isSafari() {
    return Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
    (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification); // eslint-disable-line
  }

  //This clunky regex is taken directly from http://detectmobilebrowsers.com/
  isMobile() {
    let stringCheck = (navigator.userAgent||navigator.vendor||window.opera);
    return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(stringCheck) ||
           /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(stringCheck.substr(0,4));
  }

  isTabletAndMobile() {
    let stringCheck = (navigator.userAgent||navigator.vendor||window.opera);
    return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(stringCheck) ||
           /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(stringCheck.substr(0,4));
  }

  addClasses() {
    let $html = $('html');
    this.UA = {
      "ua-touch"          : this.isTouch(),
      "ua-no-touch"       : !this.isTouch(),
      "ua-legacy-android" : this.isLegacyAndroid(),
      "ua-android"        : this.isAndroid(),
      "ua-legacy-ios"     : this.isLegacyIOS(),
      "ua-ios"            : this.isIOS(),
      "ua-legacy-touch"   : this.isLegacyTouch(),
      "ua-mobile-ie"      : this.isMobileIE(),
      "ua-ie8"            : this.isIE8(),
      "ua-ie9"            : this.isIE9(),
      "ua-ie10"           : this.isIE10(),
      "ua-ie11"           : this.isIE11(),
      "ua-ie"             : this.isIE(),
      "ua-windows"        : this.isWindows(),
      "ua-firefox"        : this.isFirefox(),
      "ua-opera"          : this.isOpera(),
      "ua-ie-edge"        : this.isIEEdge(),
      "ua-ios-chrome"     : this.isIOSChrome(),
      "ua-chrome"         : this.isChrome(),
      "ua-safari"         : this.isSafari(),
      "ua-mobile"         : this.isMobile()
    };

    for (var ua in this.UA) {
      if (this.UA.hasOwnProperty(ua) && this.UA[ua]) {
        $html.addClass(ua);
      }
    }
  }

  removeClasses() {
    let $html = $('html');
    let classes = $html.attr('class').split(" ");

    classes.map((element) => {
      if (element.indexOf('ua-') > -1){
        $html.removeClass(element);
      }
    });
  }
}

const instance = new AgentDetection();

export default instance;
<commit_msg>delete unused removeClasses method<commit_after>/* global $ */

class AgentDetection {

  isUA(text){
    return navigator.userAgent.toLowerCase().indexOf(text) > -1;
  }

  isTouch() {
    return ('ontouchstart' in window);
  }

  isLegacyAndroid() {
    return this.isUA("android 2") ||
           this.isUA("android 3") ||
           this.isUA("android 4");
  }

  isAndroid() {
    this.android = this.isUA("android") && !this.isLegacyAndroid();
  }

  isLegacyIOS() {
    return this.isUA("iphone os 4") || this.isUA("cpu os 4") ||
           this.isUA("iphone os 5") || this.isUA("cpu os 5") ||
           this.isUA("iphone os 6") || this.isUA("cpu os 6") ||
           this.isUA("iphone os 7") || this.isUA("cpu os 7");
  }

  isIOS() {
    return (this.isUA("iphone os") || this.isUA("cpu os")) && !this.isLegacyIOS();
  }

  isLegacyTouch() {
    return  this.isLegacyAndroid() || this.isLegacyIOS();
  }

  isMobileIE() {
    return this.isUA("iemobile");
  }

  isIE8() {
    return this.isUA("trident/4") && !this.isMobileIE();
  }

  isIE9() {
    return this.isUA("trident/5") && !this.isMSIE10() && !this.isMobileIE();
  }

  isMSIE10() {
    return this.isUA("msie 10");
  }

  isIE10() {
    return this.isUA("trident/6") || this.isMSIE10() && !this.isMobileIE();
  }

  isIE11() {
    return this.isUA("trident/7") && !this.isMobileIE();
  }

  // Internet Explorer 6-11
  isIE() {
    return /*@cc_on!@*/false || !!document.documentMode;
  }

  isWindows() {
    return this.isUA("windows");
  }

  // Firefox 1.0+
  isFirefox() {
    return typeof window.InstallTrigger !== 'undefined';
  }

  //Opera 8.0+
  isOpera() {
    return (!!window.opr && !!opr.addons) || !!window.opera || this.isUA(' OPR/'); // eslint-disable-line
  }

  // Edge 20+
  isIEEdge() {
    return !this.isIE() && !!window.StyleMedia;
  }

  isIOSChrome() {
    return this.isUA("CriOS");
  }

  isChrome() {
    return !!window.chrome && !!window.chrome.webstore;
  }

  // Safari 3.0+ "[object HTMLElementConstructor]"
  isSafari() {
    return Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
    (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification); // eslint-disable-line
  }

  //This clunky regex is taken directly from http://detectmobilebrowsers.com/
  isMobile() {
    let stringCheck = (navigator.userAgent||navigator.vendor||window.opera);
    return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(stringCheck) ||
           /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(stringCheck.substr(0,4));
  }

  isTabletAndMobile() {
    let stringCheck = (navigator.userAgent||navigator.vendor||window.opera);
    return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(stringCheck) ||
           /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(stringCheck.substr(0,4));
  }

  addClasses() {
    let $html = $('html');
    this.UA = {
      "ua-touch"          : this.isTouch(),
      "ua-no-touch"       : !this.isTouch(),
      "ua-legacy-android" : this.isLegacyAndroid(),
      "ua-android"        : this.isAndroid(),
      "ua-legacy-ios"     : this.isLegacyIOS(),
      "ua-ios"            : this.isIOS(),
      "ua-legacy-touch"   : this.isLegacyTouch(),
      "ua-mobile-ie"      : this.isMobileIE(),
      "ua-ie8"            : this.isIE8(),
      "ua-ie9"            : this.isIE9(),
      "ua-ie10"           : this.isIE10(),
      "ua-ie11"           : this.isIE11(),
      "ua-ie"             : this.isIE(),
      "ua-windows"        : this.isWindows(),
      "ua-firefox"        : this.isFirefox(),
      "ua-opera"          : this.isOpera(),
      "ua-ie-edge"        : this.isIEEdge(),
      "ua-ios-chrome"     : this.isIOSChrome(),
      "ua-chrome"         : this.isChrome(),
      "ua-safari"         : this.isSafari(),
      "ua-mobile"         : this.isMobile()
    };

    for (var ua in this.UA) {
      if (this.UA.hasOwnProperty(ua) && this.UA[ua]) {
        $html.addClass(ua);
      }
    }

  }
}

const instance = new AgentDetection();

export default instance;
<|endoftext|>


<commit_before>dojo.provide("dijit.Editor");
dojo.require("dijit._editor.RichText");
dojo.require("dijit.Toolbar");
dojo.require("dijit._editor._Plugin");
dojo.require("dijit._Container");
dojo.require("dojo.i18n");
dojo.requireLocalization("dijit._editor", "commands");

dojo.declare(
	"dijit.Editor",
	dijit._editor.RichText,
	{
	// summary: A rich-text Editing widget

		// plugins: Array
		//		a list of plugin names (as strings) or instances (as objects)
		//		for this widget.
		plugins: null,

		// extraPlugins: Array
		//		a list of extra plugin names which will be appended to plugins array
		extraPlugins: null,

		constructor: function(){
			if(!dojo.isArray(this.plugins)){
				this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
				"insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull"/*"createLink"*/];
			}

			this._plugins=[];
			this._editInterval = this.editActionInterval * 1000;
		},

		postCreate: function(){
			//for custom undo/redo
			if(this.customUndo){
				dojo['require']("dijit._editor.range");
				this._steps=this._steps.slice(0);
				this._undoedSteps=this._undoedSteps.slice(0);
//				this.addKeyHandler('z',this.KEY_CTRL,this.undo);
//				this.addKeyHandler('y',this.KEY_CTRL,this.redo);
			}
			if(dojo.isArray(this.extraPlugins)){
				this.plugins=this.plugins.concat(this.extraPlugins);
			}

//			try{
			dijit.Editor.superclass.postCreate.apply(this, arguments);

			this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang);

			if(!this.toolbar){
				// if we haven't been assigned a toolbar, create one
				var toolbarNode = dojo.doc.createElement("div");
				dojo.place(toolbarNode, this.editingArea, "before");
				this.toolbar = new dijit.Toolbar({}, toolbarNode);
			}

			dojo.forEach(this.plugins, this.addPlugin, this);
			this.onNormalizedDisplayChanged(); //update toolbar button status
//			}catch(e){ console.debug(e); }
		},
		destroy: function(){
			dojo.forEach(this._plugins, function(p){
				if(p.destroy){
					p.destroy();
				}
			});
			this._plugins=[];
			this.toolbar.destroy(); delete this.toolbar;
			this.inherited('destroy',arguments);
		},
		addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
			//	summary:
			//		takes a plugin name as a string or a plugin instance and
			//		adds it to the toolbar and associates it with this editor
			//		instance. The resulting plugin is added to the Editor's
			//		plugins array. If index is passed, it's placed in the plugins
			//		array at that index. No big magic, but a nice helper for
			//		passing in plugin names via markup.
			//	plugin: String, args object or plugin instance. Required.
			//	args: This object will be passed to the plugin constructor.
			//	index:
			//		Integer, optional. Used when creating an instance from
			//		something already in this.plugins. Ensures that the new
			//		instance is assigned to this.plugins at that index.
			var args=dojo.isString(plugin)?{name:plugin}:plugin;
			if(!args.setEditor){
				var o={"args":args,"plugin":null,"editor":this};
				dojo.publish("dijit.Editor.getPlugin",[o]);
				if(!o.plugin){
					var pc = dojo.getObject(args.name);
					if(pc){
						o.plugin=new pc(args);
					}
				}
				if(!o.plugin){
					console.debug('Cannot find plugin',plugin);
					return;
				}
				plugin=o.plugin;
			}
			if(arguments.length > 1){
				this._plugins[index] = plugin;
			}else{
				this._plugins.push(plugin);
			}
			plugin.setEditor(this);
			if(dojo.isFunction(plugin.setToolbar)){
				plugin.setToolbar(this.toolbar);
			}
		},
		/* beginning of custom undo/redo support */

		// customUndo: Boolean
		//		Whether we shall use custom undo/redo support instead of the native
		//		browser support. By default, we only enable customUndo for IE, as it
		//		has broken native undo/redo support. Note: the implementation does
		//		support other browsers which have W3C DOM2 Range API.
		customUndo: dojo.isIE,

		//	editActionInterval: Integer
		//		When using customUndo, not every keystroke will be saved as a step.
		//		Instead typing (including delete) will be grouped together: after
		//		a user stop typing for editActionInterval seconds, a step will be
		//		saved; if a user resume typing within editActionInterval seconds,
		//		the timeout will be restarted. By default, editActionInterval is 3
		//		seconds.
		editActionInterval: 3,
		beginEditing: function(cmd){
			if(!this._inEditing){
				this._inEditing=true;
				this._beginEditing(cmd);
			}
			if(this.editActionInterval>0){
				if(this._editTimer){
					clearTimeout(this._editTimer);
				}
				this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval);
			}
		},
		_steps:[],
		_undoedSteps:[],
		execCommand: function(cmd){
			if(this.customUndo && (cmd=='undo' || cmd=='redo')){
				return this[cmd]();
			}else{
				try{
					if(this.customUndo){
						this.endEditing();
						this._beginEditing();
					}
					var r = this.inherited('execCommand',arguments);
					if(this.customUndo){
						this._endEditing();
					}
					return r;
				}catch(e){
					if(dojo.isMoz && /copy|cut|paste/.test(cmd)){
						// Warn user of platform limitation.  Cannot programmatically access keyboard. See ticket #4136
						var sub = dojo.string.substitute,
							accel = {cut:'X', copy:'C', paste:'V'},
							isMac = navigator.userAgent.indexOf("Macintosh") != -1;
						alert(sub(this.commands.systemShortcutFF,
							[cmd, sub(this.commands[isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
					}
					return false;
				}
			}
		},
		queryCommandEnabled: function(cmd){
			if(this.customUndo && (cmd=='undo' || cmd=='redo')){
				return cmd=='undo'?(this._steps.length>1):(this._undoedSteps.length>0);
			}else{
				return this.inherited('queryCommandEnabled',arguments);
			}
		},
		_changeToStep: function(from,to){
			this.setValue(to.text);
			var b=to.bookmark;
			if(!b){ return; }
			if(dojo.isIE){
				if(dojo.isArray(b)){//IE CONTROL
					var tmp=[];
					dojo.forEach(b,function(n){
						tmp.push(dijit.range.getNode(n,this.editNode));
					},this);
					b=tmp;
				}
			}else{//w3c range
				var r=dijit.range.create();
				r.setStart(dijit.range.getNode(b.startContainer,this.editNode),b.startOffset);
				r.setEnd(dijit.range.getNode(b.endContainer,this.editNode),b.endOffset);
				b=r;
			}
			dojo.withGlobal(this.window,'moveToBookmark',dijit,[b]);
		},
		undo: function(){
//			console.log('undo');
			this.endEditing(true);
			var s=this._steps.pop();
			if(this._steps.length>0){
				this.focus();
				this._changeToStep(s,this._steps[this._steps.length-1]);
				this._undoedSteps.push(s);
				this.onDisplayChanged();
				return true;
			}
			return false;
		},
		redo: function(){
//			console.log('redo');
			this.endEditing(true);
			var s=this._undoedSteps.pop();
			if(s && this._steps.length>0){
				this.focus();
				this._changeToStep(this._steps[this._steps.length-1],s);
				this._steps.push(s);
				this.onDisplayChanged();
				return true;
			}
			return false;
		},
		endEditing: function(ignore_caret){
			if(this._editTimer){
				clearTimeout(this._editTimer);
			}
			if(this._inEditing){
				this._endEditing(ignore_caret);
				this._inEditing=false;
			}
		},
		_getBookmark: function(){
			var b=dojo.withGlobal(this.window,dijit.getBookmark);
			if(dojo.isIE){
				if(dojo.isArray(b)){//CONTROL
					var tmp=[];
					dojo.forEach(b,function(n){
						tmp.push(dijit.range.getIndex(n,this.editNode).o);
					},this);
					b=tmp;
				}
			}else{//w3c range
				var tmp=dijit.range.getIndex(b.startContainer,this.editNode).o
				b={startContainer:tmp,
					startOffset:b.startOffset,
					endContainer:b.endContainer===b.startContainer?tmp:dijit.range.getIndex(b.endContainer,this.editNode).o,
					endOffset:b.endOffset};
			}
			return b;
		},
		_beginEditing: function(cmd){
			if(this._steps.length===0){
				this._steps.push({'text':this.savedContent,'bookmark':this._getBookmark()});
			}
		},
		_endEditing: function(ignore_caret){
			var v=this.getValue(true);

			this._undoedSteps=[];//clear undoed steps
			this._steps.push({'text':v,'bookmark':this._getBookmark()});
		},
		onKeyDown: function(e){
			if(!this.customUndo){
				this.inherited('onKeyDown',arguments);
				return;
			}
			var k=e.keyCode,ks=dojo.keys;
			if(e.ctrlKey){
				if(k===90||k===122){ //z
					dojo.stopEvent(e);
					this.undo();
					return;
				}else if(k===89||k===121){ //y
					dojo.stopEvent(e);
					this.redo();
					return;
				}
			}
			this.inherited('onKeyDown',arguments);

			switch(k){
					case ks.ENTER:
					case ks.BACKSPACE:
					case ks.DELETE:
						this.beginEditing();
						break;
					case 88: //x
					case 86: //v
						if(e.ctrlKey && !e.altKey && !e.metaKey){
							this.endEditing();//end current typing step if any
							if(e.keyCode == 88){
								this.beginEditing('cut');
								//use timeout to trigger after the cut is complete
								setTimeout(dojo.hitch(this, this.endEditing), 1);
							}else{
								this.beginEditing('paste');
								//use timeout to trigger after the paste is complete
								setTimeout(dojo.hitch(this, this.endEditing), 1);
							}
							break;
						}
						//pass through
					default:
						if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){
							this.beginEditing();
							break;
						}
						//pass through
					case ks.ALT:
						this.endEditing();
						break;
					case ks.UP_ARROW:
					case ks.DOWN_ARROW:
					case ks.LEFT_ARROW:
					case ks.RIGHT_ARROW:
					case ks.HOME:
					case ks.END:
					case ks.PAGE_UP:
					case ks.PAGE_DOWN:
						this.endEditing(true);
						break;
					//maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
					case ks.CTRL:
					case ks.SHIFT:
					case ks.TAB:
						break;
				}
		},
		_onBlur: function(){
			this.inherited('_onBlur',arguments);
			this.endEditing(true);
		},
		onClick: function(){
			this.endEditing(true);
			this.inherited('onClick',arguments);
		}
		/* end of custom undo/redo support */
	}
);

/* the following code is to registered a handler to get default plugins */
dojo.subscribe("dijit.Editor.getPlugin",null,function(o){
	if(o.plugin){ return; }
	var args=o.args, p;
	var _p = dijit._editor._Plugin;
	var name=args.name;
	switch(name){
		case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList":
		case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter":
		case "justifyFull": case "justifyLeft": case "justifyRight": case "delete":
		case "selectAll": case "removeFormat":
			p = new _p({ command: name });
			break;

		case "bold": case "italic": case "underline": case "strikethrough":
		case "subscript": case "superscript":
			p = new _p({ buttonClass: dijit.form.ToggleButton, command: name });
			break;
		case "|":
			p = new _p({ button: new dijit.ToolbarSeparator() });
			break;
		case "createLink":
//					dojo['require']('dijit._editor.plugins.LinkDialog');
			p = new dijit._editor.plugins.LinkDialog({ command: name });
			break;
		case "foreColor": case "hiliteColor":
			p = new dijit._editor.plugins.TextColor({ command: name });
			break;
		case "fontName": case "fontSize": case "formatBlock":
			p = new dijit._editor.plugins.FontChoice({ command: name });
	}
//	console.log('name',name,p);
	o.plugin=p;
});
<commit_msg>Forgot to look up string in translation table for clipboard message.  Fixes #4575<commit_after>dojo.provide("dijit.Editor");
dojo.require("dijit._editor.RichText");
dojo.require("dijit.Toolbar");
dojo.require("dijit._editor._Plugin");
dojo.require("dijit._Container");
dojo.require("dojo.i18n");
dojo.requireLocalization("dijit._editor", "commands");

dojo.declare(
	"dijit.Editor",
	dijit._editor.RichText,
	{
	// summary: A rich-text Editing widget

		// plugins: Array
		//		a list of plugin names (as strings) or instances (as objects)
		//		for this widget.
		plugins: null,

		// extraPlugins: Array
		//		a list of extra plugin names which will be appended to plugins array
		extraPlugins: null,

		constructor: function(){
			if(!dojo.isArray(this.plugins)){
				this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
				"insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull"/*"createLink"*/];
			}

			this._plugins=[];
			this._editInterval = this.editActionInterval * 1000;
		},

		postCreate: function(){
			//for custom undo/redo
			if(this.customUndo){
				dojo['require']("dijit._editor.range");
				this._steps=this._steps.slice(0);
				this._undoedSteps=this._undoedSteps.slice(0);
//				this.addKeyHandler('z',this.KEY_CTRL,this.undo);
//				this.addKeyHandler('y',this.KEY_CTRL,this.redo);
			}
			if(dojo.isArray(this.extraPlugins)){
				this.plugins=this.plugins.concat(this.extraPlugins);
			}

//			try{
			dijit.Editor.superclass.postCreate.apply(this, arguments);

			this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang);

			if(!this.toolbar){
				// if we haven't been assigned a toolbar, create one
				var toolbarNode = dojo.doc.createElement("div");
				dojo.place(toolbarNode, this.editingArea, "before");
				this.toolbar = new dijit.Toolbar({}, toolbarNode);
			}

			dojo.forEach(this.plugins, this.addPlugin, this);
			this.onNormalizedDisplayChanged(); //update toolbar button status
//			}catch(e){ console.debug(e); }
		},
		destroy: function(){
			dojo.forEach(this._plugins, function(p){
				if(p.destroy){
					p.destroy();
				}
			});
			this._plugins=[];
			this.toolbar.destroy(); delete this.toolbar;
			this.inherited('destroy',arguments);
		},
		addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
			//	summary:
			//		takes a plugin name as a string or a plugin instance and
			//		adds it to the toolbar and associates it with this editor
			//		instance. The resulting plugin is added to the Editor's
			//		plugins array. If index is passed, it's placed in the plugins
			//		array at that index. No big magic, but a nice helper for
			//		passing in plugin names via markup.
			//	plugin: String, args object or plugin instance. Required.
			//	args: This object will be passed to the plugin constructor.
			//	index:
			//		Integer, optional. Used when creating an instance from
			//		something already in this.plugins. Ensures that the new
			//		instance is assigned to this.plugins at that index.
			var args=dojo.isString(plugin)?{name:plugin}:plugin;
			if(!args.setEditor){
				var o={"args":args,"plugin":null,"editor":this};
				dojo.publish("dijit.Editor.getPlugin",[o]);
				if(!o.plugin){
					var pc = dojo.getObject(args.name);
					if(pc){
						o.plugin=new pc(args);
					}
				}
				if(!o.plugin){
					console.debug('Cannot find plugin',plugin);
					return;
				}
				plugin=o.plugin;
			}
			if(arguments.length > 1){
				this._plugins[index] = plugin;
			}else{
				this._plugins.push(plugin);
			}
			plugin.setEditor(this);
			if(dojo.isFunction(plugin.setToolbar)){
				plugin.setToolbar(this.toolbar);
			}
		},
		/* beginning of custom undo/redo support */

		// customUndo: Boolean
		//		Whether we shall use custom undo/redo support instead of the native
		//		browser support. By default, we only enable customUndo for IE, as it
		//		has broken native undo/redo support. Note: the implementation does
		//		support other browsers which have W3C DOM2 Range API.
		customUndo: dojo.isIE,

		//	editActionInterval: Integer
		//		When using customUndo, not every keystroke will be saved as a step.
		//		Instead typing (including delete) will be grouped together: after
		//		a user stop typing for editActionInterval seconds, a step will be
		//		saved; if a user resume typing within editActionInterval seconds,
		//		the timeout will be restarted. By default, editActionInterval is 3
		//		seconds.
		editActionInterval: 3,
		beginEditing: function(cmd){
			if(!this._inEditing){
				this._inEditing=true;
				this._beginEditing(cmd);
			}
			if(this.editActionInterval>0){
				if(this._editTimer){
					clearTimeout(this._editTimer);
				}
				this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval);
			}
		},
		_steps:[],
		_undoedSteps:[],
		execCommand: function(cmd){
			if(this.customUndo && (cmd=='undo' || cmd=='redo')){
				return this[cmd]();
			}else{
				try{
					if(this.customUndo){
						this.endEditing();
						this._beginEditing();
					}
					var r = this.inherited('execCommand',arguments);
					if(this.customUndo){
						this._endEditing();
					}
					return r;
				}catch(e){
					if(dojo.isMoz && /copy|cut|paste/.test(cmd)){
						// Warn user of platform limitation.  Cannot programmatically access keyboard. See ticket #4136
						var sub = dojo.string.substitute,
							accel = {cut:'X', copy:'C', paste:'V'},
							isMac = navigator.userAgent.indexOf("Macintosh") != -1;
						alert(sub(this.commands.systemShortcutFF,
							[this.commands[cmd], sub(this.commands[isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
					}
					return false;
				}
			}
		},
		queryCommandEnabled: function(cmd){
			if(this.customUndo && (cmd=='undo' || cmd=='redo')){
				return cmd=='undo'?(this._steps.length>1):(this._undoedSteps.length>0);
			}else{
				return this.inherited('queryCommandEnabled',arguments);
			}
		},
		_changeToStep: function(from,to){
			this.setValue(to.text);
			var b=to.bookmark;
			if(!b){ return; }
			if(dojo.isIE){
				if(dojo.isArray(b)){//IE CONTROL
					var tmp=[];
					dojo.forEach(b,function(n){
						tmp.push(dijit.range.getNode(n,this.editNode));
					},this);
					b=tmp;
				}
			}else{//w3c range
				var r=dijit.range.create();
				r.setStart(dijit.range.getNode(b.startContainer,this.editNode),b.startOffset);
				r.setEnd(dijit.range.getNode(b.endContainer,this.editNode),b.endOffset);
				b=r;
			}
			dojo.withGlobal(this.window,'moveToBookmark',dijit,[b]);
		},
		undo: function(){
//			console.log('undo');
			this.endEditing(true);
			var s=this._steps.pop();
			if(this._steps.length>0){
				this.focus();
				this._changeToStep(s,this._steps[this._steps.length-1]);
				this._undoedSteps.push(s);
				this.onDisplayChanged();
				return true;
			}
			return false;
		},
		redo: function(){
//			console.log('redo');
			this.endEditing(true);
			var s=this._undoedSteps.pop();
			if(s && this._steps.length>0){
				this.focus();
				this._changeToStep(this._steps[this._steps.length-1],s);
				this._steps.push(s);
				this.onDisplayChanged();
				return true;
			}
			return false;
		},
		endEditing: function(ignore_caret){
			if(this._editTimer){
				clearTimeout(this._editTimer);
			}
			if(this._inEditing){
				this._endEditing(ignore_caret);
				this._inEditing=false;
			}
		},
		_getBookmark: function(){
			var b=dojo.withGlobal(this.window,dijit.getBookmark);
			if(dojo.isIE){
				if(dojo.isArray(b)){//CONTROL
					var tmp=[];
					dojo.forEach(b,function(n){
						tmp.push(dijit.range.getIndex(n,this.editNode).o);
					},this);
					b=tmp;
				}
			}else{//w3c range
				var tmp=dijit.range.getIndex(b.startContainer,this.editNode).o
				b={startContainer:tmp,
					startOffset:b.startOffset,
					endContainer:b.endContainer===b.startContainer?tmp:dijit.range.getIndex(b.endContainer,this.editNode).o,
					endOffset:b.endOffset};
			}
			return b;
		},
		_beginEditing: function(cmd){
			if(this._steps.length===0){
				this._steps.push({'text':this.savedContent,'bookmark':this._getBookmark()});
			}
		},
		_endEditing: function(ignore_caret){
			var v=this.getValue(true);

			this._undoedSteps=[];//clear undoed steps
			this._steps.push({'text':v,'bookmark':this._getBookmark()});
		},
		onKeyDown: function(e){
			if(!this.customUndo){
				this.inherited('onKeyDown',arguments);
				return;
			}
			var k=e.keyCode,ks=dojo.keys;
			if(e.ctrlKey){
				if(k===90||k===122){ //z
					dojo.stopEvent(e);
					this.undo();
					return;
				}else if(k===89||k===121){ //y
					dojo.stopEvent(e);
					this.redo();
					return;
				}
			}
			this.inherited('onKeyDown',arguments);

			switch(k){
					case ks.ENTER:
					case ks.BACKSPACE:
					case ks.DELETE:
						this.beginEditing();
						break;
					case 88: //x
					case 86: //v
						if(e.ctrlKey && !e.altKey && !e.metaKey){
							this.endEditing();//end current typing step if any
							if(e.keyCode == 88){
								this.beginEditing('cut');
								//use timeout to trigger after the cut is complete
								setTimeout(dojo.hitch(this, this.endEditing), 1);
							}else{
								this.beginEditing('paste');
								//use timeout to trigger after the paste is complete
								setTimeout(dojo.hitch(this, this.endEditing), 1);
							}
							break;
						}
						//pass through
					default:
						if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){
							this.beginEditing();
							break;
						}
						//pass through
					case ks.ALT:
						this.endEditing();
						break;
					case ks.UP_ARROW:
					case ks.DOWN_ARROW:
					case ks.LEFT_ARROW:
					case ks.RIGHT_ARROW:
					case ks.HOME:
					case ks.END:
					case ks.PAGE_UP:
					case ks.PAGE_DOWN:
						this.endEditing(true);
						break;
					//maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
					case ks.CTRL:
					case ks.SHIFT:
					case ks.TAB:
						break;
				}
		},
		_onBlur: function(){
			this.inherited('_onBlur',arguments);
			this.endEditing(true);
		},
		onClick: function(){
			this.endEditing(true);
			this.inherited('onClick',arguments);
		}
		/* end of custom undo/redo support */
	}
);

/* the following code is to registered a handler to get default plugins */
dojo.subscribe("dijit.Editor.getPlugin",null,function(o){
	if(o.plugin){ return; }
	var args=o.args, p;
	var _p = dijit._editor._Plugin;
	var name=args.name;
	switch(name){
		case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList":
		case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter":
		case "justifyFull": case "justifyLeft": case "justifyRight": case "delete":
		case "selectAll": case "removeFormat":
			p = new _p({ command: name });
			break;

		case "bold": case "italic": case "underline": case "strikethrough":
		case "subscript": case "superscript":
			p = new _p({ buttonClass: dijit.form.ToggleButton, command: name });
			break;
		case "|":
			p = new _p({ button: new dijit.ToolbarSeparator() });
			break;
		case "createLink":
//					dojo['require']('dijit._editor.plugins.LinkDialog');
			p = new dijit._editor.plugins.LinkDialog({ command: name });
			break;
		case "foreColor": case "hiliteColor":
			p = new dijit._editor.plugins.TextColor({ command: name });
			break;
		case "fontName": case "fontSize": case "formatBlock":
			p = new dijit._editor.plugins.FontChoice({ command: name });
	}
//	console.log('name',name,p);
	o.plugin=p;
});
<|endoftext|>