<commit_before>import assert from 'power-assert';
import Converter from '../../src/lib/converter.js';

/** @test {Converter} */
describe( 'Converter', () => {
  /** @test {Converter#convert} */
  describe( 'convert: Markdown', () => {
    it( 'Plain text ( TEXT_NODE ), Keep a whitespace and line break', () => {
      const post     = 'Line 1\n\nLine 2  Word\nLine3';
      const expected = 'Line 1\n\nLine 2  Word\nLine3';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = '
\nLine 1\n\nLine 2\n
';
      const expected = 'Line 1 Line 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = 'Line 1
Line 2
Line 3';
      const expected = 'Line 1  \nLine 2  \nLine 3';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
 -
, and Markdown Extra Link', () => {
      const post =
`
Title

Header 2

Header 3

Header 4

Header 5

Header 6
`;

      const expected =
`# Title

## Header 2 {#header-2}

### Header 3

#### Header 4

##### Header 5

###### Header 6`;

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = '
';
      const expected = '* * *';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', ', () => {
      const post     = 'Word 1 Word 2';
      const expected = '_Word 1_ _Word 2_';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = 'Text Code Text';
      const expected = 'Text `Code` Text';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = 'Text Link Text Inter Link';
      const expected = 'Text [Link](://example.com/ "title") Text [Inter Link](#id)';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = ' ';
      const expected = '![Title](example.png) ![](example.png "Example")';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
...
', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n    const test = \'test\';\n\nText';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post = '
\nLine 1\nLine 2\n
';
      const expected = '> Line 1 Line 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

', () => {
      const post =
`

Item 1

Item 2

`;
      const expected = '*   Item 1\n*   Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

, nested', () => {
      const post =
`

Item 1

Item 1-1

Item 1-2

Item 2

`;
      const expected = '*   Item 1\n    *   Item 1-1\n    *   Item 1-2\n*   Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

', () => {
      const post =
`

Item 1

Item 2

`;
      const expected = '1.  Item 1\n2.  Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

, nested', () => {
      const post =
`

Item 1

Item 1-1

Item 1-2

Item 2

`;
      const expected = '1.  Item 1\n    1.  Item 1-1\n    2.  Item 1-2\n2.  Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( 'Block Element', () => {
      const post = '<address>Copyright 2009 - 2016 akabeko All Rights Reserved</address>';
      const expected = '<address>Copyright 2009 - 2016 akabeko All Rights Reserved</address>';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( 'Otherwise', () => {
      const post = '<example>Test</example>';
      const expected = '<example>Test</example>';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );
  } );

  /** @test {Converter#convert} */
  describe( 'convert: GitHub Flavored Markdown', () => {
    it( '
', () => {
      const post     = 'Line 1
Line 2
Line 3';
      const expected = 'Line 1\nLine 2\nLine 3';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', <s>, ', () => {
      const post     = 'Text 1 <s>Text 2</s> Text 3';
      const expected = '~~Text 1~~ ~~Text 2~~ ~~Text 3~~';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
...
', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n```\nconst test = \'test\';\n```\n\nText';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

...

', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n```js\nconst test = \'test\';\n\n```\n\nText';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = '
Header 1	Header 2
';
      const expected = '| Header 1 | Header 2 |';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = '
Value 1	Value 2
';
      const expected = '| Value 1 | Value 2 |';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', ', () => {
      const post     = '

Header 1	Header 2

Value 1	Value 2

';
      const expected = '| Header 1 | Header 2 |\n| Value 1 | Value 2 |';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
,
', () => {
      const post =
`

Header 1	Header 2

Value 1	Value 2

`;
      const expected = '| Header 1 | Header 2 |\n| --- | --- |\n| Value 1 | Value 2 |';

      return Converter.convert( post, { gmf: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );
  } );
} );
<commit_msg>#20 #21 Add an unit test of the Checklist<commit_after>import assert from 'power-assert';
import Converter from '../../src/lib/converter.js';

/** @test {Converter} */
describe( 'Converter', () => {
  /** @test {Converter#convert} */
  describe( 'convert: Markdown', () => {
    it( 'Plain text ( TEXT_NODE ), Keep a whitespace and line break', () => {
      const post     = 'Line 1\n\nLine 2  Word\nLine3';
      const expected = 'Line 1\n\nLine 2  Word\nLine3';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = '
\nLine 1\n\nLine 2\n
';
      const expected = 'Line 1 Line 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = 'Line 1
Line 2
Line 3';
      const expected = 'Line 1  \nLine 2  \nLine 3';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
 -
, and Markdown Extra Link', () => {
      const post =
`
Title

Header 2

Header 3

Header 4

Header 5

Header 6
`;

      const expected =
`# Title

## Header 2 {#header-2}

### Header 3

#### Header 4

##### Header 5

###### Header 6`;

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post     = '
';
      const expected = '* * *';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', ', () => {
      const post     = 'Word 1 Word 2';
      const expected = '_Word 1_ _Word 2_';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = 'Text Code Text';
      const expected = 'Text `Code` Text';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = 'Text Link Text Inter Link';
      const expected = 'Text [Link](://example.com/ "title") Text [Inter Link](#id)';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = ' ';
      const expected = '![Title](example.png) ![](example.png "Example")';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
...
', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n    const test = \'test\';\n\nText';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
', () => {
      const post = '
\nLine 1\nLine 2\n
';
      const expected = '> Line 1 Line 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

', () => {
      const post =
`

Item 1

Item 2

`;
      const expected = '*   Item 1\n*   Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

, nested', () => {
      const post =
`

Item 1

Item 1-1

Item 1-2

Item 2

`;
      const expected = '*   Item 1\n    *   Item 1-1\n    *   Item 1-2\n*   Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

', () => {
      const post =
`

Item 1

Item 2

`;
      const expected = '1.  Item 1\n2.  Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

, nested', () => {
      const post =
`

Item 1

Item 1-1

Item 1-2

Item 2

`;
      const expected = '1.  Item 1\n    1.  Item 1-1\n    2.  Item 1-2\n2.  Item 2';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( 'Block Element', () => {
      const post = '<address>Copyright 2009 - 2016 akabeko All Rights Reserved</address>';
      const expected = '<address>Copyright 2009 - 2016 akabeko All Rights Reserved</address>';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( 'Otherwise', () => {
      const post = '<example>Test</example>';
      const expected = '<example>Test</example>';

      return Converter.convert( post )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );
  } );

  /** @test {Converter#convert} */
  describe( 'convert: GitHub Flavored Markdown', () => {
    it( '
', () => {
      const post     = 'Line 1
Line 2
Line 3';
      const expected = 'Line 1\nLine 2\nLine 3';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', <s>, ', () => {
      const post     = 'Text 1 <s>Text 2</s> Text 3';
      const expected = '~~Text 1~~ ~~Text 2~~ ~~Text 3~~';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

<input type="checkbox">', () => {
      const post     = '

<input type="checkbox">Item 1

<input type="checkbox" checked="true">Item 2

';
      const expected = '*   [ ] Item 1\n*   [x] Item 2';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
...
', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n```\nconst test = \'test\';\n```\n\nText';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '

...

', () => {
      const post =
`
Text

const test = 'test';

Text
`;

      const expected = 'Text\n\n```js\nconst test = \'test\';\n\n```\n\nText';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = '
Header 1	Header 2
';
      const expected = '| Header 1 | Header 2 |';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '', () => {
      const post     = '
Value 1	Value 2
';
      const expected = '| Value 1 | Value 2 |';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( ', ', () => {
      const post     = '

Header 1	Header 2

Value 1	Value 2

';
      const expected = '| Header 1 | Header 2 |\n| Value 1 | Value 2 |';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );

    it( '
,
', () => {
      const post =
`

Header 1	Header 2

Value 1	Value 2

`;
      const expected = '| Header 1 | Header 2 |\n| --- | --- |\n| Value 1 | Value 2 |';

      return Converter.convert( post, { gfm: true } )
      .then( ( actual ) => {
        assert( actual === expected );
      } );
    } );
  } );
} );
<|endoftext|>


<commit_before>import Mesh from 'Engine/Mesh';
import {Ground as Model, Deer as DeerModel, Tree as TreeModel} from 'Models';
import {Ground as Shader} from 'Shaders';
import {Ground as Texture} from 'Textures';
import {Deer, Tree} from 'Meshes';
import {vec3} from 'gl-matrix';

class Ground extends Mesh {
	static Trees = [
		new TreeModel(2),
		new TreeModel(3),
		new TreeModel(4)
	];
	static Deers = [
		new DeerModel(0.01)
	];
	constructor(world, noise, chunk) {
		const origin = vec3.fromValues(chunk[0] * Model.size * Model.scale, 0, chunk[1] * Model.size * Model.scale);
		super(new Model(noise, chunk), Shader, origin, null, {mass: 0, friction: 1, group: Mesh.collisionFloor}, Texture);
		this.chunk = chunk;

		const getSpawnPoint = (minY, maxY) => {
			const x = Math.floor(Math.random() * (Model.size + 1));
			const z = Math.floor(Math.random() * (Model.size + 1));
			const y = this.model.heightMap[x + ':' + z];
			if(y < minY || y > maxY) return false;
			const floor = vec3.fromValues((x - Model.size * 0.5) * Model.scale, y, (z - Model.size * 0.5) * Model.scale);
			vec3.add(floor, floor, origin);
			return floor;
		};

		this.trees = [];
		for(let i=0; i<2; i++) {
			const spawn = getSpawnPoint(16, 48);
			if(!spawn) continue;
			this.trees.push(new Tree(Ground.Trees[Math.floor(Math.random() * 3)], spawn));
		}
		this.deers = [];
		const bounds = {
			x: origin[0] - Model.size * 0.5 * Model.scale,
			z: origin[2] - Model.size * 0.5 * Model.scale,
			width: Model.size * Model.scale,
			length: Model.size * Model.scale
		};
		if(!this.trees.length) {
			const spawn = getSpawnPoint(0, 16);
			spawn && this.deers.push(new Deer(Ground.Deers[0], spawn, world, bounds));
		}
	}
	render(camera) {
		super.render(camera);
		return this.trees.concat(this.deers);
	}
	animate(delta) {
		this.trees.concat(this.deers).forEach((mesh) => mesh.animate && mesh.animate(delta));
	}
	destroy() {
		super.destroy();
		this.model.destroy();
		this.trees.concat(this.deers).forEach((mesh) => mesh.destroy());
	}
};

export default Ground;
<commit_msg>too much deers<commit_after>import Mesh from 'Engine/Mesh';
import {Ground as Model, Deer as DeerModel, Tree as TreeModel} from 'Models';
import {Ground as Shader} from 'Shaders';
import {Ground as Texture} from 'Textures';
import {Deer, Tree} from 'Meshes';
import {vec3} from 'gl-matrix';

class Ground extends Mesh {
	static Trees = [
		new TreeModel(2),
		new TreeModel(3),
		new TreeModel(4)
	];
	static Deers = [
		new DeerModel(0.01)
	];
	constructor(world, noise, chunk) {
		const origin = vec3.fromValues(chunk[0] * Model.size * Model.scale, 0, chunk[1] * Model.size * Model.scale);
		super(new Model(noise, chunk), Shader, origin, null, {mass: 0, friction: 1, group: Mesh.collisionFloor}, Texture);
		this.chunk = chunk;

		const getSpawnPoint = (minY, maxY) => {
			const x = Math.floor(Math.random() * (Model.size + 1));
			const z = Math.floor(Math.random() * (Model.size + 1));
			const y = this.model.heightMap[x + ':' + z];
			if(y < minY || y > maxY) return false;
			const floor = vec3.fromValues((x - Model.size * 0.5) * Model.scale, y, (z - Model.size * 0.5) * Model.scale);
			vec3.add(floor, floor, origin);
			return floor;
		};

		this.trees = [];
		for(let i=0; i<2; i++) {
			const spawn = getSpawnPoint(16, 48);
			if(!spawn) continue;
			this.trees.push(new Tree(Ground.Trees[Math.floor(Math.random() * 3)], spawn));
		}
		this.deers = [];
		const bounds = {
			x: origin[0] - Model.size * 0.5 * Model.scale,
			z: origin[2] - Model.size * 0.5 * Model.scale,
			width: Model.size * Model.scale,
			length: Model.size * Model.scale
		};
		if(!this.trees.length) {
			const spawn = getSpawnPoint(1, 16);
			spawn && this.deers.push(new Deer(Ground.Deers[0], spawn, world, bounds));
		}
	}
	render(camera) {
		super.render(camera);
		return this.trees.concat(this.deers);
	}
	animate(delta) {
		this.trees.concat(this.deers).forEach((mesh) => mesh.animate && mesh.animate(delta));
	}
	destroy() {
		super.destroy();
		this.model.destroy();
		this.trees.concat(this.deers).forEach((mesh) => mesh.destroy());
	}
};

export default Ground;
<|endoftext|>


<commit_before>var scripts = document.getElementsByTagName('script'),
    script = scripts[scripts.length - 1];

var get_time_display = function(stamp) {
    var dt = new Date(stamp);
    var period = dt.getHours() < 12 ? 'AM' : 'PM';
    var hours = dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours();
    if (hours === 0) {
        hours = 12;
    }
    var minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes();
    return hours + ':' + minutes + ' ' + period;
};

function Timeline(loc, start_time, end_time, width, height) {
    this.loc = loc;
    this.start_time = start_time;
    this.end_time = end_time;
    this.width = width;
    this.height = height;

    this.container = d3.select(this.loc).append('svg');

    // Settings
    this.draw_width = this.width - 40; // Draw inside the svg without clipping
    this.y_offset = 20; // Height offset from time marker
    var one_hour = 60 * 60 * 1000; // One hour in milliseconds
    this.interval = one_hour;
    var span_in_hours = (this.end_time - this.start_time) / one_hour,
        width_span_ratio = Math.ceil(this.draw_width / span_in_hours);
    /*
    If the browser is small enough or more than a day has elapsed, show six
    time interval markers
    */
    if (width_span_ratio < 60 || span_in_hours > 24) {
        this.interval = Math.floor((span_in_hours * one_hour) / 6);
    }
    this.edge_offset = 20; // Come off the edge
}

Timeline.prototype.draw = function() {
    var dt = this.end_time - this.start_time;

    this.line_offset = this.draw_width / (dt / this.interval);

    for(var i = 0; i <= dt / this.interval; i++) {
        var x_offset = i * this.line_offset + this.edge_offset;

        this.container.append('line')
            .attr('x1', x_offset).attr('x2', x_offset)
            .attr('y1', this.y_offset).attr('y2', this.height - this.y_offset)
            .style('stroke', '#000000');

        var time = get_time_display(new Date(this.start_time + this.interval * i));

        this.container.append('text')
            .attr('x', x_offset - 15).attr('y', 10)
            .style('font-size', '10px')
            .style('fill', '#000000')
            .style('opacity', 1)
            .text(time);
    }
};

function Entry(timeline, entry_data) {
    this.project_name = entry_data['project_name'];
    this.start_time = entry_data['start_time'];
    this.end_time = entry_data['end_time'];
    this.hours = entry_data['hours'];
    this.pk = entry_data['pk'];
    this.update_url = entry_data['update_url'];
    this.active = entry_data['active'];
    this.seconds_paused = entry_data['seconds_paused'];

    this.timeline = timeline;

    this.height = timeline.height - 2 * timeline.y_offset;
}

Entry.prototype.draw = function() {
    var timeline = this.timeline;

    var dt = this.end_time - this.start_time,
        width = timeline.line_offset * (dt / timeline.interval),
        x_offset = timeline.line_offset *
            (this.start_time - timeline.start_time) / timeline.interval  + timeline.edge_offset;

    var entry = timeline.container.append('rect')
        .attr('x', x_offset).attr('y', timeline.y_offset + 5)
        .attr('rx', 10).attr('ry', 10)
        .attr('height', this.height - 10)
        .attr('width', width);

    // Add the attributes for popovers
    var popover_msg = 'You have worked ' + this.hours + ' hours.';
    var start_str = get_time_display(new Date(this.start_time));
    var end_str = get_time_display(new Date(this.end_time));
    popover_msg += '
' + start_str + ' - ' + end_str;
    entry.attr('data-title', this.project_name)
        .attr('data-content', popover_msg);

    entry.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#0061AA')
        .attr('stroke', '#333333')
        .attr('stroke-width', '2px')
        .style('opacity', '0.95');

    var text = timeline.container.append('text')
        .attr('font-weight', 'bolder')
        .style('font-size', '14px')
        .text(this.project_name);

    // Get bounding boxes for centering text
    var text_box = text.node().getBBox(),
        entry_box = entry.node().getBBox();

    // Strip project name down if its too large
    if(text_box.width > entry_box.width) {
        var sub = Math.ceil(text_box.width / (text_box.width - entry_box.width)), // Ratio using widths
            name = this.project_name.substring(0, sub) + '...';

        name = sub === 0 || entry_box.width < 30 ? '' : name;

        text.text(name);

        // New bounding box
        text_box = text.node().getBBox();
    }

    // Do some positioning to get the text centered correctly
    var text_xpos = x_offset + width / 2 - text_box.width / 2,
        text_ypos = timeline.y_offset + this.height / 2 + text_box.height / 4;

    text.attr('x', text_xpos)
        .attr('y', text_ypos);

    // Display!
    text.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#FFFFFF')
        .style('opacity', '1.0');

    // Handle click
    var that = this;
    $(entry.node()).click(function() {
        location.href = that.update_url;
    });
};

var draw_timeline = function(data) {
    var $timeline = $('#timeline'),
        height = $timeline.height(),
        width = $(window).width();

    var start_time = new Date(data.start_time),
        end_time = new Date(data.end_time);

    // Convert the minutes to milliseconds
    var timezone_offset = start_time.getTimezoneOffset() * 60 * 1000;

    var t = new Timeline($timeline.selector, start_time.getTime() + timezone_offset,
        end_time.getTime() + timezone_offset, width, height);
    t.draw();

    for(var i = 0; i < data.entries.length; i++) {
        var entry = data.entries[i],
            start = new Date(entry.start_time),
            end = new Date(entry.end_time);

        var dt = end - start,
            hours = Math.ceil((dt / (3600 * 1000)) * 100) / 100;

        var e = new Entry(t, {
            'start_time': start.getTime() + timezone_offset,
            'end_time': end.getTime() + timezone_offset,
            'project_name': entry.project,
            'pk': entry.pk,
            'active': entry.active,
            'seconds_paused': entry.seconds_paused,
            'hours': hours,
            'update_url': entry.update_url
        });
        e.draw();
    }

    // Enable popovers
    $('.timeline rect').popover({
        'placement': 'top'
    });
};

(function() {
    var data = JSON.parse(script.getAttribute('data-entries'));
    draw_timeline(data);

    $(window).resize(function() {
        $('#timeline svg').remove();
        draw_timeline(data);
    });
})();
<commit_msg>refs #492 - Made todays work span from 8-6PM unless the person has worked before/after then it rounds to the nearest complete hour<commit_after>var scripts = document.getElementsByTagName('script'),
    script = scripts[scripts.length - 1];

var get_time_display = function(stamp) {
    var dt = new Date(stamp);
    var period = dt.getHours() < 12 ? 'AM' : 'PM';
    var hours = dt.getHours() > 12 ? dt.getHours() - 12 : dt.getHours();
    if (hours === 0) {
        hours = 12;
    }
    var minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes();
    return hours + ':' + minutes + ' ' + period;
};

function Timeline(loc, start_time, end_time, width, height) {
    this.loc = loc;
    this.width = width;
    this.height = height;

    this.container = d3.select(this.loc).append('svg');

    // Settings
    var today = new Date();
    today.setHours(0),
    today.setMinutes(0),
    today.setSeconds(0),
    today.setMilliseconds(0);
    today = today.getTime();
    this.draw_width = this.width - 40; // Draw inside the svg without clipping
    this.y_offset = 20; // Height offset from time marker
    var one_hour = 60 * 60 * 1000; // One hour in milliseconds
    var default_start = (one_hour * 8) + today;
        default_end = (one_hour * 18) + today;
    this.interval = one_hour;

    /*
    Set the start and end times to defaults (8AM - 6PM) unless entries fall
    outside of this, in which case round to the nearest hour on the outside.
    */
    if (start_time < default_start) {
        this.start_time = Math.floor(start_time / one_hour) * one_hour;
    } else {
        this.start_time = default_start;
    }

    if (end_time > default_end) {
        this.end_time = Math.ceil(end_time / one_hour) * one_hour;
    } else {
        this.end_time = default_end;
    }

    var span_in_hours = (this.end_time - this.start_time) / one_hour,
        width_span_ratio = Math.ceil(this.draw_width / span_in_hours);
    /*
    If the browser is small enough or more than a day has elapsed, show six
    time interval markers
    */
    if (width_span_ratio < 60 || span_in_hours > 24) {
        this.interval = Math.floor((span_in_hours * one_hour) / 5);
    }
    this.edge_offset = 20; // Come off the edge
}

Timeline.prototype.draw = function() {
    var dt = this.end_time - this.start_time;

    this.line_offset = this.draw_width / (dt / this.interval);

    for(var i = 0; i <= dt / this.interval; i++) {
        var x_offset = i * this.line_offset + this.edge_offset;

        this.container.append('line')
            .attr('x1', x_offset).attr('x2', x_offset)
            .attr('y1', this.y_offset).attr('y2', this.height - this.y_offset)
            .style('stroke', '#000000');

        var time = get_time_display(new Date(this.start_time + this.interval * i));

        this.container.append('text')
            .attr('x', x_offset - 15).attr('y', 10)
            .style('font-size', '10px')
            .style('fill', '#000000')
            .style('opacity', 1)
            .text(time);
    }
};

function Entry(timeline, entry_data) {
    this.project_name = entry_data['project_name'];
    this.start_time = entry_data['start_time'];
    this.end_time = entry_data['end_time'];
    this.hours = entry_data['hours'];
    this.pk = entry_data['pk'];
    this.update_url = entry_data['update_url'];
    this.active = entry_data['active'];
    this.seconds_paused = entry_data['seconds_paused'];

    this.timeline = timeline;

    this.height = timeline.height - 2 * timeline.y_offset;
}

Entry.prototype.draw = function() {
    var timeline = this.timeline;

    var dt = this.end_time - this.start_time,
        width = timeline.line_offset * (dt / timeline.interval),
        x_offset = timeline.line_offset *
            (this.start_time - timeline.start_time) / timeline.interval  + timeline.edge_offset;

    var entry = timeline.container.append('rect')
        .attr('x', x_offset).attr('y', timeline.y_offset + 5)
        .attr('rx', 10).attr('ry', 10)
        .attr('height', this.height - 10)
        .attr('width', width);

    // Add the attributes for popovers
    var popover_msg = 'You have worked ' + this.hours + ' hours.';
    var start_str = get_time_display(new Date(this.start_time));
    var end_str = get_time_display(new Date(this.end_time));
    popover_msg += '
' + start_str + ' - ' + end_str;
    entry.attr('data-title', this.project_name)
        .attr('data-content', popover_msg);

    entry.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#0061AA')
        .attr('stroke', '#333333')
        .attr('stroke-width', '2px')
        .style('opacity', '0.95');

    var text = timeline.container.append('text')
        .attr('font-weight', 'bolder')
        .style('font-size', '14px')
        .text(this.project_name);

    // Get bounding boxes for centering text
    var text_box = text.node().getBBox(),
        entry_box = entry.node().getBBox();

    // Strip project name down if its too large
    if(text_box.width > entry_box.width) {
        var sub = Math.ceil(text_box.width / (text_box.width - entry_box.width)), // Ratio using widths
            name = this.project_name.substring(0, sub) + '...';

        name = sub === 0 || entry_box.width < 30 ? '' : name;

        text.text(name);

        // New bounding box
        text_box = text.node().getBBox();
    }

    // Do some positioning to get the text centered correctly
    var text_xpos = x_offset + width / 2 - text_box.width / 2,
        text_ypos = timeline.y_offset + this.height / 2 + text_box.height / 4;

    text.attr('x', text_xpos)
        .attr('y', text_ypos);

    // Display!
    text.transition()
        .delay(100)
        .duration(1500)
        .attr('fill', '#FFFFFF')
        .style('opacity', '1.0');

    // Handle click
    var that = this;
    $(entry.node()).click(function() {
        location.href = that.update_url;
    });
};

var draw_timeline = function(data) {
    var $timeline = $('#timeline'),
        height = $timeline.height(),
        width = $(window).width();

    var start_time = new Date(data.start_time),
        end_time = new Date(data.end_time);

    // Convert the minutes to milliseconds
    var timezone_offset = start_time.getTimezoneOffset() * 60 * 1000;

    var t = new Timeline($timeline.selector, start_time.getTime() + timezone_offset,
        end_time.getTime() + timezone_offset, width, height);
    t.draw();

    for(var i = 0; i < data.entries.length; i++) {
        var entry = data.entries[i],
            start = new Date(entry.start_time),
            end = new Date(entry.end_time);

        var dt = end - start,
            hours = Math.ceil((dt / (3600 * 1000)) * 100) / 100;

        var e = new Entry(t, {
            'start_time': start.getTime() + timezone_offset,
            'end_time': end.getTime() + timezone_offset,
            'project_name': entry.project,
            'pk': entry.pk,
            'active': entry.active,
            'seconds_paused': entry.seconds_paused,
            'hours': hours,
            'update_url': entry.update_url
        });
        e.draw();
    }

    // Enable popovers
    $('.timeline rect').popover({
        'placement': 'top'
    });
};

(function() {
    var data = JSON.parse(script.getAttribute('data-entries'));
    draw_timeline(data);

    $(window).resize(function() {
        $('#timeline svg').remove();
        draw_timeline(data);
    });
})();
<|endoftext|>


<commit_before>odoo.define('website_forum.website_forum', function (require) {
'use strict';

var ajax = require('web.ajax');
var core = require('web.core');
var website = require('website.website');

var _t = core._t;

var lastsearch;

if(!$('.website_forum').length) {
    return $.Deferred().reject("DOM doesn't contain '.website_forum'");
}

    $("[data-toggle='popover']").popover();
    $('.karma_required').on('click', function (ev) {
        var karma = $(ev.currentTarget).data('karma');
        if (karma) {
            ev.preventDefault();
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                karma + ' ' + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.') + '
');
            var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
            if (vote_alert.length == 0) {
                $(ev.currentTarget).parent().append($warning);
            }
        }
    });

    $('.o_js_forum_tag_follow').hover(
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeIn().css('display','block');
        },
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeOut().css('display','none');
    });

    // Extended user biography toogle
    $('.o_forum_user_info').hover(
        function () {
           $(this).parent().find('.o_forum_user_bio_expand').delay(500).toggle('fast');
        },
        function () {
            $(this).parent().find('.o_forum_user_bio_expand').clearQueue();
        }
    );

    $('.o_forum_user_bio_expand').hover(
        function () {},
        function () {
            $(this).fadeOut('fast');
        }
    );

    $('.flag').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if(data.error) {
                    var $warning;
                    if(data.error == 'anonymous_user') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to flag a post') +
                            '
');
                    } else if(data.error == 'post_already_flagged') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post is already flagged') +
                            '
');
                    } else if(data.error == 'post_non_flaggable') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post can not be flagged') +
                            '
');
                    }
                    var flag_alert = $link.parent().find("#flag_alert");
                    if (flag_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else if(data.success) {
                    var elem = $link;
                    if(data.success == 'post_flagged_moderator') {
                        elem.html(' Flagged');
                        var c = parseInt($('#count_flagged_posts').html(), 10);
                        c++;
                        $('#count_flagged_posts').html(c);
                    } else if(data.success == 'post_flagged_non_moderator') {
                        elem.html(' Flagged');
                        var forum_answer = elem.closest('.forum_answer');
                        forum_answer.fadeIn(1000);
                        forum_answer.slideUp(1000);
                    }
                }
            });
    });

    $('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if (data.error){
                    if (data.error == 'own_post'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry, you cannot vote for your own posts') +
                            '
');
                    } else if (data.error == 'anonymous_user'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to vote') +
                            '
');
                    }
                    var vote_alert = $link.parent().find("#vote_alert");
                    if (vote_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else {
                    $link.parent().find(".vote_count").html(data['vote_count']);
                    if (data.user_vote === 0) {
                        $link.parent().find(".text-success").removeClass("text-success");
                        $link.parent().find(".text-warning").removeClass("text-warning");
                    } else {
                        if (data.user_vote === 1) {
                            $link.addClass("text-success");
                        } else {
                            $link.addClass("text-warning");
                        }
                    }
                }
            });
    });

    $('.accept_answer').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data.error) {
                if (data.error == 'anonymous_user') {
                    var $warning = $('
'+
                        '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                        _t('Sorry, anonymous users cannot choose correct answer.') +
                        '
');
                }
                var correct_answer_alert = $link.parent().find("#correct_answer_alert");
                if (correct_answer_alert.length == 0) {
                    $link.parent().append($warning);
                }
            } else {
                if (data) {
                    $(".oe_answer_true").addClass('oe_answer_false').removeClass("oe_answer_true");
                    $link.addClass("oe_answer_true").removeClass('oe_answer_false');
                } else {
                    $link.removeClass("oe_answer_true").addClass('oe_answer_false');
                }
            }
        });
    });

    $('.favourite_question').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data) {
                $link.addClass("forum_favourite_question")
            } else {
                $link.removeClass("forum_favourite_question")
            }
        });
    });

    $('.comment_delete').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.closest('form').attr('action'), 'call', {}).then(function () {
            $link.parents('.comment').first().remove();
        });
    });

    $('.notification_close').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/notification_read", 'call', {
            'notification_id': $link.attr("id")});
    });

    $('.send_validation_email').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/send_validation_email", 'call', {
            'forum_id': $link.attr('forum-id'),
        }).then(function (data) {
            if (data) {
                $('button.validation_email_close').click();
            }
        });
    });

    $('.validated_email_close').on('click', function (ev) {
        ajax.jsonRpc("/forum/validate_email/close", 'call', {});
    });

    // welcome message action button
    var forum_login = _.string.sprintf('%s/web?redirect=%s',
        window.location.origin, escape(window.location.href));
    $('.forum_register_url').attr('href',forum_login);

    $('.js_close_intro').on('click', function (ev) {
        ev.preventDefault();
        document.cookie = "forum_welcome_message = false";
        $('.forum_intro').slideUp();
        return true;
    });

    $('.link_url, .o_forum_post_link').on('change', function (ev) {  // keep .link_url for compat
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var display_error = function(){
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                'Please enter valid URL. Example: http://www.odoo.com'+
                '
');
            $link.parent().append($warning);
            $link.parents('form').find('button')[0].disabled = true;

        };
        var url = $link.val();
        if (url.search("^http(s?)://.*")) {
            url = 'http://'+url;
        }
        var regex = new RegExp("(http(s)?://.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)");
        if(regex.test(url)){
            ajax.jsonRpc("/forum/get_url_title", 'call', {'url': url}).then(function (data) {
                if (data) {
                    $("input[name='post_name']")[0].value = data;
                    $link.parents('form').find('button')[0].disabled = false;
                } else {
                    display_error();
                }

            });
        } else {
            display_error();
        }
    });

    $('input.js_select2').select2({
        tags: true,
        tokenSeparators: [",", " ", "_"],
        maximumInputLength: 35,
        minimumInputLength: 2,
        maximumSelectionSize: 5,
        lastsearch: [],
        createSearchChoice: function (term) {
            if ($(lastsearch).filter(function () { return this.text.localeCompare(term) === 0;}).length === 0) {
                //check Karma
                if (parseInt($("#karma").val()) >= parseInt($("#karma_edit_retag").val())) {
                    return {
                        id: "_" + $.trim(term),
                        text: $.trim(term) + ' *',
                        isNew: true,
                    };
                }
            }
        },
        formatResult: function(term) {
            if (term.isNew) {
                return 'New ' + _.escape(term.text);
            }
            else {
                return _.escape(term.text);
            }
        },
        ajax: {
            url: '/forum/get_tags',
            dataType: 'json',
            data: function(term) {
                return {
                    q: term,
                    l: 50
                };
            },
            results: function(data) {
                var ret = [];
                _.each(data, function(x) {
                    ret.push({ id: x.id, text: x.name, isNew: false });
                });
                lastsearch = ret;
                return { results: ret };
            }
        },
        // Take default tags from the input value
        initSelection: function (element, callback) {
            var data = [];
            _.each(element.data('init-value'), function(x) {
                data.push({ id: x.id, text: x.name, isNew: false });
            });
            element.val('');
            callback(data);
        },
    });

    $('textarea.load_editor').each(function () {
        var $textarea = $(this);
        var editor_karma = $textarea.data('karma') || 30;  // default value for backward compatibility
        if (!$textarea.val().match(/\S/)) {
            $textarea.val("

");
        }
        var $form = $textarea.closest('form');
        var toolbar = [
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'clear']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['history', ['undo', 'redo']],
            ];
        if (parseInt($("#karma").val()) >= editor_karma) {
            toolbar.push(['insert', ['link', 'picture']]);
        }
        $textarea.summernote({
                height: 150,
                toolbar: toolbar,
                styleWithSpan: false
            });
        $form.on('click', 'button, .a-submit', function () {
            $textarea.html($form.find('.note-editable').code());
        });
    });

});<commit_msg>[FIX] website_forum: show to the end user that he should be logged in<commit_after>odoo.define('website_forum.website_forum', function (require) {
'use strict';

var ajax = require('web.ajax');
var core = require('web.core');
var website = require('website.website');

var _t = core._t;

var lastsearch;

if(!$('.website_forum').length) {
    return $.Deferred().reject("DOM doesn't contain '.website_forum'");
}

    $("[data-toggle='popover']").popover();
    $('.karma_required').on('click', function (ev) {
        var karma = $(ev.currentTarget).data('karma');
        if (karma) {
            ev.preventDefault();
            var msg = karma + ' ' + _t(' karma is required to perform this action. You can earn karma by having your answers upvoted by the community.')
            if ($('a[href*="/login"]').length) {
                msg = _t('Sorry you must be logged in to perform this action');
            };
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                msg + '
');
            var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
            if (vote_alert.length == 0) {
                $(ev.currentTarget).parent().append($warning);
            }
        }
    });

    $('.o_js_forum_tag_follow').hover(
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeIn().css('display','block');
        },
        function(event) {
            $(this).find('.o_forum_tag_follow_box').stop().fadeOut().css('display','none');
    });

    // Extended user biography toogle
    $('.o_forum_user_info').hover(
        function () {
           $(this).parent().find('.o_forum_user_bio_expand').delay(500).toggle('fast');
        },
        function () {
            $(this).parent().find('.o_forum_user_bio_expand').clearQueue();
        }
    );

    $('.o_forum_user_bio_expand').hover(
        function () {},
        function () {
            $(this).fadeOut('fast');
        }
    );

    $('.flag').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if(data.error) {
                    var $warning;
                    if(data.error == 'anonymous_user') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to flag a post') +
                            '
');
                    } else if(data.error == 'post_already_flagged') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post is already flagged') +
                            '
');
                    } else if(data.error == 'post_non_flaggable') {
                        $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('This post can not be flagged') +
                            '
');
                    }
                    var flag_alert = $link.parent().find("#flag_alert");
                    if (flag_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else if(data.success) {
                    var elem = $link;
                    if(data.success == 'post_flagged_moderator') {
                        elem.html(' Flagged');
                        var c = parseInt($('#count_flagged_posts').html(), 10);
                        c++;
                        $('#count_flagged_posts').html(c);
                    } else if(data.success == 'post_flagged_non_moderator') {
                        elem.html(' Flagged');
                        var forum_answer = elem.closest('.forum_answer');
                        forum_answer.fadeIn(1000);
                        forum_answer.slideUp(1000);
                    }
                }
            });
    });

    $('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {})
            .then(function (data) {
                if (data.error){
                    if (data.error == 'own_post'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry, you cannot vote for your own posts') +
                            '
');
                    } else if (data.error == 'anonymous_user'){
                        var $warning = $('
'+
                            '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                            _t('Sorry you must be logged to vote') +
                            '
');
                    }
                    var vote_alert = $link.parent().find("#vote_alert");
                    if (vote_alert.length === 0) {
                        $link.parent().append($warning);
                    }
                } else {
                    $link.parent().find(".vote_count").html(data['vote_count']);
                    if (data.user_vote === 0) {
                        $link.parent().find(".text-success").removeClass("text-success");
                        $link.parent().find(".text-warning").removeClass("text-warning");
                    } else {
                        if (data.user_vote === 1) {
                            $link.addClass("text-success");
                        } else {
                            $link.addClass("text-warning");
                        }
                    }
                }
            });
    });

    $('.accept_answer').not('.karma_required').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data.error) {
                if (data.error == 'anonymous_user') {
                    var $warning = $('
'+
                        '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                        _t('Sorry, anonymous users cannot choose correct answer.') +
                        '
');
                }
                var correct_answer_alert = $link.parent().find("#correct_answer_alert");
                if (correct_answer_alert.length == 0) {
                    $link.parent().append($warning);
                }
            } else {
                if (data) {
                    $(".oe_answer_true").addClass('oe_answer_false').removeClass("oe_answer_true");
                    $link.addClass("oe_answer_true").removeClass('oe_answer_false');
                } else {
                    $link.removeClass("oe_answer_true").addClass('oe_answer_false');
                }
            }
        });
    });

    $('.favourite_question').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
            if (data) {
                $link.addClass("forum_favourite_question")
            } else {
                $link.removeClass("forum_favourite_question")
            }
        });
    });

    $('.comment_delete').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc($link.closest('form').attr('action'), 'call', {}).then(function () {
            $link.parents('.comment').first().remove();
        });
    });

    $('.notification_close').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/notification_read", 'call', {
            'notification_id': $link.attr("id")});
    });

    $('.send_validation_email').on('click', function (ev) {
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        ajax.jsonRpc("/forum/send_validation_email", 'call', {
            'forum_id': $link.attr('forum-id'),
        }).then(function (data) {
            if (data) {
                $('button.validation_email_close').click();
            }
        });
    });

    $('.validated_email_close').on('click', function (ev) {
        ajax.jsonRpc("/forum/validate_email/close", 'call', {});
    });

    // welcome message action button
    var forum_login = _.string.sprintf('%s/web?redirect=%s',
        window.location.origin, escape(window.location.href));
    $('.forum_register_url').attr('href',forum_login);

    $('.js_close_intro').on('click', function (ev) {
        ev.preventDefault();
        document.cookie = "forum_welcome_message = false";
        $('.forum_intro').slideUp();
        return true;
    });

    $('.link_url, .o_forum_post_link').on('change', function (ev) {  // keep .link_url for compat
        ev.preventDefault();
        var $link = $(ev.currentTarget);
        var display_error = function(){
            var $warning = $('
'+
                '<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
                'Please enter valid URL. Example: http://www.odoo.com'+
                '
');
            $link.parent().append($warning);
            $link.parents('form').find('button')[0].disabled = true;

        };
        var url = $link.val();
        if (url.search("^http(s?)://.*")) {
            url = 'http://'+url;
        }
        var regex = new RegExp("(http(s)?://.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)");
        if(regex.test(url)){
            ajax.jsonRpc("/forum/get_url_title", 'call', {'url': url}).then(function (data) {
                if (data) {
                    $("input[name='post_name']")[0].value = data;
                    $link.parents('form').find('button')[0].disabled = false;
                } else {
                    display_error();
                }

            });
        } else {
            display_error();
        }
    });

    $('input.js_select2').select2({
        tags: true,
        tokenSeparators: [",", " ", "_"],
        maximumInputLength: 35,
        minimumInputLength: 2,
        maximumSelectionSize: 5,
        lastsearch: [],
        createSearchChoice: function (term) {
            if ($(lastsearch).filter(function () { return this.text.localeCompare(term) === 0;}).length === 0) {
                //check Karma
                if (parseInt($("#karma").val()) >= parseInt($("#karma_edit_retag").val())) {
                    return {
                        id: "_" + $.trim(term),
                        text: $.trim(term) + ' *',
                        isNew: true,
                    };
                }
            }
        },
        formatResult: function(term) {
            if (term.isNew) {
                return 'New ' + _.escape(term.text);
            }
            else {
                return _.escape(term.text);
            }
        },
        ajax: {
            url: '/forum/get_tags',
            dataType: 'json',
            data: function(term) {
                return {
                    q: term,
                    l: 50
                };
            },
            results: function(data) {
                var ret = [];
                _.each(data, function(x) {
                    ret.push({ id: x.id, text: x.name, isNew: false });
                });
                lastsearch = ret;
                return { results: ret };
            }
        },
        // Take default tags from the input value
        initSelection: function (element, callback) {
            var data = [];
            _.each(element.data('init-value'), function(x) {
                data.push({ id: x.id, text: x.name, isNew: false });
            });
            element.val('');
            callback(data);
        },
    });

    $('textarea.load_editor').each(function () {
        var $textarea = $(this);
        var editor_karma = $textarea.data('karma') || 30;  // default value for backward compatibility
        if (!$textarea.val().match(/\S/)) {
            $textarea.val("

");
        }
        var $form = $textarea.closest('form');
        var toolbar = [
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'clear']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['history', ['undo', 'redo']],
            ];
        if (parseInt($("#karma").val()) >= editor_karma) {
            toolbar.push(['insert', ['link', 'picture']]);
        }
        $textarea.summernote({
                height: 150,
                toolbar: toolbar,
                styleWithSpan: false
            });
        $form.on('click', 'button, .a-submit', function () {
            $textarea.html($form.find('.note-editable').code());
        });
    });

});<|endoftext|>


<commit_before>odoo.define('website_links.website_links', function (require) {
'use strict';

var core = require('web.core');
var publicWidget = require('web.public.widget');

var _t = core._t;

var SelectBox = publicWidget.Widget.extend({
    events: {
        'change': '_onChange',
    },

    /**
     * @constructor
     * @param {Object} parent
     * @param {Object} obj
     * @param {String} placeholder
     */
    init: function (parent, obj, placeholder) {
        this._super.apply(this, arguments);
        this.obj = obj;
        this.placeholder = placeholder;
    },
    /**
     * @override
     */
    willStart: function () {
        var self = this;
        var defs = [this._super.apply(this, arguments)];
        defs.push(this._rpc({
            model: this.obj,
            method: 'search_read',
        }).then(function (result) {
            self.objects = _.map(result, function (val) {
                return {id: val.id, text: val.name};
            });
        }));
        return Promise.all(defs);
    },
    /**
     * @override
     */
    start: function () {
        var self = this;
        this.$el.select2({
            placeholder: self.placeholder,
            allowClear: true,
            createSearchChoice: function (term) {
                if (self._objectExists(term)) {
                    return null;
                }
                return {id: term, text: _.str.sprintf("Create '%s'", term)};
            },
            createSearchChoicePosition: 'bottom',
            multiple: false,
            data: self.objects,
        });
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {String} query
     */
    _objectExists: function (query) {
        return _.find(this.objects, function (val) {
            return val.text.toLowerCase() === query.toLowerCase();
        }) !== undefined;
    },
    /**
     * @private
     * @param {String} name
     */
    _createObject: function (name) {
        var self = this;
        return this._rpc({
            model: this.obj,
            method: 'create',
            args: [{name: name}],
        }).then(function (record) {
            self.$el.attr('value', record);
            self.objects.push({'id': record, 'text': name});
        });
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Object} ev
     */
    _onChange: function (ev) {
        if (!ev.added || !_.isString(ev.added.id)) {
            return;
        }
        this._createObject(ev.added.id);
    },
});

var RecentLinkBox = publicWidget.Widget.extend({
    template: 'website_links.RecentLink',
    xmlDependencies: ['/website_links/static/src/xml/recent_link.xml'],
    events: {
        'click .btn_shorten_url_clipboard': '_toggleCopyButton',
        'click .o_website_links_edit_code': '_editCode',
        'click .o_website_links_ok_edit': '_onLinksOkClick',
        'click .o_website_links_cancel_edit': '_onLinksCancelClick',
        'submit #o_website_links_edit_code_form': '_onSubmitCode',
    },

    /**
     * @constructor
     * @param {Object} parent
     * @param {Object} obj
     */
    init: function (parent, obj) {
        this._super.apply(this, arguments);
        this.link_obj = obj;
        this.animating_copy = false;
    },
    /**
     * @override
     */
    start: function () {
        new ClipboardJS(this.$('.btn_shorten_url_clipboard').get(0));
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _toggleCopyButton: function () {
        if (this.animating_copy) {
            return;
        }

        var self = this;
        this.animating_copy = true;
        var top = this.$('.o_website_links_short_url').position().top;
        this.$('.o_website_links_short_url').clone()
            .css('position', 'absolute')
            .css('left', 15)
            .css('top', top - 2)
            .css('z-index', 2)
            .removeClass('o_website_links_short_url')
            .addClass('animated-link')
            .insertAfter(this.$('.o_website_links_short_url'))
            .animate({
                opacity: 0,
                top: '-=20',
            }, 500, function () {
                self.$('.animated-link').remove();
                self.animating_copy = false;
            });
    },
    /**
     * @private
     * @param {String} message
     */
    _notification: function (message) {
        this.$('.notification').append('' + message + '');
    },
    /**
     * @private
     */
    _editCode: function () {
        var initCode = this.$('#o_website_links_code').html();
        this.$('#o_website_links_code').html('<form style="display:inline;" id="o_website_links_edit_code_form"><input type="hidden" id="init_code" value="' + initCode + '"/><input type="text" id="new_code" value="' + initCode + '"/></form>');
        this.$('.o_website_links_edit_code').hide();
        this.$('.copy-to-clipboard').hide();
        this.$('.o_website_links_edit_tools').show();
    },
    /**
     * @private
     */
    _cancelEdit: function () {
        this.$('.o_website_links_edit_code').show();
        this.$('.copy-to-clipboard').show();
        this.$('.o_website_links_edit_tools').hide();
        this.$('.o_website_links_code_error').hide();

        var oldCode = this.$('#o_website_links_edit_code_form #init_code').val();
        this.$('#o_website_links_code').html(oldCode);

        this.$('#code-error').remove();
        this.$('#o_website_links_code form').remove();
    },
    /**
     * @private
     */
    _submitCode: function () {
        var self = this;

        var initCode = this.$('#o_website_links_edit_code_form #init_code').val();
        var newCode = this.$('#o_website_links_edit_code_form #new_code').val();

        if (newCode === '') {
            self.$('.o_website_links_code_error').html(_t("The code cannot be left empty"));
            self.$('.o_website_links_code_error').show();
            return;
        }

        function showNewCode(newCode) {
            self.$('.o_website_links_code_error').html('');
            self.$('.o_website_links_code_error').hide();

            self.$('#o_website_links_code form').remove();

            // Show new code
            var host = self.$('#o_website_links_host').html();
            self.$('#o_website_links_code').html(newCode);

            // Update button copy to clipboard
            self.$('.btn_shorten_url_clipboard').attr('data-clipboard-text', host + newCode);

            // Show action again
            self.$('.o_website_links_edit_code').show();
            self.$('.copy-to-clipboard').show();
            self.$('.o_website_links_edit_tools').hide();
        }

        if (initCode === newCode) {
            showNewCode(newCode);
        } else {
            this._rpc({
                route: '/website_links/add_code',
                params: {
                    init_code: initCode,
                    new_code: newCode,
                },
            }).then(function (result) {
                showNewCode(result[0].code);
            }, function () {
                self.$('.o_website_links_code_error').show();
                self.$('.o_website_links_code_error').html(_t("This code is already taken"));
            });
        }
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Event} ev
     */
    _onLinksOkClick: function (ev) {
        ev.preventDefault();
        this._submitCode();
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onLinksCancelClick: function (ev) {
        ev.preventDefault();
        this._cancelEdit();
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onSubmitCode: function (ev) {
        ev.preventDefault();
        this._submitCode();
    },
});

var RecentLinks = publicWidget.Widget.extend({

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    getRecentLinks: function (filter) {
        var self = this;
        return this._rpc({
            route: '/website_links/recent_links',
            params: {
                filter: filter,
                limit: 20,
            },
        }).then(function (result) {
            _.each(result.reverse(), function (link) {
                self._addLink(link);
            });
            self._updateNotification();
        }, function () {
            var message = _t("Unable to get recent links");
            self.$el.append('
' + message + '
');
        });
    },
    /**
     * @private
     */
    _addLink: function (link) {
        var nbLinks = this.getChildren().length;
        var recentLinkBox = new RecentLinkBox(this, link);
        recentLinkBox.prependTo(this.$el);
        $('.link-tooltip').tooltip();

        if (nbLinks === 0) {
            this._updateNotification();
        }
    },
    /**
     * @private
     */
    removeLinks: function () {
        _.invoke(this.getChildren(), 'destroy');
    },
    /**
     * @private
     */
    _updateNotification: function () {
        if (this.getChildren().length === 0) {
            var message = _t("You don't have any recent links.");
            $('.o_website_links_recent_links_notification').html('
' + message + '
');
        } else {
            $('.o_website_links_recent_links_notification').empty();
        }
    },
});

publicWidget.registry.websiteLinks = publicWidget.Widget.extend({
    selector: '.o_website_links_create_tracked_url',
    events: {
        'click #filter-newest-links': '_onFilterNewestLinksClick',
        'click #filter-most-clicked-links': '_onFilterMostClickedLinksClick',
        'click #filter-recently-used-links': '_onFilterRecentlyUsedLinksClick',
        'click #generated_tracked_link a': '_onGeneratedTrackedLinkClick',
        'keyup #url': '_onUrlKeyUp',
        'click #btn_shorten_url': '_onShortenUrlButtonClick',
        'submit #o_website_links_link_tracker_form': '_onFormSubmit',
    },

    /**
     * @override
     */
    start: function () {
        var defs = [this._super.apply(this, arguments)];

        // UTMS selects widgets
        var campaignSelect = new SelectBox(this, 'utm.campaign', _t("e.g. Promotion of June, Winter Newsletter, .."));
        defs.push(campaignSelect.attachTo($('#campaign-select')));

        var mediumSelect = new SelectBox(this, 'utm.medium', _t("e.g. Newsletter, Social Network, .."));
        defs.push(mediumSelect.attachTo($('#channel-select')));

        var sourceSelect = new SelectBox(this, 'utm.source', _t("e.g. Search Engine, Website page, .."));
        defs.push(sourceSelect.attachTo($('#source-select')));

        // Recent Links Widgets
        this.recentLinks = new RecentLinks(this);
        defs.push(this.recentLinks.appendTo($('#o_website_links_recent_links')));
        this.recentLinks.getRecentLinks('newest');

        // Clipboard Library
        new ClipboardJS($('#btn_shorten_url').get(0));

        this.url_copy_animating = false;

        $('[data-toggle="tooltip"]').tooltip();

        return Promise.all(defs);
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _onFilterNewestLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('newest');
    },
    /**
     * @private
     */
    _onFilterMostClickedLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('most-clicked');
    },
    /**
     * @private
     */
    _onFilterRecentlyUsedLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('recently-used');
    },
    /**
     * @private
     */
    _onGeneratedTrackedLinkClick: function () {
        $('#generated_tracked_link a').text(_t("Copied")).removeClass('btn-primary').addClass('btn-success');
        setTimeout(function () {
            $('#generated_tracked_link a').text(_t("Copy")).removeClass('btn-success').addClass('btn-primary');
        }, 5000);
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onUrlKeyUp: function (ev) {
        if (!$('#btn_shorten_url').hasClass('btn-copy') || ev.which === 13) {
            return;
        }

        $('#btn_shorten_url').removeClass('btn-success btn-copy').addClass('btn-primary').html('Get tracked link');
        $('#generated_tracked_link').css('display', 'none');
        $('.o_website_links_utm_forms').show();
    },
    /**
     * @private
     */
    _onShortenUrlButtonClick: function () {
        if (!$('#btn_shorten_url').hasClass('btn-copy') || this.url_copy_animating) {
            return;
        }

        var self = this;
        this.url_copy_animating = true;
        $('#generated_tracked_link').clone()
            .css('position', 'absolute')
            .css('left', '78px')
            .css('bottom', '8px')
            .css('z-index', 2)
            .removeClass('#generated_tracked_link')
            .addClass('url-animated-link')
            .appendTo($('#generated_tracked_link'))
            .animate({
                opacity: 0,
                bottom: '+=20',
            }, 500, function () {
                $('.url-animated-link').remove();
                self.url_copy_animating = false;
            });
    },
    /**
     * Add the RecentLinkBox widget and send the form when the user generate the link
     *
     * @private
     * @param {Event} ev
     */
    _onFormSubmit: function (ev) {
        var self = this;
        ev.preventDefault();

        if ($('#btn_shorten_url').hasClass('btn-copy')) {
            return;
        }

        ev.stopPropagation();

        // Get URL and UTMs
        var campaignID = $('#campaign-select').attr('value');
        var mediumID = $('#channel-select').attr('value');
        var sourceID = $('#source-select').attr('value');

        var params = {};
        params.url = $('#url').val();
        if (campaignID !== '') {
            params.campaign_id = parseInt(campaignID);
        }
        if (mediumID !== '') {
            params.medium_id = parseInt(mediumID);
        }
        if (sourceID !== '') {
            params.source_id = parseInt(sourceID);
        }

        $('#btn_shorten_url').text(_t("Generating link..."));

        this._rpc({
            route: '/website_links/new',
            params: params,
        }).then(function (result) {
            if ('error' in result) {
                // Handle errors
                if (result.error === 'empty_url') {
                    $('.notification').html('
The URL is empty.
');
                } else if (result.error === 'url_not_found') {
                    $('.notification').html('
URL not found (404)
');
                } else {
                    $('.notification').html('
An error occur while trying to generate your link. Try again later.
');
                }
            } else {
                // Link generated, clean the form and show the link
                var link = result[0];

                $('#btn_shorten_url').removeClass('btn-primary').addClass('btn-success btn-copy').html('Copy');
                $('#btn_shorten_url').attr('data-clipboard-text', link.short_url);

                $('.notification').html('');
                $('#generated_tracked_link').html(link.short_url);
                $('#generated_tracked_link').css('display', 'inline');

                self.recentLinks._addLink(link);

                // Clean URL and UTM selects
                $('#campaign-select').select2('val', '');
                $('#channel-select').select2('val', '');
                $('#source-select').select2('val', '');

                $('.o_website_links_utm_forms').hide();
            }
        });
    },
});

return {
    SelectBox: SelectBox,
    RecentLinkBox: RecentLinkBox,
    RecentLinks: RecentLinks,
};
});
<commit_msg>[REF] website_links: set is_website to True on the campaign<commit_after>odoo.define('website_links.website_links', function (require) {
'use strict';

var core = require('web.core');
var publicWidget = require('web.public.widget');

var _t = core._t;

var SelectBox = publicWidget.Widget.extend({
    events: {
        'change': '_onChange',
    },

    /**
     * @constructor
     * @param {Object} parent
     * @param {Object} obj
     * @param {String} placeholder
     */
    init: function (parent, obj, placeholder) {
        this._super.apply(this, arguments);
        this.obj = obj;
        this.placeholder = placeholder;
    },
    /**
     * @override
     */
    willStart: function () {
        var self = this;
        var defs = [this._super.apply(this, arguments)];
        defs.push(this._rpc({
            model: this.obj,
            method: 'search_read',
        }).then(function (result) {
            self.objects = _.map(result, function (val) {
                return {id: val.id, text: val.name};
            });
        }));
        return Promise.all(defs);
    },
    /**
     * @override
     */
    start: function () {
        var self = this;
        this.$el.select2({
            placeholder: self.placeholder,
            allowClear: true,
            createSearchChoice: function (term) {
                if (self._objectExists(term)) {
                    return null;
                }
                return {id: term, text: _.str.sprintf("Create '%s'", term)};
            },
            createSearchChoicePosition: 'bottom',
            multiple: false,
            data: self.objects,
        });
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {String} query
     */
    _objectExists: function (query) {
        return _.find(this.objects, function (val) {
            return val.text.toLowerCase() === query.toLowerCase();
        }) !== undefined;
    },
    /**
     * @private
     * @param {String} name
     */
    _createObject: function (name) {
        var self = this;
        var args = {
            name: name
        };
        if (this.obj === "utm.campaign"){
            args.is_website = true;
        }
        return this._rpc({
            model: this.obj,
            method: 'create',
            args: [args],
        }).then(function (record) {
            self.$el.attr('value', record);
            self.objects.push({'id': record, 'text': name});
        });
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Object} ev
     */
    _onChange: function (ev) {
        if (!ev.added || !_.isString(ev.added.id)) {
            return;
        }
        this._createObject(ev.added.id);
    },
});

var RecentLinkBox = publicWidget.Widget.extend({
    template: 'website_links.RecentLink',
    xmlDependencies: ['/website_links/static/src/xml/recent_link.xml'],
    events: {
        'click .btn_shorten_url_clipboard': '_toggleCopyButton',
        'click .o_website_links_edit_code': '_editCode',
        'click .o_website_links_ok_edit': '_onLinksOkClick',
        'click .o_website_links_cancel_edit': '_onLinksCancelClick',
        'submit #o_website_links_edit_code_form': '_onSubmitCode',
    },

    /**
     * @constructor
     * @param {Object} parent
     * @param {Object} obj
     */
    init: function (parent, obj) {
        this._super.apply(this, arguments);
        this.link_obj = obj;
        this.animating_copy = false;
    },
    /**
     * @override
     */
    start: function () {
        new ClipboardJS(this.$('.btn_shorten_url_clipboard').get(0));
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _toggleCopyButton: function () {
        if (this.animating_copy) {
            return;
        }

        var self = this;
        this.animating_copy = true;
        var top = this.$('.o_website_links_short_url').position().top;
        this.$('.o_website_links_short_url').clone()
            .css('position', 'absolute')
            .css('left', 15)
            .css('top', top - 2)
            .css('z-index', 2)
            .removeClass('o_website_links_short_url')
            .addClass('animated-link')
            .insertAfter(this.$('.o_website_links_short_url'))
            .animate({
                opacity: 0,
                top: '-=20',
            }, 500, function () {
                self.$('.animated-link').remove();
                self.animating_copy = false;
            });
    },
    /**
     * @private
     * @param {String} message
     */
    _notification: function (message) {
        this.$('.notification').append('' + message + '');
    },
    /**
     * @private
     */
    _editCode: function () {
        var initCode = this.$('#o_website_links_code').html();
        this.$('#o_website_links_code').html('<form style="display:inline;" id="o_website_links_edit_code_form"><input type="hidden" id="init_code" value="' + initCode + '"/><input type="text" id="new_code" value="' + initCode + '"/></form>');
        this.$('.o_website_links_edit_code').hide();
        this.$('.copy-to-clipboard').hide();
        this.$('.o_website_links_edit_tools').show();
    },
    /**
     * @private
     */
    _cancelEdit: function () {
        this.$('.o_website_links_edit_code').show();
        this.$('.copy-to-clipboard').show();
        this.$('.o_website_links_edit_tools').hide();
        this.$('.o_website_links_code_error').hide();

        var oldCode = this.$('#o_website_links_edit_code_form #init_code').val();
        this.$('#o_website_links_code').html(oldCode);

        this.$('#code-error').remove();
        this.$('#o_website_links_code form').remove();
    },
    /**
     * @private
     */
    _submitCode: function () {
        var self = this;

        var initCode = this.$('#o_website_links_edit_code_form #init_code').val();
        var newCode = this.$('#o_website_links_edit_code_form #new_code').val();

        if (newCode === '') {
            self.$('.o_website_links_code_error').html(_t("The code cannot be left empty"));
            self.$('.o_website_links_code_error').show();
            return;
        }

        function showNewCode(newCode) {
            self.$('.o_website_links_code_error').html('');
            self.$('.o_website_links_code_error').hide();

            self.$('#o_website_links_code form').remove();

            // Show new code
            var host = self.$('#o_website_links_host').html();
            self.$('#o_website_links_code').html(newCode);

            // Update button copy to clipboard
            self.$('.btn_shorten_url_clipboard').attr('data-clipboard-text', host + newCode);

            // Show action again
            self.$('.o_website_links_edit_code').show();
            self.$('.copy-to-clipboard').show();
            self.$('.o_website_links_edit_tools').hide();
        }

        if (initCode === newCode) {
            showNewCode(newCode);
        } else {
            this._rpc({
                route: '/website_links/add_code',
                params: {
                    init_code: initCode,
                    new_code: newCode,
                },
            }).then(function (result) {
                showNewCode(result[0].code);
            }, function () {
                self.$('.o_website_links_code_error').show();
                self.$('.o_website_links_code_error').html(_t("This code is already taken"));
            });
        }
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Event} ev
     */
    _onLinksOkClick: function (ev) {
        ev.preventDefault();
        this._submitCode();
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onLinksCancelClick: function (ev) {
        ev.preventDefault();
        this._cancelEdit();
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onSubmitCode: function (ev) {
        ev.preventDefault();
        this._submitCode();
    },
});

var RecentLinks = publicWidget.Widget.extend({

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    getRecentLinks: function (filter) {
        var self = this;
        return this._rpc({
            route: '/website_links/recent_links',
            params: {
                filter: filter,
                limit: 20,
            },
        }).then(function (result) {
            _.each(result.reverse(), function (link) {
                self._addLink(link);
            });
            self._updateNotification();
        }, function () {
            var message = _t("Unable to get recent links");
            self.$el.append('
' + message + '
');
        });
    },
    /**
     * @private
     */
    _addLink: function (link) {
        var nbLinks = this.getChildren().length;
        var recentLinkBox = new RecentLinkBox(this, link);
        recentLinkBox.prependTo(this.$el);
        $('.link-tooltip').tooltip();

        if (nbLinks === 0) {
            this._updateNotification();
        }
    },
    /**
     * @private
     */
    removeLinks: function () {
        _.invoke(this.getChildren(), 'destroy');
    },
    /**
     * @private
     */
    _updateNotification: function () {
        if (this.getChildren().length === 0) {
            var message = _t("You don't have any recent links.");
            $('.o_website_links_recent_links_notification').html('
' + message + '
');
        } else {
            $('.o_website_links_recent_links_notification').empty();
        }
    },
});

publicWidget.registry.websiteLinks = publicWidget.Widget.extend({
    selector: '.o_website_links_create_tracked_url',
    events: {
        'click #filter-newest-links': '_onFilterNewestLinksClick',
        'click #filter-most-clicked-links': '_onFilterMostClickedLinksClick',
        'click #filter-recently-used-links': '_onFilterRecentlyUsedLinksClick',
        'click #generated_tracked_link a': '_onGeneratedTrackedLinkClick',
        'keyup #url': '_onUrlKeyUp',
        'click #btn_shorten_url': '_onShortenUrlButtonClick',
        'submit #o_website_links_link_tracker_form': '_onFormSubmit',
    },

    /**
     * @override
     */
    start: function () {
        var defs = [this._super.apply(this, arguments)];

        // UTMS selects widgets
        var campaignSelect = new SelectBox(this, 'utm.campaign', _t("e.g. Promotion of June, Winter Newsletter, .."));
        defs.push(campaignSelect.attachTo($('#campaign-select')));

        var mediumSelect = new SelectBox(this, 'utm.medium', _t("e.g. Newsletter, Social Network, .."));
        defs.push(mediumSelect.attachTo($('#channel-select')));

        var sourceSelect = new SelectBox(this, 'utm.source', _t("e.g. Search Engine, Website page, .."));
        defs.push(sourceSelect.attachTo($('#source-select')));

        // Recent Links Widgets
        this.recentLinks = new RecentLinks(this);
        defs.push(this.recentLinks.appendTo($('#o_website_links_recent_links')));
        this.recentLinks.getRecentLinks('newest');

        // Clipboard Library
        new ClipboardJS($('#btn_shorten_url').get(0));

        this.url_copy_animating = false;

        $('[data-toggle="tooltip"]').tooltip();

        return Promise.all(defs);
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _onFilterNewestLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('newest');
    },
    /**
     * @private
     */
    _onFilterMostClickedLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('most-clicked');
    },
    /**
     * @private
     */
    _onFilterRecentlyUsedLinksClick: function () {
        this.recentLinks.removeLinks();
        this.recentLinks.getRecentLinks('recently-used');
    },
    /**
     * @private
     */
    _onGeneratedTrackedLinkClick: function () {
        $('#generated_tracked_link a').text(_t("Copied")).removeClass('btn-primary').addClass('btn-success');
        setTimeout(function () {
            $('#generated_tracked_link a').text(_t("Copy")).removeClass('btn-success').addClass('btn-primary');
        }, 5000);
    },
    /**
     * @private
     * @param {Event} ev
     */
    _onUrlKeyUp: function (ev) {
        if (!$('#btn_shorten_url').hasClass('btn-copy') || ev.which === 13) {
            return;
        }

        $('#btn_shorten_url').removeClass('btn-success btn-copy').addClass('btn-primary').html('Get tracked link');
        $('#generated_tracked_link').css('display', 'none');
        $('.o_website_links_utm_forms').show();
    },
    /**
     * @private
     */
    _onShortenUrlButtonClick: function () {
        if (!$('#btn_shorten_url').hasClass('btn-copy') || this.url_copy_animating) {
            return;
        }

        var self = this;
        this.url_copy_animating = true;
        $('#generated_tracked_link').clone()
            .css('position', 'absolute')
            .css('left', '78px')
            .css('bottom', '8px')
            .css('z-index', 2)
            .removeClass('#generated_tracked_link')
            .addClass('url-animated-link')
            .appendTo($('#generated_tracked_link'))
            .animate({
                opacity: 0,
                bottom: '+=20',
            }, 500, function () {
                $('.url-animated-link').remove();
                self.url_copy_animating = false;
            });
    },
    /**
     * Add the RecentLinkBox widget and send the form when the user generate the link
     *
     * @private
     * @param {Event} ev
     */
    _onFormSubmit: function (ev) {
        var self = this;
        ev.preventDefault();

        if ($('#btn_shorten_url').hasClass('btn-copy')) {
            return;
        }

        ev.stopPropagation();

        // Get URL and UTMs
        var campaignID = $('#campaign-select').attr('value');
        var mediumID = $('#channel-select').attr('value');
        var sourceID = $('#source-select').attr('value');

        var params = {};
        params.url = $('#url').val();
        if (campaignID !== '') {
            params.campaign_id = parseInt(campaignID);
        }
        if (mediumID !== '') {
            params.medium_id = parseInt(mediumID);
        }
        if (sourceID !== '') {
            params.source_id = parseInt(sourceID);
        }

        $('#btn_shorten_url').text(_t("Generating link..."));

        this._rpc({
            route: '/website_links/new',
            params: params,
        }).then(function (result) {
            if ('error' in result) {
                // Handle errors
                if (result.error === 'empty_url') {
                    $('.notification').html('
The URL is empty.
');
                } else if (result.error === 'url_not_found') {
                    $('.notification').html('
URL not found (404)
');
                } else {
                    $('.notification').html('
An error occur while trying to generate your link. Try again later.
');
                }
            } else {
                // Link generated, clean the form and show the link
                var link = result[0];

                $('#btn_shorten_url').removeClass('btn-primary').addClass('btn-success btn-copy').html('Copy');
                $('#btn_shorten_url').attr('data-clipboard-text', link.short_url);

                $('.notification').html('');
                $('#generated_tracked_link').html(link.short_url);
                $('#generated_tracked_link').css('display', 'inline');

                self.recentLinks._addLink(link);

                // Clean URL and UTM selects
                $('#campaign-select').select2('val', '');
                $('#channel-select').select2('val', '');
                $('#source-select').select2('val', '');

                $('.o_website_links_utm_forms').hide();
            }
        });
    },
});

return {
    SelectBox: SelectBox,
    RecentLinkBox: RecentLinkBox,
    RecentLinks: RecentLinks,
};
});
<|endoftext|>


<commit_before>#!/usr/bin/env node
require('coffee-script/register')
require('./node_modules/vows/bin/vows')

<commit_msg>vowsrunner: change coffee-script to coffeescript<commit_after>#!/usr/bin/env node
require('coffeescript/register')
require('./node_modules/vows/bin/vows')

<|endoftext|>