diff --git a/src/e-util/test-html-editor-units-bugs.c b/src/e-util/test-html-editor-units-bugs.c index 071be22..a4fbfe0 100644 --- a/src/e-util/test-html-editor-units-bugs.c +++ b/src/e-util/test-html-editor-units-bugs.c @@ -1319,6 +1319,44 @@ test_issue_86 (TestFixture *fixture) g_free (converted); } +static void +test_issue_103 (TestFixture *fixture) +{ + #define LONG_URL "https://www.example.com/123456789012345678901234567890123456789012345678901234567890" + #define SHORTER_URL "https://www.example.com/1234567890123456789012345678901234567890" + #define SHORT_URL "https://www.example.com/" + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:before\\n" + LONG_URL "\\n" + "after\\n" + "prefix text " SHORTER_URL " suffix\\n" + "prefix " SHORT_URL " suffix\\n" + "end\n", + HTML_PREFIX "
before
" + "
" LONG_URL "
" + "
after
" + "
prefix text " SHORTER_URL " suffix
" + "
prefix " SHORT_URL " suffix
" + "
end
" + HTML_SUFFIX, + "before\n" + LONG_URL "\n" + "after\n" + "prefix text \n" + SHORTER_URL " suffix\n" + "prefix " SHORT_URL " suffix\n" + "end")) { + g_test_fail (); + return; + } + + #undef SHORT_URL + #undef SHORTER_URL + #undef LONG_URL +} + void test_add_html_editor_bug_tests (void) { @@ -1349,4 +1387,5 @@ test_add_html_editor_bug_tests (void) test_utils_add_test ("/bug/788829", test_bug_788829); test_utils_add_test ("/bug/750636", test_bug_750636); test_utils_add_test ("/issue/86", test_issue_86); + test_utils_add_test ("/issue/103", test_issue_103); } diff --git a/src/e-util/test-html-editor-units-bugs.c.extra-new-line-before-url b/src/e-util/test-html-editor-units-bugs.c.extra-new-line-before-url new file mode 100644 index 0000000..071be22 --- /dev/null +++ b/src/e-util/test-html-editor-units-bugs.c.extra-new-line-before-url @@ -0,0 +1,1352 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com) + * + * This library is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "evolution-config.h" + +#include + +#include "test-html-editor-units-utils.h" + +#include "test-html-editor-units-bugs.h" + +static void +test_bug_726548 (TestFixture *fixture) +{ + /* This test is known to fail, skip it. */ + printf ("SKIPPED "); +#if 0 + gboolean success; + gchar *text; + const gchar *expected_plain = + "aaa\n" + " 1. a\n" + " 2. b\n" + " 3. c\n"; + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:aaa\\n\n" + "action:style-list-number\n" + "type:a\\nb\\nc\\n\\n\n" + "seq:C\n" + "type:ac\n" + "seq:c\n", + HTML_PREFIX "
aaa
" + "
    " + "
  1. a
  2. b
  3. c
" + "

" HTML_SUFFIX, + expected_plain)) { + g_test_fail (); + return; + } + + text = test_utils_get_clipboard_text (FALSE); + success = test_utils_html_equal (fixture, text, expected_plain); + + if (!success) { + g_warning ("%s: clipboard Plain text \n---%s---\n does not match expected Plain\n---%s---", + G_STRFUNC, text, expected_plain); + g_free (text); + g_test_fail (); + } else { + g_free (text); + } +#endif +} + +static void +test_bug_750657 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:html\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "\n" + "
\n" + "
This is the first paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "

\n" + "
This is the third paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "
\n" + "
This is the first paragraph of a sub-quoted text which has some long text to test. It has the second sentence as well.
\n" + "
\n" + "
\n" + "
This is the fourth paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "
\n" + "

\n" + "", + E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:uuuSuusD\n", + HTML_PREFIX + "
\n" + "
This is the first paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "

\n" + "
This is the third paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "
\n" + "

\n" + "
\n" + "
This is the fourth paragraph of a quoted text which has some long text to test. It has the second sentence as well.
\n" + "
\n" + "

" + HTML_SUFFIX, + NULL)) { + g_test_fail (); + return; + } +} + +static void +test_bug_760989 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:html\n" + "type:a\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "\n" + "One line before quotation
\n" + "
\n" + "
Single line quoted.
\n" + "
\n" + "", + E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:ChcD\n", + HTML_PREFIX "
One line before quotation
\n" + "
\n" + "
Single line quoted.
\n" + "
" HTML_SUFFIX, + "One line before quotation\n" + "> Single line quoted.")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:Cecb\n", + HTML_PREFIX "
One line before quotation
\n" + "
\n" + "
Single line quoted
\n" + "
" HTML_SUFFIX, + "One line before quotation\n" + "> Single line quoted")) { + g_test_fail (); + return; + } +} + +static void +test_bug_767903 (TestFixture *fixture) +{ + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:This is the first line:\\n\n" + "action:style-list-bullet\n" + "type:First item\\n\n" + "type:Second item\n", + HTML_PREFIX "
This is the first line:
" + "" HTML_SUFFIX, + "This is the first line:\n" + " * First item\n" + " * Second item")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:uhb\n" + "undo:undo\n", + HTML_PREFIX "
This is the first line:
" + "" HTML_SUFFIX, + "This is the first line:\n" + " * First item\n" + " * Second item")) { + g_test_fail (); + return; + } +} + +static void +test_bug_769708 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "" + "" + "" + "
aaa
" + "
-- 
" + "
user <user@no.where>
" + "
", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "
aaa
-- 
" + "
user <user@no.where>
" + "
" HTML_SUFFIX, + "aaa\n" + "-- \n" + "user ")) + g_test_fail (); +} + +static void +test_bug_769913 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:html\n")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "type:ab\n" + "seq:ltlD\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:ttllDD\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:ttlDlD\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:tttlllDDD\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:tttlDlDlD\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:tb\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:ttbb\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:ttlbrb\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:tttbbb\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:tttllbrbrb\n", + HTML_PREFIX "
ab
" HTML_SUFFIX, + "ab")) { + g_test_fail (); + return; + } +} + +static void +test_bug_769955 (TestFixture *fixture) +{ + test_utils_set_clipboard_text ("http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines", FALSE); + + /* Use paste action, pretty the same as Ctrl+V */ + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "action:paste\n" + "seq:ll\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		""
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" + HTML_SUFFIX, + "http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[1] \n" + "action:paste\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[1] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[1] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[2] \n" + "action:paste\n" + "seq:h\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[2] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[2] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[3] \n" + "action:paste\n" + "seq:Chc\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[3] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[3] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[4] \n" + "action:paste\n" + "seq:l\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[4] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[4] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + /* Use Shift+Insert instead of paste action */ + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "seq:Sis\n" + "seq:ll\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		""
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" + HTML_SUFFIX, + "http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[5] \n" + "seq:Sis\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[5] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[5] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[6] \n" + "seq:Sis\n" + "seq:h\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[6] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[6] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[7] \n" + "seq:Sis\n" + "seq:Chc\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[7] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[7] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } + + if (!test_utils_run_simple_test (fixture, + "seq:C\n" + "type:a\n" + "action:style-normal\n" + "seq:Dc\n" + "type:[8] \n" + "seq:Sis\n" + "seq:l\n" + "action:style-preformat\n", + HTML_PREFIX "
"
+		"[8] "
+		"http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines
" HTML_SUFFIX, + "[8] http://www.example.com/this-is-a-very-long-link-which-should-not-be-wrapped-into-multiple-lines")) { + g_test_fail (); + return; + } +} + +static void +test_bug_770073 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "" + "
the 1st line text
" + "
" + "
the 3rd line text
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:Chcddbb\n", + HTML_PREFIX "
On Today, User wrote:
" + "
" + "
> the 1st line text
" + "
> the 3rd line text
" + "
" HTML_SUFFIX, + "On Today, User wrote:\n" + "> the 1st line text\n" + "> the 3rd line text")) { + g_test_fail (); + return; + } + + if (!test_utils_process_commands (fixture, + "mode:html\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "" + "
the first line text
" + "
" + "
the third line text
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:Chcddbb\n", + HTML_PREFIX "
On Today, User wrote:
" + "
" + "
the first line text
" + "
the third line text
" + "
" HTML_SUFFIX, + "On Today, User wrote:\n" + "> the first line text\n" + "> the third line text")) + g_test_fail (); + +} + +static void +test_bug_770074 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "" + "
the 1st line text
" + "
" + "
the 3rd line text
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:Chcddbb\n" + "seq:n\n" + "undo:undo\n", + HTML_PREFIX "
On Today, User wrote:
" + "
" + "
> the 1st line text
" + "
> the 3rd line text
" + "
" HTML_SUFFIX, + "On Today, User wrote:\n" + "> the 1st line text\n" + "> the 3rd line text")) + g_test_fail (); +} + +static void +test_bug_771044 (TestFixture *fixture) +{ + if (!test_utils_run_simple_test (fixture, + "type:123 456\\n789 abc\\n\n" + "seq:uuhSdsD\n", + HTML_PREFIX + "
789 abc
" + "

" + HTML_SUFFIX, + "789 abc\n")) + g_test_fail (); +} + +static void +test_bug_771131 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
On <date1>, <name1> wrote:\n"
+		"
\n" + "Hello\n" + "\n" + "Goodbye
" + "
the 3rd line text
" + "
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "
On Sat, 2016-09-10 at 20:00 +0000, example@example.com wrote:
" + "
" + "
> On <date1>, <name1> wrote:
" + "
" + "
> > Hello
" + "
> >
" + "
> > Goodbye
" + "
" + "
>
" + "
> the 3rd line text
" + "
" + HTML_SUFFIX, + "On Sat, 2016-09-10 at 20:00 +0000, example@example.com wrote:\n" + "> On , wrote:\n" + "> > Hello\n" + "> > \n" + "> > Goodbye\n" + "> \n" + "> the 3rd line text")) + g_test_fail (); +} + +static void +test_bug_771493 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "

" + "----- Original Message -----\n" + "
\n" + "This week summary:" + "
" + "
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "
On Thu, 2016-09-15 at 08:08 -0400, user wrote:
" + "
" + "
>
" + "
> ----- Original Message -----
" + "
" + "
> > This week summary:
" + "
" + "
" + HTML_SUFFIX, + "On Thu, 2016-09-15 at 08:08 -0400, user wrote:\n" + "> \n" + "> ----- Original Message -----\n" + "> > This week summary:")) + g_test_fail (); +} + +static void +test_bug_772171 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
a\n"
+		"b\n"
+		"
" + "" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:ddeb", + HTML_PREFIX "
On Thu, 2016-09-15 at 08:08 -0400, user wrote:
" + "
" + "
>
" + "
> b
" + "
" + HTML_SUFFIX, + "On Thu, 2016-09-15 at 08:08 -0400, user wrote:\n" + "> \n" + "> b")) + g_test_fail (); +} + +static void +test_bug_772513 (TestFixture *fixture) +{ + EContentEditor *cnt_editor; + gboolean set_signature_from_message, check_if_signature_is_changed, ignore_next_signature_change; + + test_utils_fixture_change_setting_boolean (fixture, "org.gnome.evolution.mail", "composer-reply-start-bottom", TRUE); + + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + cnt_editor = test_utils_get_content_editor (fixture); + + e_content_editor_insert_signature ( + cnt_editor, + "", + FALSE, + "none", + &set_signature_from_message, + &check_if_signature_is_changed, + &ignore_next_signature_change); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "

" HTML_SUFFIX, + "\n")) + g_test_fail (); +} + +static void +test_bug_772918 (TestFixture *fixture) +{ + if (!test_utils_run_simple_test (fixture, + "mode:html\n" + "type:a b c d\n" + "seq:lll\n" + "type:1 2 3 \n" + "undo:undo:6\n" + "undo:redo:6\n", + HTML_PREFIX "
a b 1 2 3 c d
" HTML_SUFFIX, + "a b 1 2 3 c d")) + g_test_fail (); +} + +static void +test_bug_773164 (TestFixture *fixture) +{ + test_utils_set_clipboard_text ("This is paragraph 1\n\nThis is paragraph 2\n\nThis is a longer paragraph 3", FALSE); + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "undo:save\n" + "action:paste\n" + "undo:undo\n" + "undo:test\n" + "undo:redo\n" + "seq:huuuue\n" /* Go to the end of the first line */ + "seq:Sdds\n" + "action:cut\n" + "seq:dde\n" /* Go to the end of the last line */ + "action:paste\n" + "undo:undo:5\n" + "undo:test\n" + "undo:redo:5\n", + HTML_PREFIX "
This is paragraph 1
" + "

" + "
This is a longer paragraph 3
" + "

" + "
This is paragraph 2
" + HTML_SUFFIX, + "This is paragraph 1\n" + "\n" + "This is a longer paragraph 3\n" + "\n" + "This is paragraph 2")) + g_test_fail (); +} + +static void +test_bug_775042 (TestFixture *fixture) +{ + test_utils_insert_content (fixture, + "
a\n"
+		"b\n"
+		"c"
+		""
+		"",
+		E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML);
+
+	if (!test_utils_run_simple_test (fixture,
+		"seq:rl\n"
+		"mode:plain\n",
+		HTML_PREFIX "
On Fri, 2016-11-25 at 08:18 +0000, user wrote:
" + "
" + "
> a
" + "> b
" + "> c
" + "
" + HTML_SUFFIX, + "On Fri, 2016-11-25 at 08:18 +0000, user wrote:\n" + "> a\n" + "> b\n" + "> c")) + g_test_fail (); +} + +static void +test_bug_775691 (TestFixture *fixture) +{ + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:abc def ghi\\n\n" + "seq:urrrrSrrrs\n" + "action:copy\n" + "seq:d\n" + "action:paste\n", + HTML_PREFIX "
abc def ghi
" + "
def
" + HTML_SUFFIX, + "abc def ghi\n" + "def")) + g_test_fail (); +} + +static void +test_bug_779707 (TestFixture *fixture) +{ + test_utils_fixture_change_setting_boolean (fixture, "org.gnome.evolution.mail", "composer-reply-start-bottom", TRUE); + test_utils_fixture_change_setting_boolean (fixture, "org.gnome.evolution.mail", "composer-wrap-quoted-text-in-replies", FALSE); + + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
line 1\n"
+		"line 2\n"
+		"line 3\n"
+		"
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:uuuSesDbnnu\n" + "type:a very long text, which splits into multiple lines when this paragraph is not marked as preformatted, but as normal, as it should be\n" + "", + HTML_PREFIX "
Credits:
" + "
" + "
> line 1
" + "
" + "

" + "
a very long text, which splits into multiple lines when this paragraph is not marked as preformatted, but as normal, as it should be
" + "

" + "
" + "
> line 3
" + "
" + "

" + HTML_SUFFIX, + "Credits:\n" + "> line 1\n" + "\n" + "a very long text, which splits into multiple lines when this paragraph\n" + "is not marked as preformatted, but as normal, as it should be\n" + "\n" + "> line 3\n")) + g_test_fail (); +} + +static void +test_bug_780275_html (TestFixture *fixture) +{ + test_utils_set_clipboard_text ("line 1\nline 2\nline 3", FALSE); + + if (!test_utils_run_simple_test (fixture, + "mode:html\n" + "type:line 0\n" + "seq:nn\n" + "action:paste-quote\n" + "undo:save\n" /* 1 */ + "seq:huuuD\n" + "undo:undo\n" + "undo:test:1\n" + "undo:redo\n" + "type:X\n" + "seq:ddenn\n" + "type:line 4\n" + "undo:drop\n" + "undo:save\n" /* 1 */ + "seq:hSuusD\n" + "undo:undo\n" + "undo:test:1\n" + "undo:redo\n" + "", + HTML_PREFIX "
line 0
" + "
" + "
Xline 1
" + "
line 2
" + "
" + "
line 4
" + HTML_SUFFIX, + "line 0\n" + "> Xline 1\n" + "> line 2\n" + "line 4")) + g_test_fail (); +} + +static void +test_bug_780275_plain (TestFixture *fixture) +{ + test_utils_set_clipboard_text ("line 1\nline 2\nline 3", FALSE); + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:line 0\n" + "seq:nn\n" + "action:paste-quote\n" + "undo:save\n" /* 1 */ + "seq:huuuD\n" + "undo:undo\n" + "undo:test:1\n" + "undo:redo\n" + "type:X\n" + "seq:ddenn\n" + "type:line 4\n" + "undo:drop\n" + "undo:save\n" /* 1 */ + "seq:hSuusD\n" + "undo:undo\n" + "undo:test:1\n" + "undo:redo\n", + HTML_PREFIX "
line 0
" + "
" + "
> Xline 1
" + "
> line 2
" + "
" + "
line 4
" + HTML_SUFFIX, + "line 0\n" + "> Xline 1\n" + "> line 2\n" + "line 4")) + g_test_fail (); +} + +static void +test_bug_781722 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
Signed-off-by: User <user@no.where>\n"
+		"
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:dd\n" + "action:style-preformat\n", + HTML_PREFIX "
Credits:
" + "
" + "
> Signed-off-by: User <user@no.where>
" + "
" + HTML_SUFFIX, + "Credits:\n" + "> Signed-off-by: User ")) + g_test_fail (); +} + +static void +test_bug_781116 (TestFixture *fixture) +{ + test_utils_fixture_change_setting_boolean (fixture, "org.gnome.evolution.mail", "composer-wrap-quoted-text-in-replies", FALSE); + + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
a very long text, which splits into multiple lines when this paragraph is not marked as preformatted, but as normal, as it should be
\n" + "
" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "seq:dd\n" + "action:wrap-lines\n", + HTML_PREFIX "
Credits:
" + "
" + "
> a very long text, which splits into multiple lines when this
" + "> paragraph is not marked as preformatted, but as normal, as it should
" + "> be
" + "
" + HTML_SUFFIX, + "Credits:\n" + "> a very long text, which splits into multiple lines when this\n" + "> paragraph is not marked as preformatted, but as normal, as it should\n" + "> be")) + g_test_fail (); +} + +static void +test_bug_780088 (TestFixture *fixture) +{ + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_set_clipboard_text ("Seeing @blah instead of @foo XX'ed on" UNICODE_NBSP "https://example.sub" UNICODE_NBSP "domain.org/page I'd recommend to XX YY , click fjwvne on the left, click skjd sjewncj on the right, and set wqje wjfdn Xs to something like wqjfnm www.example.com/~user wjfdncj or such.", FALSE); + + if (!test_utils_run_simple_test (fixture, + "action:paste\n" + "seq:n", + HTML_PREFIX "
" + "Seeing @blah instead of @foo XX'ed on https://example.sub" + " domain.org/page I'd recommend to XX YY " + "<https://example.subdomain.org/p/user/> , " + "click fjwvne on the left, click skjd sjewncj on the right, and set wqje wjfdn Xs to something like " + "wqjfnm www.example.com/~user wjfdncj or such.
" + "

" + HTML_SUFFIX, + "Seeing @blah instead of @foo XX'ed on" UNICODE_NBSP "https://example.sub" UNICODE_NBSP "domain.org/pa\n" + "ge I'd recommend to XX YY ,\n" + "click fjwvne on the left, click skjd sjewncj on the right, and set wqje\n" + "wjfdn Xs to something like wqjfnm www.example.com/~user wjfdncj or\n" + "such.\n")) + g_test_fail (); +} + +static void +test_bug_788829 (TestFixture *fixture) +{ + test_utils_fixture_change_setting_boolean (fixture, "org.gnome.evolution.mail", "composer-wrap-quoted-text-in-replies", TRUE); + test_utils_fixture_change_setting_int32 (fixture, "org.gnome.evolution.mail", "composer-word-wrap-length", 71); + + if (!test_utils_process_commands (fixture, + "mode:plain\n")) { + g_test_fail (); + return; + } + + test_utils_insert_content (fixture, + "
Xxxxx xx xxxxxxxxx xx xxxxxxx xx xxxxx xxxx xxxx xx xxx xxx xxxx xxx xxxçx xôxé " + "\"xxxxx xxxx xxxxxxx xxx\" xx xxxx xxxxé xxx xxx xxxéx xxx x'x xéxxxxé x'xxxxxxxxx xx " + "xxx \"Xxxx XXX Xxxxxx Xxx\". Xx xxxx xxxxxxxx xxx xxxxxxxxxxxxxxxx.xx (xxxxxxx xxxxxxxxxx xx .xxx). Xxxx " + "êxxx xxx xxxxxxxxxxx xxxéxxxxxxxx, xxxx xxxxx xx XXX xx xéxxx à xx xxx \"xxx xxxxxx xxxx " + "xx xxxxxxx\" xx xxxx xx xxxxx xxxxxxxx xxxxxxxx xx $ xx xxxx x'xxxxxx.

" + "
Xxxx xx xéxxxxxxx, xxxxxxxx xxxxxxx (!), xxxxxxx à xxx, xxxx ooo$ XXX xxxxé: " + "https://xxxxxxxxxxxxxxxx.xx/xxxxxxx/xxxxx-xxxx-xxxxxxxx-x" + "xxxx-xxxx-xxx-xxxxxxxx-xxx/ xx xx xxxx xéxéxxxxxxx x'xxxxxx xxxx xx xxxxxx xx xxxxxx" + "xxxxxx xx xxx (xxxxx Xxxxxx) xxxx xxxx x'xxxxxxx xx xxxxxx: " + "https://xxxxxxxxxxxxxxxx.xxx/xx-xxxxxxx/xxxxxxx/Xxxxxxxxxxxx-Xxxxx-Xxxx-XXX-Xxxxxx-Xxx.xxx" + "

Xxxx xxx xxx xxxxxxx xxxxxxxéxx x'xxxêxxxx à xxxxx, xxx xx x" + "xxxé xx oooxooo xxxxx xxxxx xxxx... xxxx x'xxx xxxxxxxxxxxx xxxxx xxx xxxxxxxx xx \"xx xxx" + "xx xxx xxx xxxxxxx xxxxxxx xxxxxxxxxxxxxx xxxx xxxxx xxxxxx xx xx xxxx xx x'xxxxxx\". Xx " + "xxxx-êxxx xxx xx xxxxxxxx xx xxxx \"x'xxxêxx à xxxxx xx oooxooo xxxx xxx xéxxxxxxxx, xxxx" + "\"...

Xxxxx xxxxxx'xx xxx x xxxx xxxxxxx xxxxx xx xxèx xxxxxxxxx " + "xxxxxxxxxxxxxxxx à xx xxx x'xx xx xêxx (éxxxxxxxxx xxxx-xx-xxxxxxxx): https://xxxxxxxxxxxxxxxx.xxx/xx-xxxxxxx/xxxxxxx/Xxxxx-xxxx-xxx-xxxxxxxxxx-xx" + "xxx.xxx

...x'x xxxxx xx xxxxxx x'xxxxxx xéxxxxxxx, " + "xx xxx xxxx xxxxxx x'xxxxxxxxxxx xxxxxx, xxxx https://xxxx" + "xxxxxxxxxxxx.xxx/xxxxxxxx-xxxxxxx-xxxx-xxx-o/ xxxxx xxx https://xxxxxxxxxxxxxxxx.xxx/xxxxxxxx-xxxxxxx-xxxx-xxx-o/ ...
" + "" + "", + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "
On Today, User wrote:
" + "
> Xxxxx xx xxxxxxxxx xx xxxxxxx xx xxxxx xxxx " + "xxxx xx xxx xxx xxxx xxx
> xxxçx xôxé \"xxxxx xxxx xxxxxxx xxx\" xx xxxx " + "xxxxé xxx xxx xxxéx xxx
> x'x xéxxxxé x'xxxxxxxxx xx xxx \"Xxxx XXX Xxxxxx " + "Xxx\". Xx xxxx
> xxxxxxxx xxx xxxxxxxxxxxxxxxx.xx (xxxxxxx xxxxxxxxxx xx .xx" + "x). Xxxx
> êxxx xxx xxxxxxxxxxx xxxéxxxxxxxx, xxxx xxxxx xx XXX xx xéxxx à " + "xx
> xxx \"xxx xxxxxx xxxx xx xxxxxxx\" xx xxxx xx xxxxx xxxxxxxx xxxxxxxx" + "
> xx $ xx xxxx x'xxxxxx.
>
" + "
> Xxxx xx xéxxxxxxx, xxxxxxxx xxxxxxx (!), " + "xxxxxxx à xxx, xxxx ooo$ XXX
> xxxxé: https://xxxxxxxxxxxxxxxx.xx/xxx" + "xxxx/xxxxx-xxxx-xxxxxxxx-xxxxx-
> xxxx-xxx-xxxxxxxx-xxx/ xx xx xxxx " + "xéxéxxxxxxx x'xxxxxx xxxx xx xxxxxx
> xx xxxxxxxxxxxx xx xxx (xxxxx " + "Xxxxxx) xxxx xxxx x'xxxxxxx xx xxxxxx:
> https://xxxxxxxxxxxxxxxx.xx" + "x/xx-xxxxxxx/xxxxxxx/Xxxxxxxxxxxx-Xxxxx-Xx
> xx-XXX-Xxxxxx-Xxx.xxx
" + "
>
> Xx" + "xx xxx xxx xxxxxxx xxxxxxxéxx x'xxxêxxxx à xxxxx, xxx xx xxxxé xx
> oooxo" + "oo xxxxx xxxxx xxxx... xxxx x'xxx xxxxxxxxxxxx xxxxx xxx
> xxxxxxxx xx \"" + "xx xxxxx xxx xxx xxxxxxx xxxxxxx xxxxxxxxxxxxxx xxxx
> xxxxx xxxxxx xx xx " + "xxxx xx x'xxxxxx\". Xx xxxx-êxxx xxx xx xxxxxxxx xx
> xxxx \"x'xxxêxx à " + "xxxxx xx oooxooo xxxx xxx xéxxxxxxxx, xxxx\"...
" + ">
> Xxxxx xxxxxx'xx xxx x xxxx xxxxxxx " + "xxxxx xx xxèx xxxxxxxxx
>
> xxxxxxxxxxxxxxxx à xx xxx x'xx xx xêxx " + "(éxxxxxxxxx xxxx-xx-xxxxxxxx): https://xxxxxxxxxxxxxxxx.xxx/xx-xxx" + "xxxx/xxxxxxx/Xxxxx-xxxx-xxx-
> xxxxxxxxxx-xxxxx.xxx
>
> ...x'x " + "xxxxx xx xxxxxx x'xxxxxx xéxxxxxxx, xx xxx xxxx xxxxxx
> x'xxxxxxxxxxx " + "xxxxxx, xxxx https://xxxxxxxxxxxxxxxx.xxx/xxxxxxxx-xxxx
> xxx-xxxx-xxx-o/ " + "xxxxx xxx https://xxxxxxxxxxxxxxxx.xxx/xxxxxxxx-xxxxx
> xx-xxxx-xxx-o/ ...
" HTML_SUFFIX, + "On Today, User wrote:\n" + "> Xxxxx xx xxxxxxxxx xx xxxxxxx xx xxxxx xxxx xxxx xx xxx xxx xxxx xxx\n" + "> xxxçx xôxé \"xxxxx xxxx xxxxxxx xxx\" xx xxxx xxxxé xxx xxx xxxéx xxx\n" + "> x'x xéxxxxé x'xxxxxxxxx xx xxx \"Xxxx XXX Xxxxxx Xxx\". Xx xxxx\n" + "> xxxxxxxx xxx xxxxxxxxxxxxxxxx.xx (xxxxxxx xxxxxxxxxx xx .xxx). Xxxx\n" + "> êxxx xxx xxxxxxxxxxx xxxéxxxxxxxx, xxxx xxxxx xx XXX xx xéxxx à xx\n" + "> xxx \"xxx xxxxxx xxxx xx xxxxxxx\" xx xxxx xx xxxxx xxxxxxxx xxxxxxxx\n" + "> xx $ xx xxxx x'xxxxxx.\n" + "> \n" + "> Xxxx xx xéxxxxxxx, xxxxxxxx xxxxxxx (!), xxxxxxx à xxx, xxxx ooo$ XXX\n" + "> xxxxé: https://xxxxxxxxxxxxxxxx.xx/xxxxxxx/xxxxx-xxxx-xxxxxxxx-xxxxx-\n" + "> xxxx-xxx-xxxxxxxx-xxx/ xx xx xxxx xéxéxxxxxxx x'xxxxxx xxxx xx xxxxxx\n" + "> xx xxxxxxxxxxxx xx xxx (xxxxx Xxxxxx) xxxx xxxx x'xxxxxxx xx xxxxxx: \n" + "> https://xxxxxxxxxxxxxxxx.xxx/xx-xxxxxxx/xxxxxxx/Xxxxxxxxxxxx-Xxxxx-Xx\n" + "> xx-XXX-Xxxxxx-Xxx.xxx\n" + "> \n" + "> Xxxx xxx xxx xxxxxxx xxxxxxxéxx x'xxxêxxxx à xxxxx, xxx xx xxxxé xx\n" + "> oooxooo xxxxx xxxxx xxxx... xxxx x'xxx xxxxxxxxxxxx xxxxx xxx\n" + "> xxxxxxxx xx \"xx xxxxx xxx xxx xxxxxxx xxxxxxx xxxxxxxxxxxxxx xxxx\n" + "> xxxxx xxxxxx xx xx xxxx xx x'xxxxxx\". Xx xxxx-êxxx xxx xx xxxxxxxx xx\n" + "> xxxx \"x'xxxêxx à xxxxx xx oooxooo xxxx xxx xéxxxxxxxx, xxxx\"...\n" + "> \n" + "> Xxxxx xxxxxx'xx xxx x xxxx xxxxxxx xxxxx xx xxèx xxxxxxxxx\n" + "> \n" + "> xxxxxxxxxxxxxxxx à xx xxx x'xx xx xêxx (éxxxxxxxxx xxxx-xx-xxxxxxxx): https://xxxxxxxxxxxxxxxx.xxx/xx-xxxxxxx/xxxxxxx/Xxxxx-xxxx-xxx-\n" + "> xxxxxxxxxx-xxxxx.xxx ; \n" + "> \n" + "> ...x'x xxxxx xx xxxxxx x'xxxxxx xéxxxxxxx, xx xxx xxxx xxxxxx\n" + "> x'xxxxxxxxxxx xxxxxx, xxxx https://xxxxxxxxxxxxxxxx.xxx/xxxxxxxx-xxxx\n" + "> xxx-xxxx-xxx-o/ xxxxx xxx https://xxxxxxxxxxxxxxxx.xxx/xxxxxxxx-xxxxx\n" + "> xx-xxxx-xxx-o/ ...")) + g_test_fail (); +} + +static void +test_bug_750636 (TestFixture *fixture) +{ + test_utils_fixture_change_setting_int32 (fixture, "org.gnome.evolution.mail", "composer-word-wrap-length", 71); + + if (!test_utils_run_simple_test (fixture, + "mode:plain\n" + "type:" + "12345678901234567890123456789012345678901234567890123456789012345678901" + "12345678901234567890123456789012345678901234567890123456789012345678901A\\n\\n" + "1234567890123456789012345678901234567890123456789012345678901234567890 B\\n\\n" + "12345678901234567890123456789012345678901234567890123456789012345678901 C\\n\\n" + "1234567890123456789012345678901234567890123456789012345678901234567890 D\\n\\n" + "12345678901234567890123456789012345678901234567890123456789012345678901" UNICODE_NBSP UNICODE_NBSP UNICODE_NBSP "E\\n\\n" + "1234567890123456789012345678901234567890123456789012345678901234567890" UNICODE_NBSP UNICODE_NBSP UNICODE_NBSP "F\\n\\n" + " 1\\n" + " 2\\n" + " 3\\n" + "\n", + HTML_PREFIX "
" + "12345678901234567890123456789012345678901234567890123456789012345678901" + "12345678901234567890123456789012345678901234567890123456789012345678901A
" + "

" + "1234567890123456789012345678901234567890123456789012345678901234567890 B
" + "

" + "12345678901234567890123456789012345678901234567890123456789012345678901 C
" + "

" + "1234567890123456789012345678901234567890123456789012345678901234567890 D
" + "

" + "12345678901234567890123456789012345678901234567890123456789012345678901   E
" + "

" + "1234567890123456789012345678901234567890123456789012345678901234567890   F
" + "

" + "
1
" + "
2
" + "
3
" + "

" + HTML_SUFFIX, + "12345678901234567890123456789012345678901234567890123456789012345678901\n" + "12345678901234567890123456789012345678901234567890123456789012345678901\n" + "A\n\n" + "1234567890123456789012345678901234567890123456789012345678901234567890\n" + "B\n\n" + "12345678901234567890123456789012345678901234567890123456789012345678901\n" + "C\n\n" + "1234567890123456789012345678901234567890123456789012345678901234567890 \n" + "D\n\n" + "12345678901234567890123456789012345678901234567890123456789012345678901\n" + " E\n\n" + "1234567890123456789012345678901234567890123456789012345678901234567890 \n" + " F\n\n" + " 1\n" + " 2\n" + " 3\n")) + g_test_fail (); +} + +static void +test_issue_86 (TestFixture *fixture) +{ + const gchar *source_text = + "normal text\n" + "\n" + "> level 1\n" + "> level 1\n" + "> > level 2\n" + "> > level 2\n" + "> >\n" + "> > level 2\n" + ">\n" + "> level 1\n" + "> level 1\n" + ">\n" + "> > > level 3\n" + "> > > level 3\n" + ">\n" + "> > level 2\n" + "> > level 2\n" + ">\n" + "> level 1\n" + "\n" + "back normal text\n"; + gchar *converted, *to_insert; + + if (!test_utils_process_commands (fixture, + "mode:html\n")) { + g_test_fail (); + return; + } + + converted = camel_text_to_html (source_text, + CAMEL_MIME_FILTER_TOHTML_PRE | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | + CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES | + CAMEL_MIME_FILTER_TOHTML_QUOTE_CITATION, + 0xDDDDDD); + + g_return_if_fail (converted != NULL); + + to_insert = g_strconcat (converted, + "" + "", + NULL); + + test_utils_insert_content (fixture, to_insert, + E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_HTML); + + if (!test_utils_run_simple_test (fixture, + "", + HTML_PREFIX "
On Today, User wrote:
" + "
" + "
normal text
" + "

" + "
" + "
level 1
" + "
level 1
" + "
" + "
level 2
" + "
level 2
" + "

" + "
level 2
" + "
" + "

" + "
level 1
" + "
level 1
" + "

" + "
" + "
" + "
level 3
" + "
level 3
" + "
" + "
" + "

" + "
" + "
level 2
" + "
level 2
" + "
" + "

" + "
level 1
" + "
" + "

" + "
back normal text
" + "
" HTML_SUFFIX, + "On Today, User wrote:\n" + "> normal text\n" + "> \n" + "> > level 1\n" + "> > level 1\n" + "> > > level 2\n" + "> > > level 2\n" + "> > > \n" + "> > > level 2\n" + "> > \n" + "> > level 1\n" + "> > level 1\n" + "> > \n" + "> > > > level 3\n" + "> > > > level 3\n" + "> > \n" + "> > > level 2\n" + "> > > level 2\n" + "> > \n" + "> > level 1\n" + "> \n" + "> back normal text")) + g_test_fail (); + + g_free (to_insert); + g_free (converted); +} + +void +test_add_html_editor_bug_tests (void) +{ + test_utils_add_test ("/bug/726548", test_bug_726548); + test_utils_add_test ("/bug/750657", test_bug_750657); + test_utils_add_test ("/bug/760989", test_bug_760989); + test_utils_add_test ("/bug/767903", test_bug_767903); + test_utils_add_test ("/bug/769708", test_bug_769708); + test_utils_add_test ("/bug/769913", test_bug_769913); + test_utils_add_test ("/bug/769955", test_bug_769955); + test_utils_add_test ("/bug/770073", test_bug_770073); + test_utils_add_test ("/bug/770074", test_bug_770074); + test_utils_add_test ("/bug/771044", test_bug_771044); + test_utils_add_test ("/bug/771131", test_bug_771131); + test_utils_add_test ("/bug/771493", test_bug_771493); + test_utils_add_test ("/bug/772171", test_bug_772171); + test_utils_add_test ("/bug/772513", test_bug_772513); + test_utils_add_test ("/bug/772918", test_bug_772918); + test_utils_add_test ("/bug/773164", test_bug_773164); + test_utils_add_test ("/bug/775042", test_bug_775042); + test_utils_add_test ("/bug/775691", test_bug_775691); + test_utils_add_test ("/bug/779707", test_bug_779707); + test_utils_add_test ("/bug/780275/html", test_bug_780275_html); + test_utils_add_test ("/bug/780275/plain", test_bug_780275_plain); + test_utils_add_test ("/bug/781722", test_bug_781722); + test_utils_add_test ("/bug/781116", test_bug_781116); + test_utils_add_test ("/bug/780088", test_bug_780088); + test_utils_add_test ("/bug/788829", test_bug_788829); + test_utils_add_test ("/bug/750636", test_bug_750636); + test_utils_add_test ("/issue/86", test_issue_86); +} diff --git a/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c b/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c index 87bc86e..3568f6e 100644 --- a/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c +++ b/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c @@ -14089,7 +14089,7 @@ wrap_lines (EEditorPage *editor_page, next_sibling = webkit_dom_node_get_next_sibling (node); /* If the anchor doesn't fit on the line, add it to a separate line. */ - if ((line_length + anchor_length) > length_to_wrap) { + if (line_length > 0 && (line_length + anchor_length) > length_to_wrap) { /* Put
before the anchor, thus it starts on a new line */ element = webkit_dom_document_create_element (document, "BR", NULL); element_add_class (element, "-x-evo-wrap-br"); diff --git a/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c.extra-new-line-before-url b/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c.extra-new-line-before-url new file mode 100644 index 0000000..87bc86e --- /dev/null +++ b/src/modules/webkit-editor/web-extension/e-editor-dom-functions.c.extra-new-line-before-url @@ -0,0 +1,18123 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com) + * + * This library is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "evolution-config.h" + +#include + +#include + +#include "web-extensions/e-dom-utils.h" + +#include "e-editor-page.h" +#include "e-editor-undo-redo-manager.h" + +#include "e-editor-dom-functions.h" + +#define HTML_KEY_CODE_BACKSPACE 8 +#define HTML_KEY_CODE_RETURN 13 +#define HTML_KEY_CODE_CONTROL 17 +#define HTML_KEY_CODE_SPACE 32 +#define HTML_KEY_CODE_DELETE 46 +#define HTML_KEY_CODE_TABULATOR 9 + +/* ******************** Tests ******************** */ + +static gchar * +workaround_spaces (const gchar *text) +{ + GString *tmp; + gchar *str = NULL; + + tmp = e_str_replace_string (text, " ", " "); + if (tmp) { + str = g_string_free (tmp, FALSE); + text = str; + } + + tmp = e_str_replace_string (text, " ", " "); + if (tmp) { + g_free (str); + str = g_string_free (tmp, FALSE); + } else if (!str) { + str = g_strdup (text); + } + + return str; +} + +gboolean +e_editor_dom_test_html_equal (WebKitDOMDocument *document, + const gchar *html1, + const gchar *html2) +{ + WebKitDOMElement *elem1, *elem2; + gchar *str1, *str2; + gboolean res = FALSE; + GError *error = NULL; + + g_return_val_if_fail (WEBKIT_DOM_IS_DOCUMENT (document), FALSE); + g_return_val_if_fail (html1 != NULL, FALSE); + g_return_val_if_fail (html2 != NULL, FALSE); + + elem1 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error); + if (error || !elem1) { + g_warning ("%s: Failed to create elem1: %s", G_STRFUNC, error ? error->message : "Unknown error"); + g_clear_error (&error); + return FALSE; + } + + elem2 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error); + if (error || !elem2) { + g_warning ("%s: Failed to create elem2: %s", G_STRFUNC, error ? error->message : "Unknown error"); + g_clear_error (&error); + return FALSE; + } + + /* FIXME WK2: Workaround when   is used instead of regular spaces. (Placed by WebKit?) */ + str1 = workaround_spaces (html1); + str2 = workaround_spaces (html2); + + webkit_dom_element_set_inner_html (elem1, str1, &error); + if (!error) { + webkit_dom_element_set_inner_html (elem2, str2, &error); + + if (!error) { + webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem1)); + webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem2)); + + res = webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (elem1), WEBKIT_DOM_NODE (elem2)); + } else { + g_warning ("%s: Failed to set inner html2: %s", G_STRFUNC, error->message); + } + } else { + g_warning ("%s: Failed to set inner html1: %s", G_STRFUNC, error->message); + } + + if (res && (g_strcmp0 (html1, str1) != 0 || g_strcmp0 (html2, str2) != 0)) + g_warning ("%s: Applied the ' ' workaround", G_STRFUNC); + + g_clear_error (&error); + g_free (str1); + g_free (str2); + + return res; +} + +/* ******************** Actions ******************** */ + +static WebKitDOMElement * +get_table_cell_element (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMElement *cell; + WebKitDOMNode *node_under_mouse_click; + + document = e_editor_page_get_document (editor_page); + cell = webkit_dom_document_get_element_by_id (document, "-x-evo-current-cell"); + + if (cell) + return cell; + + node_under_mouse_click = e_editor_page_get_node_under_mouse_click (editor_page); + + if (node_under_mouse_click && WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node_under_mouse_click)) { + cell = WEBKIT_DOM_ELEMENT (node_under_mouse_click); + } else { + WebKitDOMElement *selection_start; + + e_editor_dom_selection_save (editor_page); + + selection_start = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (selection_start), "TD"); + if (!cell) + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (selection_start), "TH"); + + e_editor_dom_selection_restore (editor_page); + } + + return cell; +} + +static void +prepare_history_for_table (EEditorPage *editor_page, + WebKitDOMElement *table, + EEditorHistoryEvent *ev) +{ + ev->type = HISTORY_TABLE_DIALOG; + + e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y); + + ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error ( + WEBKIT_DOM_NODE (table), TRUE, NULL)); +} + + +static void +save_history_for_table (EEditorPage *editor_page, + WebKitDOMElement *table, + EEditorHistoryEvent *ev) +{ + EEditorUndoRedoManager *manager; + + if (table) + ev->data.dom.to = g_object_ref (webkit_dom_node_clone_node_with_error ( + WEBKIT_DOM_NODE (table), TRUE, NULL)); + else + ev->data.dom.to = NULL; + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + e_editor_undo_redo_manager_insert_history_event (manager, ev); +} + +void +e_editor_dom_delete_cell_contents (EEditorPage *editor_page) +{ + WebKitDOMNode *node; + WebKitDOMElement *cell, *table_cell, *table; + EEditorHistoryEvent *ev = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD"); + if (!cell) + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH"); + g_return_if_fail (cell != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + while ((node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (cell)))) + remove_node (node); + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_delete_column (EEditorPage *editor_page) +{ + WebKitDOMElement *cell, *table, *table_cell; + WebKitDOMHTMLCollection *rows = NULL; + EEditorHistoryEvent *ev = NULL; + gulong index, length, ii; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + /* Find TD in which the selection starts */ + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD"); + if (!cell) + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH"); + g_return_if_fail (cell != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + rows = webkit_dom_html_table_element_get_rows ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table)); + length = webkit_dom_html_collection_get_length (rows); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *row; + + row = webkit_dom_html_collection_item (rows, ii); + + webkit_dom_html_table_row_element_delete_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL); + } + + g_clear_object (&rows); + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_delete_row (EEditorPage *editor_page) +{ + WebKitDOMElement *row, *table, *table_cell; + EEditorHistoryEvent *ev = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR"); + g_return_if_fail (row != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + remove_node (WEBKIT_DOM_NODE (row)); + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_delete_table (EEditorPage *editor_page) +{ + WebKitDOMElement *table, *table_cell; + EEditorHistoryEvent *ev = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + remove_node (WEBKIT_DOM_NODE (table)); + + save_history_for_table (editor_page, NULL, ev); +} + +void +e_editor_dom_insert_column_after (EEditorPage *editor_page) +{ + WebKitDOMElement *cell, *row, *table_cell, *table; + EEditorHistoryEvent *ev = NULL; + gulong index; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD"); + if (!cell) + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH"); + g_return_if_fail (cell != NULL); + + row = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR"); + g_return_if_fail (row != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + /* Get the first row in the table */ + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_first_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)))); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + while (row) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index + 1, NULL); + + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row))); + } + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_insert_column_before (EEditorPage *editor_page) +{ + WebKitDOMElement *cell, *row, *table_cell, *table; + EEditorHistoryEvent *ev = NULL; + gulong index; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD"); + if (!cell) { + cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH"); + } + g_return_if_fail (cell != NULL); + + row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR"); + g_return_if_fail (row != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + /* Get the first row in the table */ + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_first_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)))); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + while (row) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL); + + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row))); + } + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_insert_row_above (EEditorPage *editor_page) +{ + WebKitDOMElement *row, *table, *table_cell; + WebKitDOMHTMLCollection *cells = NULL; + WebKitDOMHTMLElement *new_row; + EEditorHistoryEvent *ev = NULL; + gulong index, cell_count, ii; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR"); + g_return_if_fail (row != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + index = webkit_dom_html_table_row_element_get_row_index ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + + new_row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index, NULL); + + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + cell_count = webkit_dom_html_collection_get_length (cells); + for (ii = 0; ii < cell_count; ii++) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL); + } + + g_clear_object (&cells); + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_insert_row_below (EEditorPage *editor_page) +{ + WebKitDOMElement *row, *table, *table_cell; + WebKitDOMHTMLCollection *cells = NULL; + WebKitDOMHTMLElement *new_row; + EEditorHistoryEvent *ev = NULL; + gulong index, cell_count, ii; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + table_cell = get_table_cell_element (editor_page); + g_return_if_fail (table_cell != NULL); + + row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR"); + g_return_if_fail (row != NULL); + + table = dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE"); + g_return_if_fail (table != NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + prepare_history_for_table (editor_page, table, ev); + + index = webkit_dom_html_table_row_element_get_row_index ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + + new_row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index + 1, NULL); + + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + cell_count = webkit_dom_html_collection_get_length (cells); + for (ii = 0; ii < cell_count; ii++) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL); + } + + g_clear_object (&cells); + + save_history_for_table (editor_page, table, ev); +} + +void +e_editor_dom_save_history_for_cut (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDocumentFragment *fragment; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMRange *range = NULL; + EEditorHistoryEvent *ev; + EEditorUndoRedoManager *manager; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + + if (!webkit_dom_dom_selection_get_range_count (dom_selection) || + webkit_dom_dom_selection_get_is_collapsed (dom_selection)) { + g_clear_object (&dom_selection); + return; + } + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_DELETE; + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + + ev->after.start.x = ev->before.start.x; + ev->after.start.y = ev->before.start.y; + ev->after.end.x = ev->before.start.x; + ev->after.end.y = ev->before.start.y; + + /* Save the fragment. */ + fragment = webkit_dom_range_clone_contents (range, NULL); + g_clear_object (&dom_selection); + g_clear_object (&range); + ev->data.fragment = g_object_ref (fragment); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + e_editor_undo_redo_manager_insert_history_event (manager, ev); + e_editor_page_set_dont_save_history_in_body_input (editor_page, TRUE); +} + +/* ******************** View ******************** */ + +/* + * e_editor_dom_exec_command: + * @document: a #WebKitDOMDocument + * @command: an #EContentEditorCommand to execute + * @value: value of the command (or @NULL if the command does not require value) + * + * The function will fail when @value is @NULL or empty but the current @command + * requires a value to be passed. The @value is ignored when the @command does + * not expect any value. + * + * Returns: @TRUE when the command was succesfully executed, @FALSE otherwise. + */ +gboolean +e_editor_dom_exec_command (EEditorPage *editor_page, + EContentEditorCommand command, + const gchar *value) +{ + const gchar *cmd_str = 0; + gboolean has_value = FALSE; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + +#define CHECK_COMMAND(cmd,str,val) case cmd:\ + if (val) {\ + g_return_val_if_fail (value && *value, FALSE);\ + }\ + has_value = val; \ + cmd_str = str;\ + break; + + switch (command) { + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_BOLD, "Bold", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_COPY, "Copy", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_CREATE_LINK, "CreateLink", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_CUT, "Cut", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_DELETE, "Delete", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FIND_STRING, "FindString", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_NAME, "FontName", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_SIZE, "FontSize", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORE_COLOR, "ForeColor", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_HILITE_COLOR, "HiliteColor", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INDENT, "Indent", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_HTML, "InsertHTML", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_IMAGE, "InsertImage", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, "InsertText", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_ITALIC, "Italic", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_OUTDENT, "Outdent", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE, "Paste", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PRINT, "Print", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_REDO, "Redo", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SELECT_ALL, "SelectAll", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SUBSCRIPT, "Subscript", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SUPERSCRIPT, "Superscript", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_TRANSPOSE, "Transpose", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNDERLINE, "Underline", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNDO, "Undo", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNLINK, "Unlink", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNSELECT, "Unselect", FALSE) + CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_USE_CSS, "UseCSS", TRUE) + } + + e_editor_page_set_dont_save_history_in_body_input (editor_page, TRUE); + + return webkit_dom_document_exec_command ( + e_editor_page_get_document (editor_page), cmd_str, FALSE, has_value ? value : "" ); +} + +static void +perform_spell_check (WebKitDOMDOMSelection *dom_selection, + WebKitDOMRange *start_range, + WebKitDOMRange *end_range) +{ + WebKitDOMRange *actual = start_range; + + /* FIXME WK2: this doesn't work, the cursor is moved, but the spellcheck is not updated */ + /* Go through all words to spellcheck them. To avoid this we have to wait for + * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */ + /* We are moving forward word by word until we hit the text on the end. */ + while (actual && webkit_dom_range_compare_boundary_points (actual, WEBKIT_DOM_RANGE_START_TO_START, end_range, NULL) < 0) { + if (actual != start_range) + g_object_unref (actual); + webkit_dom_dom_selection_modify ( + dom_selection, "move", "forward", "word"); + actual = webkit_dom_dom_selection_get_range_at ( + dom_selection, 0, NULL); + } + g_clear_object (&actual); +} + +void +e_editor_dom_force_spell_check_for_current_paragraph (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMElement *parent; + WebKitDOMHTMLElement *body; + WebKitDOMRange *end_range = NULL, *actual = NULL; + WebKitDOMText *text; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + + if (!e_editor_page_get_inline_spelling_enabled (editor_page)) + return; + + document = e_editor_page_get_document (editor_page); + body = webkit_dom_document_get_body (document); + + if (!body) + return; + + if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) + return; + + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + if (!selection_start_marker || !selection_end_marker) + return; + + /* Block callbacks of selection-changed signal as we don't want to + * recount all the block format things in EEditorSelection and here as well + * when we are moving with caret */ + e_editor_page_block_selection_changed (editor_page); + + parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_end_marker)); + if (!parent) + parent = WEBKIT_DOM_ELEMENT (body); + + /* Append some text on the end of the element */ + text = webkit_dom_document_create_text_node (document, "-x-evo-end"); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (text), + NULL); + + parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)); + if (!parent) + parent = WEBKIT_DOM_ELEMENT (body); + + /* Create range that's pointing on the end of this text */ + end_range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + end_range, WEBKIT_DOM_NODE (text), NULL); + webkit_dom_range_collapse (end_range, FALSE, NULL); + + /* Move on the beginning of the paragraph */ + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + + actual = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + actual, WEBKIT_DOM_NODE (parent), NULL); + webkit_dom_range_collapse (actual, TRUE, NULL); + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, actual); + g_clear_object (&actual); + + actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + perform_spell_check (dom_selection, actual, end_range); + + g_clear_object (&dom_selection); + g_clear_object (&dom_window); + g_clear_object (&end_range); + g_clear_object (&actual); + + /* Remove the text that we inserted on the end of the paragraph */ + remove_node (WEBKIT_DOM_NODE (text)); + + e_editor_dom_selection_restore (editor_page); + /* Unblock the callbacks */ + e_editor_page_unblock_selection_changed (editor_page); +} + +static void +refresh_spell_check (EEditorPage *editor_page, + gboolean enable_spell_check) +{ + WebKitDOMDocument *document; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMHTMLElement *body; + WebKitDOMRange *end_range = NULL, *actual = NULL; + WebKitDOMText *text; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + body = webkit_dom_document_get_body (document); + + if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) + return; + + /* Enable/Disable spellcheck in composer */ + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), + "spellcheck", + enable_spell_check ? "true" : "false", + NULL); + + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + /* Sometimes the web view is not focused, so we have to save the selection + * manually into the body */ + if (!selection_start_marker || !selection_end_marker) { + WebKitDOMNode *child; + + child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + if (!WEBKIT_DOM_IS_ELEMENT (child)) + return; + + dom_add_selection_markers_into_element_start ( + document, + WEBKIT_DOM_ELEMENT (child), + &selection_start_marker, + &selection_end_marker); + } + + /* Block callbacks of selection-changed signal as we don't want to + * recount all the block format things in EEditorSelection and here as well + * when we are moving with caret */ + e_editor_page_block_selection_changed (editor_page); + + /* Append some text on the end of the body */ + text = webkit_dom_document_create_text_node (document, "-x-evo-end"); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL); + + /* Create range that's pointing on the end of this text */ + end_range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + end_range, WEBKIT_DOM_NODE (text), NULL); + webkit_dom_range_collapse (end_range, FALSE, NULL); + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + + /* Move on the beginning of the document */ + webkit_dom_dom_selection_modify ( + dom_selection, "move", "backward", "documentboundary"); + + actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + perform_spell_check (dom_selection, actual, end_range); + + g_clear_object (&dom_selection); + g_clear_object (&dom_window); + g_clear_object (&end_range); + g_clear_object (&actual); + + /* Remove the text that we inserted on the end of the body */ + remove_node (WEBKIT_DOM_NODE (text)); + + e_editor_dom_selection_restore (editor_page); + /* Unblock the callbacks */ + e_editor_page_unblock_selection_changed (editor_page); +} + +void +e_editor_dom_turn_spell_check_off (EEditorPage *editor_page) +{ + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + refresh_spell_check (editor_page, FALSE); +} + +void +e_editor_dom_force_spell_check_in_viewport (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMElement *last_element; + WebKitDOMHTMLElement *body; + WebKitDOMRange *end_range = NULL, *actual = NULL; + WebKitDOMText *text; + glong viewport_height; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!e_editor_page_get_inline_spelling_enabled (editor_page)) + return; + + document = e_editor_page_get_document (editor_page); + body = webkit_dom_document_get_body (document); + + if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) + return; + + e_editor_dom_selection_save (editor_page); + + /* Block callbacks of selection-changed signal as we don't want to + * recount all the block format things in EEditorSelection and here as well + * when we are moving with caret */ + e_editor_page_block_selection_changed (editor_page); + + /* We have to add 10 px offset as otherwise just the HTML element will be returned */ + actual = webkit_dom_document_caret_range_from_point (document, 10, 10); + if (!actual) + goto out; + + /* Append some text on the end of the body */ + text = webkit_dom_document_create_text_node (document, "-x-evo-end"); + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + + /* We have to add 10 px offset as otherwise just the HTML element will be returned */ + viewport_height = webkit_dom_dom_window_get_inner_height (dom_window); + last_element = webkit_dom_document_element_from_point (document, 10, viewport_height - 10); + if (last_element && !WEBKIT_DOM_IS_HTML_HTML_ELEMENT (last_element) && + !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (last_element)) { + WebKitDOMElement *parent; + + parent = get_parent_block_element (WEBKIT_DOM_NODE (last_element)); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (parent ? parent : last_element), WEBKIT_DOM_NODE (text), NULL); + } else + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL); + + /* Create range that's pointing on the end of viewport */ + end_range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + end_range, WEBKIT_DOM_NODE (text), NULL); + webkit_dom_range_collapse (end_range, FALSE, NULL); + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, actual); + perform_spell_check (dom_selection, actual, end_range); + + g_clear_object (&dom_selection); + g_clear_object (&dom_window); + g_clear_object (&end_range); + g_clear_object (&actual); + + /* Remove the text that we inserted on the end of the body */ + remove_node (WEBKIT_DOM_NODE (text)); + + out: + e_editor_dom_selection_restore (editor_page); + /* Unblock the callbacks */ + e_editor_page_unblock_selection_changed (editor_page); +} + +void +e_editor_dom_force_spell_check (EEditorPage *editor_page) +{ + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (e_editor_page_get_inline_spelling_enabled (editor_page)) + refresh_spell_check (editor_page, TRUE); +} + +gboolean +e_editor_dom_node_is_citation_node (WebKitDOMNode *node) +{ + gboolean ret_val = FALSE; + gchar *value; + + if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) + return FALSE; + + /* citation ==
*/ + if ((value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type"))) + ret_val = g_strcmp0 (value, "cite") == 0; + + g_free (value); + + return ret_val; +} + +gint +e_editor_dom_get_citation_level (WebKitDOMNode *node) +{ + WebKitDOMNode *parent = node; + gint level = 0; + + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) && + webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) + level++; + + parent = webkit_dom_node_get_parent_node (parent); + } + + return level; +} + +static gchar * +get_quotation_for_level (gint quote_level) +{ + const gchar *quote_element = "" QUOTE_SYMBOL " "; + gint ii; + GString *output = g_string_new (""); + + for (ii = 0; ii < quote_level; ii++) + g_string_append (output, quote_element); + + return g_string_free (output, FALSE); +} + +void +e_editor_dom_quote_plain_text_element_after_wrapping (EEditorPage *editor_page, + WebKitDOMElement *element, + gint quote_level) +{ + WebKitDOMDocument *document; + WebKitDOMNodeList *list = NULL; + WebKitDOMNode *quoted_node; + gint ii; + gchar *quotation; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + g_return_if_fail (element != NULL); + + document = e_editor_page_get_document (editor_page); + + quoted_node = WEBKIT_DOM_NODE ( + webkit_dom_document_create_element (document, "SPAN", NULL)); + webkit_dom_element_set_class_name ( + WEBKIT_DOM_ELEMENT (quoted_node), "-x-evo-quoted"); + quotation = get_quotation_for_level (quote_level); + webkit_dom_element_set_inner_html ( + WEBKIT_DOM_ELEMENT (quoted_node), quotation, NULL); + + list = webkit_dom_element_query_selector_all ( + element, "br.-x-evo-wrap-br, pre > br", NULL); + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (element), + quoted_node, + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)), + NULL); + + for (ii = webkit_dom_node_list_get_length (list); ii--;) { + WebKitDOMNode *br = webkit_dom_node_list_item (list, ii); + WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (br); + + if ((!WEBKIT_DOM_IS_ELEMENT (prev_sibling) || + !element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted")) && + webkit_dom_node_get_next_sibling (br)) { + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (br), + webkit_dom_node_clone_node_with_error (quoted_node, TRUE, NULL), + webkit_dom_node_get_next_sibling (br), + NULL); + } + } + + g_clear_object (&list); + g_free (quotation); +} + +static gboolean +return_pressed_in_empty_line (EEditorPage *editor_page) +{ + WebKitDOMNode *node; + WebKitDOMRange *range = NULL; + + range = e_editor_dom_get_current_range (editor_page); + if (!range) + return FALSE; + + node = webkit_dom_range_get_start_container (range, NULL); + if (!WEBKIT_DOM_IS_TEXT (node)) { + WebKitDOMNode *first_child; + + first_child = webkit_dom_node_get_first_child (node); + if (first_child && WEBKIT_DOM_IS_ELEMENT (first_child) && + element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted")) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling (node); + if (!prev_sibling) { + gboolean collapsed; + + collapsed = webkit_dom_range_get_collapsed (range, NULL); + g_clear_object (&range); + return collapsed; + } + } + } + + g_clear_object (&range); + + return FALSE; +} + +WebKitDOMNode * +e_editor_dom_get_parent_block_node_from_child (WebKitDOMNode *node) +{ + WebKitDOMNode *parent = node; + + if (!WEBKIT_DOM_IS_ELEMENT (parent) || + e_editor_dom_is_selection_position_node (parent)) + parent = webkit_dom_node_get_parent_node (parent); + + if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") || + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character") || + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-signature") || + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper") || + WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) || + element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") || + element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") || + element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u")) + parent = webkit_dom_node_get_parent_node (parent); + + if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") || + element_has_class (WEBKIT_DOM_ELEMENT (parent), "Apple-tab-span") || + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper")) + parent = webkit_dom_node_get_parent_node (parent); + + return parent; +} + +gboolean +e_editor_dom_node_is_paragraph (WebKitDOMNode *node) +{ + if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node)) + return FALSE; + + return webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-evo-paragraph"); +} + +WebKitDOMElement * +e_editor_dom_wrap_and_quote_element (EEditorPage *editor_page, + WebKitDOMElement *element) +{ + gint citation_level; + WebKitDOMElement *tmp_element = element; + + g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (element), element); + + if (e_editor_page_get_html_mode (editor_page)) + return element; + + citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element)); + + e_editor_dom_remove_quoting_from_element (element); + e_editor_dom_remove_wrapping_from_element (element); + + if (e_editor_dom_node_is_paragraph (WEBKIT_DOM_NODE (element))) { + gint word_wrap_length, length; + + word_wrap_length = e_editor_page_get_word_wrap_length (editor_page); + length = word_wrap_length - 2 * citation_level; + tmp_element = e_editor_dom_wrap_paragraph_length ( + editor_page, element, length); + } + + if (citation_level > 0) { + webkit_dom_node_normalize (WEBKIT_DOM_NODE (tmp_element)); + e_editor_dom_quote_plain_text_element_after_wrapping ( + editor_page, tmp_element, citation_level); + } + + return tmp_element; +} + +WebKitDOMElement * +e_editor_dom_insert_new_line_into_citation (EEditorPage *editor_page, + const gchar *html_to_insert) +{ + WebKitDOMDocument *document; + WebKitDOMElement *element, *paragraph = NULL; + WebKitDOMNode *last_block; + gboolean html_mode = FALSE, ret_val, avoid_editor_call; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL); + + document = e_editor_page_get_document (editor_page); + html_mode = e_editor_page_get_html_mode (editor_page); + + avoid_editor_call = return_pressed_in_empty_line (editor_page); + + if (avoid_editor_call) { + WebKitDOMElement *selection_start_marker; + WebKitDOMNode *current_block, *parent, *parent_block, *block_clone; + + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + + current_block = e_editor_dom_get_parent_block_node_from_child ( + WEBKIT_DOM_NODE (selection_start_marker)); + + block_clone = webkit_dom_node_clone_node_with_error (current_block, TRUE, NULL); + /* Find selection start marker and restore it after the new line + * is inserted */ + selection_start_marker = webkit_dom_element_query_selector ( + WEBKIT_DOM_ELEMENT (block_clone), "#-x-evo-selection-start-marker", NULL); + + /* Find parent node that is immediate child of the BODY */ + /* Build the same structure of parent nodes of the current block */ + parent_block = current_block; + parent = webkit_dom_node_get_parent_node (parent_block); + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + WebKitDOMNode *node; + + parent_block = parent; + node = webkit_dom_node_clone_node_with_error (parent_block, FALSE, NULL); + webkit_dom_node_append_child (node, block_clone, NULL); + block_clone = node; + parent = webkit_dom_node_get_parent_node (parent_block); + } + + paragraph = e_editor_dom_get_paragraph_element (editor_page, -1, 0); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (paragraph), + WEBKIT_DOM_NODE ( + webkit_dom_document_create_element (document, "BR", NULL)), + NULL); + + /* Insert the selection markers to right place */ + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (paragraph), + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker)), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)), + NULL); + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (paragraph), + WEBKIT_DOM_NODE (selection_start_marker), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)), + NULL); + + /* Insert the cloned nodes before the BODY parent node */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent_block), + block_clone, + parent_block, + NULL); + + /* Insert the new empty paragraph before the BODY parent node */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent_block), + WEBKIT_DOM_NODE (paragraph), + parent_block, + NULL); + + /* Remove the old block (its copy was moved to the right place) */ + remove_node (current_block); + + e_editor_dom_selection_restore (editor_page); + + return NULL; + } else { + e_editor_dom_remove_input_event_listener_from_body (editor_page); + e_editor_page_block_selection_changed (editor_page); + + ret_val = e_editor_dom_exec_command ( + editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL); + + e_editor_page_unblock_selection_changed (editor_page); + e_editor_dom_register_input_event_listener_on_body (editor_page); + + if (!ret_val) + return NULL; + + element = webkit_dom_document_query_selector ( + document, "body>br", NULL); + + if (!element) + return NULL; + } + + last_block = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)); + while (last_block && e_editor_dom_node_is_citation_node (last_block)) + last_block = webkit_dom_node_get_last_child (last_block); + + if (last_block) { + WebKitDOMNode *last_child; + + if ((last_child = webkit_dom_node_get_last_child (last_block))) { + if (WEBKIT_DOM_IS_ELEMENT (last_child) && + element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-quoted")) + webkit_dom_node_append_child ( + last_block, + WEBKIT_DOM_NODE ( + webkit_dom_document_create_element ( + document, "br", NULL)), + NULL); + } + } + + if (!html_mode) { + WebKitDOMNode *sibling; + + sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)); + + if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (sibling)) { + WebKitDOMNode *node; + + node = webkit_dom_node_get_first_child (sibling); + while (node && e_editor_dom_node_is_citation_node (node)) + node = webkit_dom_node_get_first_child (node); + + /* Rewrap and requote nodes that were created by split. */ + if (WEBKIT_DOM_IS_ELEMENT (node)) + e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (node)); + + if (WEBKIT_DOM_IS_ELEMENT (last_block)) + e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (last_block)); + + e_editor_dom_force_spell_check_in_viewport (editor_page); + } + } + + if (html_to_insert && *html_to_insert) { + paragraph = e_editor_dom_prepare_paragraph (editor_page, FALSE); + webkit_dom_element_set_inner_html ( + paragraph, html_to_insert, NULL); + if (!webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-start-marker", NULL)) + dom_add_selection_markers_into_element_end ( + document, paragraph, NULL, NULL); + } else + paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + WEBKIT_DOM_NODE (paragraph), + WEBKIT_DOM_NODE (element), + NULL); + + remove_node (WEBKIT_DOM_NODE (element)); + + e_editor_dom_selection_restore (editor_page); + + return paragraph; +} + +/* For purpose of this function see e-mail-formatter-quote.c */ +static void +put_body_in_citation (WebKitDOMDocument *document) +{ + WebKitDOMElement *cite_body = webkit_dom_document_query_selector ( + document, "span.-x-evo-cite-body", NULL); + + if (cite_body) { + WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document); + WebKitDOMNode *citation; + WebKitDOMNode *sibling; + + citation = WEBKIT_DOM_NODE ( + webkit_dom_document_create_element (document, "blockquote", NULL)); + webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (citation), "-x-evo-main-cite"); + webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (citation), "type", "cite", NULL); + + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + citation, + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), + NULL); + + while ((sibling = webkit_dom_node_get_next_sibling (citation))) + webkit_dom_node_append_child (citation, sibling, NULL); + + remove_node (WEBKIT_DOM_NODE (cite_body)); + } +} + +/* For purpose of this function see e-mail-formatter-quote.c */ +static void +move_elements_to_body (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + WebKitDOMNodeList *list = NULL; + gint ii, jj; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + body = webkit_dom_document_get_body (document); + list = webkit_dom_document_query_selector_all ( + document, "div[data-headers]", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (node), "data-headers"); + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + node, + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (body)), + NULL); + + } + g_clear_object (&list); + + list = webkit_dom_document_query_selector_all ( + document, "span.-x-evo-to-body[data-credits]", NULL); + e_editor_page_set_allow_top_signature (editor_page, webkit_dom_node_list_get_length (list) > 0); + for (jj = 0, ii = webkit_dom_node_list_get_length (list); ii--; jj++) { + char *credits; + WebKitDOMElement *element; + WebKitDOMNode *node = webkit_dom_node_list_item (list, jj); + + element = e_editor_dom_get_paragraph_element (editor_page, -1, 0); + credits = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "data-credits"); + if (credits) + webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (element), credits, NULL); + g_free (credits); + + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (body)), + NULL); + + remove_node (node); + } + g_clear_object (&list); +} + +static void +repair_blockquotes (WebKitDOMDocument *document) +{ + WebKitDOMHTMLCollection *collection; + gulong ii; + + collection = webkit_dom_document_get_elements_by_class_name_as_html_collection ( + document, "gmail_quote"); + for (ii = webkit_dom_html_collection_get_length (collection); ii--;) { + WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii); + + if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) + continue; + + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class"); + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style"); + webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL); + + if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (webkit_dom_node_get_last_child (node)) && + webkit_dom_node_get_next_sibling (node)) + webkit_dom_node_append_child ( + node, + WEBKIT_DOM_NODE ( + webkit_dom_document_create_element ( + document, "br", NULL)), + NULL); + } + g_clear_object (&collection); + + collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "blockquote"); + for (ii = webkit_dom_html_collection_get_length (collection); ii--;) { + WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii); + + if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) + continue; + + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class"); + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style"); + webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL); + } + g_clear_object (&collection); +} + +static void +style_blockquotes (WebKitDOMElement *element) +{ + WebKitDOMNodeList *list; + gulong ii; + + g_return_if_fail (WEBKIT_DOM_IS_ELEMENT (element)); + + list = webkit_dom_element_query_selector_all (element, "blockquote", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) + continue; + + webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "style", E_EVOLUTION_BLOCKQUOTE_STYLE, NULL); + } + g_clear_object (&list); +} + +static void +remove_thunderbird_signature (WebKitDOMDocument *document) +{ + WebKitDOMElement *signature; + + signature = webkit_dom_document_query_selector ( + document, "pre.moz-signature", NULL); + if (signature) + remove_node (WEBKIT_DOM_NODE (signature)); +} + +void +e_editor_dom_check_magic_links (EEditorPage *editor_page, + gboolean include_space_by_user) +{ + WebKitDOMDocument *document; + WebKitDOMNode *node; + WebKitDOMRange *range = NULL; + gchar *node_text; + gchar **urls; + gboolean include_space = FALSE; + gboolean is_email_address = FALSE; + gboolean return_key_pressed; + GRegex *regex = NULL; + GMatchInfo *match_info; + gint start_pos_url, end_pos_url; + gboolean has_selection; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!e_editor_page_get_magic_links_enabled (editor_page)) + return; + + return_key_pressed = e_editor_page_get_return_key_pressed (editor_page); + document = e_editor_page_get_document (editor_page); + + if (include_space_by_user) + include_space = TRUE; + else + include_space = e_editor_page_get_space_key_pressed (editor_page); + + range = e_editor_dom_get_current_range (editor_page); + node = webkit_dom_range_get_end_container (range, NULL); + has_selection = !webkit_dom_range_get_collapsed (range, NULL); + g_clear_object (&range); + + if (return_key_pressed) { + WebKitDOMNode* block; + + block = e_editor_dom_get_parent_block_node_from_child (node); + /* Get previous block */ + if (!(block = webkit_dom_node_get_previous_sibling (block))) + return; + + /* If block is quoted content, get the last block there */ + while (block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (block)) + block = webkit_dom_node_get_last_child (block); + + /* Get the last non-empty node */ + node = webkit_dom_node_get_last_child (block); + if (WEBKIT_DOM_IS_CHARACTER_DATA (node) && + webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)) == 0) + node = webkit_dom_node_get_previous_sibling (node); + } else { + e_editor_dom_selection_save (editor_page); + if (has_selection) { + WebKitDOMElement *selection_end_marker; + + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + node = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (selection_end_marker)); + } + } + + if (!node || WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) + goto out; + + if (!WEBKIT_DOM_IS_TEXT (node)) { + if (webkit_dom_node_has_child_nodes (node)) + node = webkit_dom_node_get_first_child (node); + if (!WEBKIT_DOM_IS_TEXT (node)) + goto out; + } + + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + if (!(node_text && *node_text) || !g_utf8_validate (node_text, -1, NULL)) { + g_free (node_text); + goto out; + } + + if (strstr (node_text, "@") && !strstr (node_text, "://")) { + is_email_address = TRUE; + regex = g_regex_new (include_space ? E_MAIL_PATTERN_SPACE : E_MAIL_PATTERN, 0, 0, NULL); + } else + regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL); + + if (!regex) { + g_free (node_text); + goto out; + } + + g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info); + urls = g_match_info_fetch_all (match_info); + + if (urls) { + const gchar *end_of_match = NULL; + gchar *final_url = NULL, *url_end_raw, *url_text; + glong url_start, url_end, url_length; + WebKitDOMNode *url_text_node; + WebKitDOMElement *anchor; + + g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url); + + /* Get start and end position of URL in node's text because positions + * that we get from g_match_info_fetch_pos are not UTF-8 aware */ + url_end_raw = g_strndup (node_text, end_pos_url); + url_end = g_utf8_strlen (url_end_raw, -1); + url_length = g_utf8_strlen (urls[0], -1); + + end_of_match = url_end_raw + end_pos_url - (include_space ? 3 : 2); + /* URLs are extremely unlikely to end with any punctuation, so + * strip any trailing punctuation off from link and put it after + * the link. Do the same for any closing double-quotes as well. */ + while (end_of_match && end_of_match != url_end_raw && strchr (URL_INVALID_TRAILING_CHARS, *end_of_match)) { + url_length--; + url_end--; + end_of_match--; + } + + url_start = url_end - url_length; + + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), + include_space ? url_end - 1 : url_end, + NULL); + + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), url_start, NULL); + url_text_node = webkit_dom_node_get_next_sibling (node); + if (url_text_node) + url_text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (url_text_node)); + else + url_text = NULL; + + /* Sanity check */ + if (!url_text || !*url_text || strrchr (url_text, ' ')) + goto skip; + + if (g_str_has_prefix (url_text, "www.")) + final_url = g_strconcat ("http://" , url_text, NULL); + else if (is_email_address) + final_url = g_strconcat ("mailto:" , url_text, NULL); + else + final_url = g_strdup (url_text); + + /* Create and prepare new anchor element */ + anchor = webkit_dom_document_create_element (document, "A", NULL); + + webkit_dom_element_set_inner_html (anchor, url_text, NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor), + final_url); + + /* Insert new anchor element into document */ + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (anchor), + WEBKIT_DOM_NODE (url_text_node), + NULL); + + skip: + g_free (url_end_raw); + g_free (url_text); + if (final_url) + g_free (final_url); + } else { + gboolean appending_to_link = FALSE; + gchar *href, *text, *url, *text_to_append = NULL; + gint diff; + WebKitDOMElement *parent; + WebKitDOMNode *prev_sibling; + + parent = webkit_dom_node_get_parent_element (node); + prev_sibling = webkit_dom_node_get_previous_sibling (node); + + /* If previous sibling is ANCHOR and actual text node is not beginning with + * space => we're appending to link */ + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) { + text_to_append = webkit_dom_node_get_text_content (node); + if (text_to_append && *text_to_append && + !strstr (text_to_append, " ") && + !(strchr (URL_INVALID_TRAILING_CHARS, *text_to_append) && + !(*text_to_append == '?' && strlen(text_to_append) > 1)) && + !g_str_has_prefix (text_to_append, UNICODE_NBSP)) { + + appending_to_link = TRUE; + parent = WEBKIT_DOM_ELEMENT (prev_sibling); + /* If the node(text) contains the some of unwanted characters + * split it into two nodes and select the right one. */ + if (g_str_has_suffix (text_to_append, UNICODE_NBSP) || + g_str_has_suffix (text_to_append, UNICODE_ZERO_WIDTH_SPACE)) { + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), + g_utf8_strlen (text_to_append, -1) - 1, + NULL); + g_free (text_to_append); + text_to_append = webkit_dom_node_get_text_content (node); + } + } + } + + /* If parent is ANCHOR => we're editing the link */ + if ((!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) || !text_to_append) { + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (node_text); + g_free (text_to_append); + goto out; + } + + /* edit only if href and description are the same */ + href = webkit_dom_html_anchor_element_get_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent)); + + if (appending_to_link) { + gchar *inner_text; + + inner_text = + webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (parent)), + + text = g_strconcat (inner_text, text_to_append, NULL); + g_free (inner_text); + } else + text = webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (parent)); + + element_remove_class (parent, "-x-evo-visited-link"); + + if (strstr (href, "://") && !strstr (text, "://")) { + url = strstr (href, "://") + 3; + diff = strlen (text) - strlen (url); + + if (text [strlen (text) - 1] != '/') + diff++; + + if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) { + gchar *new_href; + + new_href = g_strconcat (href, appending_to_link ? "" : text_to_append, NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), + new_href); + + if (appending_to_link) { + webkit_dom_element_insert_adjacent_html ( + WEBKIT_DOM_ELEMENT (parent), + "beforeend", + text_to_append, + NULL); + + remove_node (node); + } + + g_free (new_href); + } + } else { + diff = strlen (text) - strlen (href); + if (text [strlen (text) - 1] != '/') + diff++; + + if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) { + gchar *inner_html; + gchar *new_href; + + inner_html = webkit_dom_element_get_inner_html (parent); + new_href = g_strconcat ( + inner_html, + appending_to_link ? text_to_append : "", + NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), + new_href); + + if (appending_to_link) { + webkit_dom_element_insert_adjacent_html ( + WEBKIT_DOM_ELEMENT (parent), + "beforeend", + text_to_append, + NULL); + + remove_node (node); + } + + g_free (new_href); + g_free (inner_html); + } + + } + g_free (text_to_append); + g_free (text); + g_free (href); + } + + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (node_text); + + out: + if (!return_key_pressed) + e_editor_dom_selection_restore (editor_page); +} + +void +e_editor_dom_embed_style_sheet (EEditorPage *editor_page, + const gchar *style_sheet_content) +{ + WebKitDOMDocument *document; + WebKitDOMElement *sheet; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + + e_dom_utils_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet"); + + sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet"); + webkit_dom_element_set_attribute ( + sheet, + "type", + "text/css", + NULL); + + webkit_dom_element_set_inner_html (sheet, style_sheet_content, NULL); +} + +void +e_editor_dom_remove_embedded_style_sheet (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMElement *sheet; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + + sheet = webkit_dom_document_get_element_by_id ( + document, "-x-evo-composer-sheet"); + + if (sheet) + remove_node (WEBKIT_DOM_NODE (sheet)); +} + +static void +insert_delete_event (EEditorPage *editor_page, + WebKitDOMRange *range) +{ + EEditorHistoryEvent *ev; + WebKitDOMDocumentFragment *fragment; + EEditorUndoRedoManager *manager; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + + if (e_editor_undo_redo_manager_is_operation_in_progress (manager)) + return; + + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_DELETE; + + fragment = webkit_dom_range_clone_contents (range, NULL); + ev->data.fragment = g_object_ref (fragment); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + + ev->after.start.x = ev->before.start.x; + ev->after.start.y = ev->before.start.y; + ev->after.end.x = ev->before.start.x; + ev->after.end.y = ev->before.start.y; + + e_editor_undo_redo_manager_insert_history_event (manager, ev); + + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_AND; + + e_editor_undo_redo_manager_insert_history_event (manager, ev); +} + +/* Based on original use_pictograms() from GtkHTML */ +static const gchar *emoticons_chars = + /* 0 */ "DO)(|/PQ*!" + /* 10 */ "S\0:-\0:\0:-\0" + /* 20 */ ":\0:;=-\"\0:;" + /* 30 */ "B\"|\0:-'\0:X" + /* 40 */ "\0:\0:-\0:\0:-" + /* 50 */ "\0:\0:-\0:\0:-" + /* 60 */ "\0:\0:\0:-\0:\0" + /* 70 */ ":-\0:\0:-\0:\0"; +static gint emoticons_states[] = { + /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70, + /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0, + /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20, + /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2, + /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51, + /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61, + /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0, + /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 }; +static const gchar *emoticons_icon_names[] = { + "face-angel", + "face-angry", + "face-cool", + "face-crying", + "face-devilish", + "face-embarrassed", + "face-kiss", + "face-laugh", /* not used */ + "face-monkey", /* not used */ + "face-plain", + "face-raspberry", + "face-sad", + "face-sick", + "face-smile", + "face-smile-big", + "face-smirk", + "face-surprise", + "face-tired", + "face-uncertain", + "face-wink", + "face-worried" +}; + +typedef struct _EmoticonLoadContext { + EEmoticon *emoticon; + EEditorPage *editor_page; + gchar *content_type; + gchar *name; +} EmoticonLoadContext; + +static EmoticonLoadContext * +emoticon_load_context_new (EEditorPage *editor_page, + EEmoticon *emoticon) +{ + EmoticonLoadContext *load_context; + + load_context = g_slice_new0 (EmoticonLoadContext); + load_context->emoticon = emoticon; + load_context->editor_page = editor_page; + + return load_context; +} + +static void +emoticon_load_context_free (EmoticonLoadContext *load_context) +{ + g_free (load_context->content_type); + g_free (load_context->name); + g_slice_free (EmoticonLoadContext, load_context); +} + +static void +emoticon_insert_span (EEmoticon *emoticon, + EmoticonLoadContext *load_context, + WebKitDOMElement *span) +{ + EEditorHistoryEvent *ev = NULL; + EEditorUndoRedoManager *manager; + EEditorPage *editor_page = load_context->editor_page; + gboolean misplaced_selection = FALSE, smiley_written; + gchar *node_text = NULL; + const gchar *emoticon_start; + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *node, *insert_before, *prev_sibling, *next_sibling; + WebKitDOMNode *selection_end_marker_parent, *inserted_node; + WebKitDOMRange *range = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + smiley_written = e_editor_page_get_is_smiley_written (editor_page); + manager = e_editor_page_get_undo_redo_manager (editor_page); + + if (e_editor_dom_selection_is_collapsed (editor_page)) { + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + if (!smiley_written) { + if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) { + ev = g_new0 (EEditorHistoryEvent, 1); + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) + ev->type = HISTORY_INPUT; + else { + ev->type = HISTORY_SMILEY; + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + } + } + } + } else { + WebKitDOMRange *tmp_range = NULL; + + tmp_range = e_editor_dom_get_current_range (editor_page); + insert_delete_event (editor_page, tmp_range); + g_clear_object (&tmp_range); + + e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL); + + if (!smiley_written) { + if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) { + ev = g_new0 (EEditorHistoryEvent, 1); + + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) + ev->type = HISTORY_INPUT; + else { + ev->type = HISTORY_SMILEY; + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + } + } + } + + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + } + + /* If the selection was not saved, move it into the first child of body */ + if (!selection_start_marker || !selection_end_marker) { + WebKitDOMHTMLElement *body; + WebKitDOMNode *child; + + body = webkit_dom_document_get_body (document); + child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + + dom_add_selection_markers_into_element_start ( + document, + WEBKIT_DOM_ELEMENT (child), + &selection_start_marker, + &selection_end_marker); + + if (ev && !e_editor_page_get_unicode_smileys_enabled (editor_page)) + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + } + + /* Sometimes selection end marker is in body. Move it into next sibling */ + selection_end_marker_parent = e_editor_dom_get_parent_block_node_from_child ( + WEBKIT_DOM_NODE (selection_end_marker)); + if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (selection_end_marker_parent)) { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)), + WEBKIT_DOM_NODE (selection_end_marker), + WEBKIT_DOM_NODE (selection_start_marker), + NULL); + if (ev && !e_editor_page_get_unicode_smileys_enabled (editor_page)) + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + } + selection_end_marker_parent = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_end_marker)); + + /* Determine before what node we have to insert the smiley */ + insert_before = WEBKIT_DOM_NODE (selection_start_marker); + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (selection_start_marker)); + if (prev_sibling) { + if (webkit_dom_node_is_same_node ( + prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) { + insert_before = WEBKIT_DOM_NODE (selection_end_marker); + } else { + prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling); + if (prev_sibling && + webkit_dom_node_is_same_node ( + prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) { + insert_before = WEBKIT_DOM_NODE (selection_end_marker); + } + } + } else + insert_before = WEBKIT_DOM_NODE (selection_start_marker); + + /* Look if selection is misplaced - that means that the selection was + * restored before the previously inserted smiley in situations when we + * are writing more smileys in a row */ + next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)); + if (next_sibling && WEBKIT_DOM_IS_ELEMENT (next_sibling)) + if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-smiley-wrapper")) + misplaced_selection = TRUE; + + range = e_editor_dom_get_current_range (editor_page); + node = webkit_dom_range_get_end_container (range, NULL); + g_clear_object (&range); + if (WEBKIT_DOM_IS_TEXT (node)) + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + + if (misplaced_selection) { + /* Insert smiley and selection markers after it */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + WEBKIT_DOM_NODE (selection_start_marker), + webkit_dom_node_get_next_sibling (next_sibling), + NULL); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + WEBKIT_DOM_NODE (selection_end_marker), + webkit_dom_node_get_next_sibling (next_sibling), + NULL); + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) + inserted_node = webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (span)), + webkit_dom_node_get_next_sibling (next_sibling), + NULL); + else + inserted_node = webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + WEBKIT_DOM_NODE (span), + webkit_dom_node_get_next_sibling (next_sibling), + NULL); + } else { + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) + inserted_node = webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (span)), + insert_before, + NULL); + else + inserted_node = webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (insert_before), + WEBKIT_DOM_NODE (span), + insert_before, + NULL); + } + + if (!e_editor_page_get_unicode_smileys_enabled (editor_page)) { + /* ​ == UNICODE_ZERO_WIDTH_SPACE */ + webkit_dom_element_insert_adjacent_html ( + WEBKIT_DOM_ELEMENT (span), "afterend", "​", NULL); + } + + if (ev) { + WebKitDOMDocumentFragment *fragment; + WebKitDOMNode *node; + + fragment = webkit_dom_document_create_document_fragment (document); + node = webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (inserted_node), TRUE, NULL), + NULL); + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, TRUE)), + NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, FALSE)), + NULL); + } else + webkit_dom_element_insert_adjacent_html ( + WEBKIT_DOM_ELEMENT (node), "afterend", "​", NULL); + ev->data.fragment = g_object_ref (fragment); + } + + /* Remove the text that represents the text version of smiley that was + * written into the composer. */ + if (node_text && smiley_written) { + emoticon_start = g_utf8_strrchr ( + node_text, -1, g_utf8_get_char (emoticon->text_face)); + /* Check if the written smiley is really the one that we inserted. */ + if (emoticon_start) { + /* The written smiley is the same as text version. */ + if (g_str_has_prefix (emoticon_start, emoticon->text_face)) { + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + g_utf8_strlen (node_text, -1) - strlen (emoticon_start), + strlen (emoticon->text_face), + NULL); + } else if (strstr (emoticon->text_face, "-")) { + gboolean same = TRUE, compensate = FALSE; + gint ii = 0, jj = 0; + + /* Try to recognize smileys without the dash e.g. :). */ + while (emoticon_start[ii] && emoticon->text_face[jj]) { + if (emoticon_start[ii] == emoticon->text_face[jj]) { + if (emoticon->text_face[jj+1] && emoticon->text_face[jj+1] == '-') { + ii++; + jj+=2; + compensate = TRUE; + } else { + ii++; + jj++; + } + } else { + same = FALSE; + break; + } + } + + if (same) { + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + g_utf8_strlen (node_text, -1) - strlen (emoticon_start), + ii, + NULL); + } + /* If we recognize smiley without dash, but we inserted + * the text version with dash we need it insert new + * history input event with that dash. */ + if (compensate) + e_editor_undo_redo_manager_insert_dash_history_event (manager); + } + } + + e_editor_page_set_is_smiley_written (editor_page, FALSE); + } + + if (ev) { + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + e_editor_undo_redo_manager_insert_history_event (manager, ev); + } + + e_editor_dom_selection_restore (editor_page); + + e_editor_page_emit_content_changed (editor_page); + + g_free (node_text); +} + +static void +emoticon_read_async_cb (GFile *file, + GAsyncResult *result, + EmoticonLoadContext *load_context) +{ + EEmoticon *emoticon = load_context->emoticon; + EEditorPage *editor_page = load_context->editor_page; + GError *error = NULL; + gboolean html_mode; + gchar *mime_type; + gchar *base64_encoded, *output, *data; + GFileInputStream *input_stream; + GOutputStream *output_stream; + gssize size; + WebKitDOMElement *wrapper, *image, *smiley_text; + WebKitDOMDocument *document; + + input_stream = g_file_read_finish (file, result, &error); + g_return_if_fail (!error && input_stream); + + output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + + size = g_output_stream_splice ( + output_stream, G_INPUT_STREAM (input_stream), + G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error); + + if (error || (size == -1)) + goto out; + + mime_type = g_content_type_get_mime_type (load_context->content_type); + + data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream)); + base64_encoded = g_base64_encode ((const guchar *) data, size); + output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL); + + html_mode = e_editor_page_get_html_mode (editor_page); + document = e_editor_page_get_document (editor_page); + + /* Insert span with image representation and another one with text + * representation and hide/show them dependant on active composer mode */ + wrapper = webkit_dom_document_create_element (document, "SPAN", NULL); + if (html_mode) + webkit_dom_element_set_attribute ( + wrapper, "class", "-x-evo-smiley-wrapper -x-evo-resizable-wrapper", NULL); + else + webkit_dom_element_set_attribute ( + wrapper, "class", "-x-evo-smiley-wrapper", NULL); + + image = webkit_dom_document_create_element (document, "IMG", NULL); + webkit_dom_element_set_attribute (image, "src", output, NULL); + webkit_dom_element_set_attribute (image, "data-inline", "", NULL); + webkit_dom_element_set_attribute (image, "data-name", load_context->name, NULL); + webkit_dom_element_set_attribute (image, "alt", emoticon->text_face, NULL); + webkit_dom_element_set_attribute (image, "class", "-x-evo-smiley-img", NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (image), NULL); + + smiley_text = webkit_dom_document_create_element (document, "SPAN", NULL); + webkit_dom_element_set_attribute (smiley_text, "class", "-x-evo-smiley-text", NULL); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (smiley_text), emoticon->text_face, NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (smiley_text), NULL); + + emoticon_insert_span (emoticon, load_context, wrapper); + + g_free (base64_encoded); + g_free (output); + g_free (mime_type); + g_object_unref (output_stream); + out: + emoticon_load_context_free (load_context); +} + +static void +emoticon_query_info_async_cb (GFile *file, + GAsyncResult *result, + EmoticonLoadContext *load_context) +{ + GError *error = NULL; + GFileInfo *info; + + info = g_file_query_info_finish (file, result, &error); + g_return_if_fail (!error && info); + + load_context->content_type = g_strdup (g_file_info_get_content_type (info)); + load_context->name = g_strdup (g_file_info_get_name (info)); + + g_file_read_async ( + file, G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) emoticon_read_async_cb, load_context); + + g_object_unref (info); +} + +void +e_editor_dom_insert_smiley (EEditorPage *editor_page, + EEmoticon *emoticon) +{ + WebKitDOMDocument *document; + GFile *file; + gchar *filename_uri; + EmoticonLoadContext *load_context; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + + if (e_editor_page_get_unicode_smileys_enabled (editor_page)) { + WebKitDOMElement *wrapper; + + wrapper = webkit_dom_document_create_element (document, "SPAN", NULL); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (wrapper), emoticon->unicode_character, NULL); + + load_context = emoticon_load_context_new (editor_page, emoticon); + emoticon_insert_span (emoticon, load_context, wrapper); + emoticon_load_context_free (load_context); + } else { + filename_uri = e_emoticon_get_uri (emoticon); + g_return_if_fail (filename_uri != NULL); + + load_context = emoticon_load_context_new (editor_page, emoticon); + + file = g_file_new_for_uri (filename_uri); + g_file_query_info_async ( + file, "standard::*", G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context); + + g_free (filename_uri); + g_object_unref (file); + } +} + +void +e_editor_dom_insert_smiley_by_name (EEditorPage *editor_page, + const gchar *name) +{ + const EEmoticon *emoticon; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + emoticon = e_emoticon_chooser_lookup_emoticon (name); + e_editor_page_set_is_smiley_written (editor_page, FALSE); + e_editor_dom_insert_smiley (editor_page, (EEmoticon *) emoticon); +} + +void +e_editor_dom_check_magic_smileys (EEditorPage *editor_page) +{ + WebKitDOMNode *node; + WebKitDOMRange *range = NULL; + gint pos, state, relative, start; + gchar *node_text; + gunichar uc; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!e_editor_page_get_magic_smileys_enabled (editor_page)) + return; + + range = e_editor_dom_get_current_range (editor_page); + node = webkit_dom_range_get_end_container (range, NULL); + if (!WEBKIT_DOM_IS_TEXT (node)) { + g_clear_object (&range); + return; + } + + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + if (node_text == NULL) { + g_clear_object (&range); + return; + } + + start = webkit_dom_range_get_end_offset (range, NULL) - 1; + pos = start; + state = 0; + while (pos >= 0) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos)); + relative = 0; + while (emoticons_chars[state + relative]) { + if (emoticons_chars[state + relative] == uc) + break; + relative++; + } + state = emoticons_states[state + relative]; + /* 0 .. not found, -n .. found n-th */ + if (state <= 0) + break; + pos--; + } + + /* Special case needed to recognize angel and devilish. */ + if (pos > 0 && state == -14) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); + if (uc == 'O') { + state = -1; + pos--; + } else if (uc == '>') { + state = -5; + pos--; + } + } + + if (state < 0) { + const EEmoticon *emoticon; + + if (pos > 0) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); + if (!g_unichar_isspace (uc)) { + g_free (node_text); + g_clear_object (&range); + return; + } + } + + emoticon = e_emoticon_chooser_lookup_emoticon ( + emoticons_icon_names[-state - 1]); + e_editor_page_set_is_smiley_written (editor_page, TRUE); + e_editor_dom_insert_smiley (editor_page, (EEmoticon *) emoticon); + } + + g_clear_object (&range); + g_free (node_text); +} + +static void +dom_set_links_active (WebKitDOMDocument *document, + gboolean active) +{ + WebKitDOMElement *style; + + style = webkit_dom_document_get_element_by_id (document, "-x-evo-style-a"); + if (style) + remove_node (WEBKIT_DOM_NODE (style)); + + if (!active) { + WebKitDOMHTMLHeadElement *head; + head = webkit_dom_document_get_head (document); + + style = webkit_dom_document_create_element (document, "STYLE", NULL); + webkit_dom_element_set_id (style, "-x-evo-style-a"); + webkit_dom_element_set_attribute (style, "type", "text/css", NULL); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL); + } +} + +static void +fix_paragraph_structure_after_pressing_enter_after_smiley (WebKitDOMDocument *document) +{ + WebKitDOMElement *element; + + element = webkit_dom_document_query_selector ( + document, "span.-x-evo-smiley-wrapper > br", NULL); + + if (element) { + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)); + webkit_dom_element_set_inner_html ( + webkit_dom_node_get_parent_element (parent), + UNICODE_ZERO_WIDTH_SPACE, + NULL); + } +} + +static gboolean +fix_paragraph_structure_after_pressing_enter (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMNode *body, *prev_sibling, *node; + WebKitDOMElement *br; + gboolean prev_is_heading = FALSE; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + document = e_editor_page_get_document (editor_page); + body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document)); + + e_editor_dom_selection_save (editor_page); + + /* When pressing Enter on empty line in the list (or after heading elements) + * WebKit will end that list and inserts

so replace it + * with the right paragraph element. */ + br = webkit_dom_document_query_selector ( + document, "body > div:not([data-evo-paragraph]) > #-x-evo-selection-end-marker + br", NULL); + + if (!br || webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (br)) || + webkit_dom_node_get_previous_sibling (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (br)))) + goto out; + + node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br)); + + prev_sibling = webkit_dom_node_get_previous_sibling (node); + if (prev_sibling && WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (prev_sibling)) + prev_is_heading = TRUE; + + webkit_dom_node_replace_child ( + body, + WEBKIT_DOM_NODE (e_editor_dom_prepare_paragraph (editor_page, FALSE)), + node, + NULL); + + out: + e_editor_dom_selection_restore (editor_page); + + return prev_is_heading; +} + +static gboolean +surround_text_with_paragraph_if_needed (EEditorPage *editor_page, + WebKitDOMNode *node) +{ + WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node); + WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node); + WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node); + WebKitDOMElement *element; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + /* All text in composer has to be written in div elements, so if + * we are writing something straight to the body, surround it with + * paragraph */ + if (WEBKIT_DOM_IS_TEXT (node) && + (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent) || + WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent))) { + element = e_editor_dom_put_node_into_paragraph (editor_page, node, TRUE); + if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent)) + webkit_dom_element_remove_attribute (element, "style"); + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling)) + remove_node (next_sibling); + + /* Tab character */ + if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) && + element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (element), + prev_sibling, + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (element)), + NULL); + } + + return TRUE; + } + + return FALSE; +} + +static gboolean +selection_is_in_table (WebKitDOMDocument *document, + gboolean *first_cell, + WebKitDOMNode **table_node) +{ + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMNode *node, *parent; + WebKitDOMRange *range = NULL; + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + + if (first_cell != NULL) + *first_cell = FALSE; + + if (table_node != NULL) + *table_node = NULL; + + if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) { + g_clear_object (&dom_selection); + return FALSE; + } + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + node = webkit_dom_range_get_start_container (range, NULL); + g_clear_object (&dom_selection); + + parent = node; + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent)) { + if (first_cell != NULL) { + if (!webkit_dom_node_get_previous_sibling (parent)) { + gboolean on_start = TRUE; + WebKitDOMNode *tmp; + + tmp = webkit_dom_node_get_previous_sibling (node); + if (!tmp && WEBKIT_DOM_IS_TEXT (node)) + on_start = webkit_dom_range_get_start_offset (range, NULL) == 0; + else if (tmp) + on_start = FALSE; + + if (on_start) { + node = webkit_dom_node_get_parent_node (parent); + if (node && WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (node)) + if (!webkit_dom_node_get_previous_sibling (node)) + *first_cell = TRUE; + } + } + } else { + g_clear_object (&range); + return TRUE; + } + } + if (WEBKIT_DOM_IS_HTML_TABLE_ELEMENT (parent)) { + if (table_node != NULL) + *table_node = parent; + else { + g_clear_object (&range); + return TRUE; + } + } + parent = webkit_dom_node_get_parent_node (parent); + } + + g_clear_object (&range); + + if (table_node == NULL) + return FALSE; + + return *table_node != NULL; +} + +static gboolean +jump_to_next_table_cell (WebKitDOMDocument *document, + gboolean jump_back) +{ + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMNode *node, *cell; + WebKitDOMRange *range = NULL; + + if (!selection_is_in_table (document, NULL, NULL)) + return FALSE; + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + node = webkit_dom_range_get_start_container (range, NULL); + + cell = node; + while (cell && !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell)) { + cell = webkit_dom_node_get_parent_node (cell); + } + + if (!WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell)) { + g_clear_object (&range); + g_clear_object (&dom_selection); + return FALSE; + } + + if (jump_back) { + /* Get previous cell */ + node = webkit_dom_node_get_previous_sibling (cell); + if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) { + /* No cell, go one row up. */ + node = webkit_dom_node_get_parent_node (cell); + node = webkit_dom_node_get_previous_sibling (node); + if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) { + node = webkit_dom_node_get_last_child (node); + } else { + /* No row above, move to the block before table. */ + node = webkit_dom_node_get_parent_node (cell); + while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) + node = webkit_dom_node_get_parent_node (node); + + node = webkit_dom_node_get_previous_sibling (node); + } + } + } else { + /* Get next cell */ + node = webkit_dom_node_get_next_sibling (cell); + if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) { + /* No cell, go one row below. */ + node = webkit_dom_node_get_parent_node (cell); + node = webkit_dom_node_get_next_sibling (node); + if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) { + node = webkit_dom_node_get_first_child (node); + } else { + /* No row below, move to the block after table. */ + node = webkit_dom_node_get_parent_node (cell); + while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) + node = webkit_dom_node_get_parent_node (node); + + node = webkit_dom_node_get_next_sibling (node); + } + } + } + + if (!node) { + g_clear_object (&range); + g_clear_object (&dom_selection); + return FALSE; + } + + webkit_dom_range_select_node_contents (range, node, NULL); + webkit_dom_range_collapse (range, TRUE, NULL); + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, range); + g_clear_object (&range); + g_clear_object (&dom_selection); + + return TRUE; +} + +static gboolean +save_history_before_event_in_table (EEditorPage *editor_page, + WebKitDOMRange *range) +{ + WebKitDOMNode *node; + WebKitDOMElement *block; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + node = webkit_dom_range_get_start_container (range, NULL); + if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) + block = WEBKIT_DOM_ELEMENT (node); + else + block = get_parent_block_element (node); + + if (block && WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (block)) { + EEditorUndoRedoManager *manager; + EEditorHistoryEvent *ev; + + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_TABLE_INPUT; + + e_editor_dom_selection_save (editor_page); + ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (block), TRUE, NULL)); + e_editor_dom_selection_restore (editor_page); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + e_editor_undo_redo_manager_insert_history_event (manager, ev); + + return TRUE; + } + + return FALSE; +} + +static gboolean +insert_tabulator (EEditorPage *editor_page) +{ + EEditorUndoRedoManager *manager; + EEditorHistoryEvent *ev = NULL; + gboolean success; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + + if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) { + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_INPUT; + + if (!e_editor_dom_selection_is_collapsed (editor_page)) { + WebKitDOMRange *tmp_range = NULL; + + tmp_range = e_editor_dom_get_current_range (editor_page); + insert_delete_event (editor_page, tmp_range); + g_clear_object (&tmp_range); + } + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + + ev->before.end.x = ev->before.start.x; + ev->before.end.y = ev->before.start.y; + } + + success = e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, "\t"); + + if (ev) { + if (success) { + WebKitDOMDocument *document; + WebKitDOMElement *element; + WebKitDOMDocumentFragment *fragment; + + document = e_editor_page_get_document (editor_page); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + + fragment = webkit_dom_document_create_document_fragment (document); + element = webkit_dom_document_create_element (document, "span", NULL); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (element), "\t", NULL); + webkit_dom_element_set_attribute ( + element, "class", "Apple-tab-span", NULL); + webkit_dom_element_set_attribute ( + element, "style", "white-space:pre", NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), WEBKIT_DOM_NODE (element), NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE (dom_create_selection_marker (document, TRUE)), + NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE (dom_create_selection_marker (document, FALSE)), + NULL); + ev->data.fragment = g_object_ref (fragment); + + e_editor_undo_redo_manager_insert_history_event (manager, ev); + e_editor_page_emit_content_changed (editor_page); + } else { + e_editor_undo_redo_manager_remove_current_history_event (manager); + e_editor_undo_redo_manager_remove_current_history_event (manager); + g_free (ev); + } + } + + return success; +} + +static void +body_keypress_event_cb (WebKitDOMElement *element, + WebKitDOMUIEvent *event, + EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMRange *range = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + e_editor_page_set_is_processing_keypress_event (editor_page, TRUE); + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element)); + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + if (range && !webkit_dom_range_get_collapsed (range, NULL)) + insert_delete_event (editor_page, range); + + g_clear_object (&dom_selection); + g_clear_object (&range); +} + +static void +body_keydown_event_cb (WebKitDOMElement *element, + WebKitDOMUIEvent *event, + EEditorPage *editor_page) +{ + gboolean backspace_key, delete_key, space_key, return_key; + gboolean shift_key, control_key, tabulator_key; + glong key_code; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMRange *range = NULL; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element)); + + key_code = webkit_dom_ui_event_get_key_code (event); + delete_key = key_code == HTML_KEY_CODE_DELETE; + return_key = key_code == HTML_KEY_CODE_RETURN; + backspace_key = key_code == HTML_KEY_CODE_BACKSPACE; + space_key = key_code == HTML_KEY_CODE_SPACE; + tabulator_key = key_code == HTML_KEY_CODE_TABULATOR; + + if (key_code == HTML_KEY_CODE_CONTROL) { + dom_set_links_active (document, TRUE); + return; + } + + e_editor_page_set_dont_save_history_in_body_input (editor_page, delete_key || backspace_key); + + e_editor_page_set_return_key_pressed (editor_page, return_key); + e_editor_page_set_space_key_pressed (editor_page, space_key); + + if (!(delete_key || return_key || backspace_key || space_key || tabulator_key)) + return; + + shift_key = webkit_dom_keyboard_event_get_shift_key (WEBKIT_DOM_KEYBOARD_EVENT (event)); + control_key = webkit_dom_keyboard_event_get_ctrl_key (WEBKIT_DOM_KEYBOARD_EVENT (event)); + + if (tabulator_key) { + if (jump_to_next_table_cell (document, shift_key)) { + webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event)); + goto out; + } + + if (!shift_key && insert_tabulator (editor_page)) + webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event)); + + goto out; + } + + if (return_key && e_editor_dom_key_press_event_process_return_key (editor_page)) { + webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event)); + goto out; + } + + if (backspace_key && e_editor_dom_key_press_event_process_backspace_key (editor_page)) { + webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event)); + goto out; + } + + if (delete_key || backspace_key) { + if (e_editor_dom_key_press_event_process_delete_or_backspace_key (editor_page, key_code, control_key, delete_key)) + webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event)); + goto out; + } + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + if (save_history_before_event_in_table (editor_page, range)) + goto out; + + if (return_key) { + EEditorHistoryEvent *ev; + EEditorUndoRedoManager *manager; + + /* Insert new history event for Return to have the right coordinates. + * The fragment will be added later. */ + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_INPUT; + + manager = e_editor_page_get_undo_redo_manager (editor_page); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->before.start.x, + &ev->before.start.y, + &ev->before.end.x, + &ev->before.end.y); + e_editor_undo_redo_manager_insert_history_event (manager, ev); + } + out: + g_clear_object (&range); + g_clear_object (&dom_selection); +} + +static gboolean +save_history_after_event_in_table (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMElement *element; + WebKitDOMNode *node; + WebKitDOMRange *range = NULL; + EEditorHistoryEvent *ev; + EEditorUndoRedoManager *manager; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + document = e_editor_page_get_document (editor_page); + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + + if (!webkit_dom_dom_selection_get_range_count (dom_selection)) { + g_clear_object (&dom_selection); + return FALSE; + } + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + /* Find if writing into table. */ + node = webkit_dom_range_get_start_container (range, NULL); + if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = get_parent_block_element (node); + + g_clear_object (&dom_selection); + g_clear_object (&range); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + /* If writing to table we have to create different history event. */ + if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (element)) { + ev = e_editor_undo_redo_manager_get_current_history_event (manager); + if (ev->type != HISTORY_TABLE_INPUT) + return FALSE; + } else + return FALSE; + + e_editor_dom_selection_save (editor_page); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + + ev->data.dom.to = g_object_ref (webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), TRUE, NULL)); + + e_editor_dom_selection_restore (editor_page); + + return TRUE; +} + +static void +save_history_for_input (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMDocumentFragment *fragment; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMRange *range = NULL, *range_clone = NULL; + WebKitDOMNode *start_container; + EEditorHistoryEvent *ev; + EEditorUndoRedoManager *manager; + glong offset; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + manager = e_editor_page_get_undo_redo_manager (editor_page); + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + g_clear_object (&dom_window); + + if (!webkit_dom_dom_selection_get_range_count (dom_selection)) { + g_clear_object (&dom_selection); + return; + } + + if (e_editor_page_get_return_key_pressed (editor_page)) { + ev = e_editor_undo_redo_manager_get_current_history_event (manager); + if (ev->type != HISTORY_INPUT) { + g_clear_object (&dom_selection); + return; + } + } else { + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_INPUT; + } + + e_editor_page_block_selection_changed (editor_page); + + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + range_clone = webkit_dom_range_clone_range (range, NULL); + offset = webkit_dom_range_get_start_offset (range_clone, NULL); + start_container = webkit_dom_range_get_start_container (range_clone, NULL); + if (offset > 0) + webkit_dom_range_set_start ( + range_clone, + start_container, + offset - 1, + NULL); + fragment = webkit_dom_range_clone_contents (range_clone, NULL); + /* We have to specially handle Return key press */ + if (e_editor_page_get_return_key_pressed (editor_page)) { + WebKitDOMElement *element_start, *element_end; + WebKitDOMNode *parent_start, *parent_end, *node; + + element_start = webkit_dom_document_create_element (document, "span", NULL); + webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (element_start), NULL); + webkit_dom_dom_selection_modify (dom_selection, "move", "left", "character"); + g_clear_object (&range); + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + element_end = webkit_dom_document_create_element (document, "span", NULL); + webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (element_end), NULL); + + parent_start = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element_start)); + parent_end = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element_end)); + + while (parent_start && parent_end && !webkit_dom_node_is_same_node (parent_start, parent_end) && + !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_start) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_end)) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (fragment), + webkit_dom_node_clone_node_with_error (parent_start, FALSE, NULL), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)), + NULL); + parent_start = webkit_dom_node_get_parent_node (parent_start); + parent_end = webkit_dom_node_get_parent_node (parent_end); + } + + node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)); + while (webkit_dom_node_get_next_sibling (node)) { + WebKitDOMNode *last_child; + + last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment)); + webkit_dom_node_append_child ( + webkit_dom_node_get_previous_sibling (last_child), + last_child, + NULL); + } + + node = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment)); + while (webkit_dom_node_get_last_child (node)) { + node = webkit_dom_node_get_last_child (node); + } + + webkit_dom_node_append_child ( + node, + WEBKIT_DOM_NODE ( + webkit_dom_document_create_element (document, "br", NULL)), + NULL); + webkit_dom_node_append_child ( + node, + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, TRUE)), + NULL); + webkit_dom_node_append_child ( + node, + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, FALSE)), + NULL); + + remove_node (WEBKIT_DOM_NODE (element_start)); + remove_node (WEBKIT_DOM_NODE (element_end)); + + g_object_set_data ( + G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1)); + + webkit_dom_dom_selection_modify (dom_selection, "move", "right", "character"); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, TRUE)), + NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, FALSE)), + NULL); + } + + g_clear_object (&dom_selection); + g_clear_object (&range); + g_clear_object (&range_clone); + + e_editor_page_unblock_selection_changed (editor_page); + + ev->data.fragment = g_object_ref (fragment); + + if (!e_editor_page_get_return_key_pressed (editor_page)) + e_editor_undo_redo_manager_insert_history_event (manager, ev); +} + +typedef struct _TimeoutContext TimeoutContext; + +struct _TimeoutContext { + EEditorPage *editor_page; +}; + +static void +timeout_context_free (TimeoutContext *context) +{ + g_slice_free (TimeoutContext, context); +} + +static gboolean +force_spell_check_on_timeout (TimeoutContext *context) +{ + e_editor_dom_force_spell_check_in_viewport (context->editor_page); + e_editor_page_set_spell_check_on_scroll_event_source_id (context->editor_page, 0); + return FALSE; +} + +static void +body_scroll_event_cb (WebKitDOMElement *element, + WebKitDOMEvent *event, + EEditorPage *editor_page) +{ + TimeoutContext *context; + guint id; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!e_editor_page_get_inline_spelling_enabled (editor_page)) + return; + + context = g_slice_new0 (TimeoutContext); + context->editor_page = editor_page; + + id = e_editor_page_get_spell_check_on_scroll_event_source_id (editor_page); + if (id > 0) + g_source_remove (id); + + id = g_timeout_add_seconds_full ( + 1, + G_PRIORITY_DEFAULT, + (GSourceFunc)force_spell_check_on_timeout, + context, + (GDestroyNotify)timeout_context_free); + + e_editor_page_set_spell_check_on_scroll_event_source_id (editor_page, id); +} + +static void +remove_zero_width_spaces_on_body_input (EEditorPage *editor_page, + WebKitDOMNode *node) +{ + gboolean html_mode; + + html_mode = e_editor_page_get_html_mode (editor_page); + /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE + * to move caret into right space. When this callback is called it is not + * necessary anymore so remove it */ + if (html_mode) { + WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node); + + if (parent) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (parent)); + + if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) { + gchar *text = webkit_dom_node_get_text_content ( + prev_sibling); + + if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) + remove_node (prev_sibling); + + g_free (text); + } + + } + } + + /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */ + if (WEBKIT_DOM_IS_TEXT (node)) { + gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node)); + glong length = webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)); + WebKitDOMNode *parent; + + /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE + * character as when we will remove it it will collapse */ + if (length > 1) { + if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL); + else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL); + } + g_free (text); + + parent = webkit_dom_node_get_parent_node (node); + if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) && + !webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-evo-paragraph")) { + if (html_mode) + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (parent), + "data-evo-paragraph", + "", + NULL); + else + e_editor_dom_set_paragraph_style ( + editor_page, WEBKIT_DOM_ELEMENT (parent), -1, 0, NULL); + } + + /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the + * caret position to right place. It is removed when user starts typing. But + * when the user will press left arrow he will move the caret into + * smiley wrapper. If he will start to write there we have to move the written + * text out of the wrapper and move caret to right place */ + if (WEBKIT_DOM_IS_ELEMENT (parent) && + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text")) { + gchar *text; + WebKitDOMCharacterData *data; + WebKitDOMText *text_node; + WebKitDOMDocument *document; + + document = e_editor_page_get_document (editor_page); + + /* Split out the newly written character to its own text node, */ + data = WEBKIT_DOM_CHARACTER_DATA (node); + parent = webkit_dom_node_get_parent_node (parent); + text = webkit_dom_character_data_substring_data ( + data, + webkit_dom_character_data_get_length (data) - 1, + 1, + NULL); + webkit_dom_character_data_delete_data ( + data, + webkit_dom_character_data_get_length (data) - 1, + 1, + NULL); + text_node = webkit_dom_document_create_text_node (document, text); + g_free (text); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, FALSE)), + webkit_dom_node_get_next_sibling (parent), + NULL); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE ( + dom_create_selection_marker (document, TRUE)), + webkit_dom_node_get_next_sibling (parent), + NULL); + /* Move the text node outside of smiley. */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE (text_node), + webkit_dom_node_get_next_sibling (parent), + NULL); + e_editor_dom_selection_restore (editor_page); + } + } +} + +void +e_editor_dom_body_input_event_process (EEditorPage *editor_page, + WebKitDOMEvent *event) +{ + WebKitDOMDocument *document; + WebKitDOMNode *node; + WebKitDOMRange *range = NULL; + EEditorUndoRedoManager *manager; + gboolean do_spell_check = FALSE; + gboolean html_mode; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + range = e_editor_dom_get_current_range (editor_page); + node = webkit_dom_range_get_end_container (range, NULL); + + manager = e_editor_page_get_undo_redo_manager (editor_page); + + html_mode = e_editor_page_get_html_mode (editor_page); + e_editor_page_emit_content_changed (editor_page); + + if (e_editor_undo_redo_manager_is_operation_in_progress (manager)) { + e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE); + e_editor_page_set_dont_save_history_in_body_input (editor_page, FALSE); + remove_zero_width_spaces_on_body_input (editor_page, node); + do_spell_check = TRUE; + goto out; + } + + /* When the Backspace is pressed in a bulleted list item with just one + * character left in it, WebKit will create another BR element in the + * item. */ + if (!html_mode) { + WebKitDOMElement *element; + + element = webkit_dom_document_query_selector ( + document, "ul > li > br + br", NULL); + + if (element) + remove_node (WEBKIT_DOM_NODE (element)); + } + + if (!save_history_after_event_in_table (editor_page)) { + if (!e_editor_page_get_dont_save_history_in_body_input (editor_page)) + save_history_for_input (editor_page); + else + do_spell_check = TRUE; + } + + /* Don't try to look for smileys if we are deleting text. */ + if (!e_editor_page_get_dont_save_history_in_body_input (editor_page)) + e_editor_dom_check_magic_smileys (editor_page); + + e_editor_page_set_dont_save_history_in_body_input (editor_page, FALSE); + + if (e_editor_page_get_return_key_pressed (editor_page) || + e_editor_page_get_space_key_pressed (editor_page)) { + e_editor_dom_check_magic_links (editor_page, FALSE); + if (e_editor_page_get_return_key_pressed (editor_page)) { + if (fix_paragraph_structure_after_pressing_enter (editor_page) && + html_mode) { + /* When the return is pressed in a H1-6 element, WebKit doesn't + * continue with the same element, but creates normal paragraph, + * so we have to unset the bold font. */ + e_editor_undo_redo_manager_set_operation_in_progress (manager, TRUE); + e_editor_dom_selection_set_bold (editor_page, FALSE); + e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE); + } + + fix_paragraph_structure_after_pressing_enter_after_smiley (document); + + do_spell_check = TRUE; + } + } else { + WebKitDOMNode *node; + + node = webkit_dom_range_get_end_container (range, NULL); + + if (surround_text_with_paragraph_if_needed (editor_page, node)) { + WebKitDOMElement *element; + + element = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)); + e_editor_dom_selection_restore (editor_page); + } + + if (WEBKIT_DOM_IS_TEXT (node)) { + WebKitDOMElement *parent; + gchar *text; + + text = webkit_dom_node_get_text_content (node); + + if (text && *text && *text != ' ' && !g_str_has_prefix (text, UNICODE_NBSP)) { + gboolean valid = FALSE; + + if (*text == '?' && strlen (text) > 1) + valid = TRUE; + else if (!strchr (URL_INVALID_TRAILING_CHARS, *text)) + valid = TRUE; + + if (valid) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling (node); + + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) + e_editor_dom_check_magic_links (editor_page, FALSE); + } + } + + parent = webkit_dom_node_get_parent_element (node); + if (element_has_class (parent, "-x-evo-resizable-wrapper") || + element_has_class (parent, "-x-evo-smiley-wrapper")) { + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMNode *prev_sibling; + gboolean writing_before = TRUE; + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + + prev_sibling = webkit_dom_node_get_previous_sibling (node); + if (prev_sibling && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (prev_sibling)) + writing_before = FALSE; + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (parent)), + node, + writing_before ? + WEBKIT_DOM_NODE (parent) : + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)), + NULL); + + g_clear_object (&range); + + range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents (range, node, NULL); + webkit_dom_range_collapse (range, FALSE, NULL); + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, range); + + g_clear_object (&dom_window); + g_clear_object (&dom_selection); + } + + g_free (text); + } + } + + remove_zero_width_spaces_on_body_input (editor_page, node); + + /* Writing into quoted content */ + if (!html_mode) { + gint citation_level; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *node, *parent; + + node = webkit_dom_range_get_end_container (range, NULL); + + citation_level = e_editor_dom_get_citation_level (node); + if (citation_level == 0) + goto out; + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + if (selection_start_marker) + goto out; + + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + /* If the selection was not saved, move it into the first child of body */ + if (!selection_start_marker || !selection_end_marker) { + WebKitDOMHTMLElement *body; + WebKitDOMNode *child; + + body = webkit_dom_document_get_body (document); + child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + + dom_add_selection_markers_into_element_start ( + document, + WEBKIT_DOM_ELEMENT (child), + &selection_start_marker, + &selection_end_marker); + } + + /* We have to process elements only inside normal block */ + parent = WEBKIT_DOM_NODE (get_parent_block_element ( + WEBKIT_DOM_NODE (selection_start_marker))); + if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) { + e_editor_dom_selection_restore (editor_page); + goto out; + } + + if (selection_start_marker) { + gchar *content; + gint text_length, word_wrap_length, length; + WebKitDOMElement *block; + gboolean remove_quoting = FALSE; + + word_wrap_length = e_editor_page_get_word_wrap_length (editor_page); + length = word_wrap_length - 2 * citation_level; + + block = WEBKIT_DOM_ELEMENT (parent); + if (webkit_dom_element_query_selector ( + WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (selection_end_marker)); + + if (WEBKIT_DOM_IS_ELEMENT (prev_sibling)) + remove_quoting = element_has_class ( + WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted"); + } + + content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (block)); + text_length = g_utf8_strlen (content, -1); + g_free (content); + + /* Wrap and quote the line */ + if (!remove_quoting && text_length >= word_wrap_length) { + e_editor_dom_remove_quoting_from_element (block); + + block = e_editor_dom_wrap_paragraph_length (editor_page, block, length); + webkit_dom_node_normalize (WEBKIT_DOM_NODE (block)); + e_editor_dom_quote_plain_text_element_after_wrapping ( + editor_page, WEBKIT_DOM_ELEMENT (block), citation_level); + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + if (!selection_start_marker) + dom_add_selection_markers_into_element_end ( + document, + WEBKIT_DOM_ELEMENT (block), + NULL, + NULL); + + e_editor_dom_selection_restore (editor_page); + do_spell_check = TRUE; + goto out; + } + } + e_editor_dom_selection_restore (editor_page); + } + out: + if (do_spell_check) + e_editor_dom_force_spell_check_for_current_paragraph (editor_page); + + g_clear_object (&range); +} + +static void +body_input_event_cb (WebKitDOMElement *element, + WebKitDOMEvent *event, + EEditorPage *editor_page) +{ + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + /* Only process the input event if it was triggered by the key press + * and not i.e. by execCommand. This behavior changed when the support + * for beforeinput event was introduced in WebKit. */ + if (e_editor_page_is_processing_keypress_event (editor_page)) + e_editor_dom_body_input_event_process (editor_page, event); + + e_editor_page_set_is_processing_keypress_event (editor_page, FALSE); +} + +void +e_editor_dom_remove_input_event_listener_from_body (EEditorPage *editor_page) +{ + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!e_editor_page_get_body_input_event_removed (editor_page)) { + WebKitDOMDocument *document; + + document = e_editor_page_get_document (editor_page); + + webkit_dom_event_target_remove_event_listener ( + WEBKIT_DOM_EVENT_TARGET (webkit_dom_document_get_body (document)), + "input", + G_CALLBACK (body_input_event_cb), + FALSE); + + e_editor_page_set_body_input_event_removed (editor_page, TRUE); + } +} + +void +e_editor_dom_register_input_event_listener_on_body (EEditorPage *editor_page) +{ + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (e_editor_page_get_body_input_event_removed (editor_page)) { + WebKitDOMDocument *document; + + document = e_editor_page_get_document (editor_page); + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (webkit_dom_document_get_body (document)), + "input", + G_CALLBACK (body_input_event_cb), + FALSE, + editor_page); + + e_editor_page_set_body_input_event_removed (editor_page, FALSE); + } +} + +static void +remove_empty_blocks (WebKitDOMDocument *document) +{ + gint ii; + WebKitDOMNodeList *list = NULL; + + list = webkit_dom_document_query_selector_all ( + document, "blockquote[type=cite] > :empty:not(br)", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) + remove_node (webkit_dom_node_list_item (list, ii)); + g_clear_object (&list); + + list = webkit_dom_document_query_selector_all ( + document, "blockquote[type=cite]:empty", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) + remove_node (webkit_dom_node_list_item (list, ii)); + g_clear_object (&list); +} + +/* Following two functions are used when deleting the selection inside + * the quoted content. The thing is that normally the quote marks are not + * selectable by user. But this caused a lot of problems for WebKit when removing + * the selection. This will avoid it as when the delete or backspace key is pressed + * we will make the quote marks user selectable so they will act as any other text. + * On HTML keyup event callback we will make them again non-selectable. */ +void +e_editor_dom_disable_quote_marks_select (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMHTMLHeadElement *head; + WebKitDOMElement *style_element; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + head = webkit_dom_document_get_head (document); + + if (!webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style")) { + style_element = webkit_dom_document_create_element (document, "style", NULL); + webkit_dom_element_set_id (style_element, "-x-evo-quote-style"); + webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL); + webkit_dom_element_set_inner_html ( + style_element, + ".-x-evo-quoted { -webkit-user-select: none; }", + NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL); + } +} + +static void +enable_quote_marks_select (WebKitDOMDocument *document) +{ + WebKitDOMElement *style_element; + + if ((style_element = webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style"))) + remove_node (WEBKIT_DOM_NODE (style_element)); +} + +void +e_editor_dom_remove_node_and_parents_if_empty (WebKitDOMNode *node) +{ + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (node)); + + remove_node (WEBKIT_DOM_NODE (node)); + + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + WebKitDOMNode *tmp; + + tmp = webkit_dom_node_get_parent_node (parent); + remove_node_if_empty (parent); + parent = tmp; + } +} + +void +e_editor_dom_merge_siblings_if_necessary (EEditorPage *editor_page, + WebKitDOMDocumentFragment *deleted_content) +{ + WebKitDOMDocument *document; + WebKitDOMElement *element, *prev_element; + WebKitDOMNode *child; + WebKitDOMNodeList *list = NULL; + gboolean equal_nodes; + gint ii; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = e_editor_page_get_document (editor_page); + + if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-main-cite"))) + webkit_dom_element_remove_attribute (element, "id"); + + element = webkit_dom_document_query_selector (document, "blockquote:not([data-evo-query-skip]) + blockquote:not([data-evo-query-skip])", NULL); + if (!element) + goto signature; + repeat: + child = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)); + if (WEBKIT_DOM_IS_ELEMENT (child)) + prev_element = WEBKIT_DOM_ELEMENT (child); + else + goto signature; + + equal_nodes = webkit_dom_node_is_equal_node ( + webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), FALSE, NULL), + webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (prev_element), FALSE, NULL)); + + if (equal_nodes) { + if (webkit_dom_element_get_child_element_count (element) > + webkit_dom_element_get_child_element_count (prev_element)) { + while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (prev_element), child, NULL); + remove_node (WEBKIT_DOM_NODE (element)); + } else { + while ((child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (prev_element)))) + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (element), + child, + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (element)), + NULL); + remove_node (WEBKIT_DOM_NODE (prev_element)); + } + } else + webkit_dom_element_set_attribute (element, "data-evo-query-skip", "", NULL); + + element = webkit_dom_document_query_selector (document, "blockquote:not([data-evo-query-skip]) + blockquote:not([data-evo-query-skip])", NULL); + if (element) + goto repeat; + + signature: + list = webkit_dom_document_query_selector_all ( + document, "blockquote[data-evo-query-skip]", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (node), "data-evo-query-skip"); + } + g_clear_object (&list); + + if (!deleted_content) + return; + + /* Replace the corrupted signatures with the right one. */ + element = webkit_dom_document_query_selector ( + document, ".-x-evo-signature-wrapper + .-x-evo-signature-wrapper", NULL); + if (element) { + WebKitDOMElement *right_signature; + + right_signature = webkit_dom_document_fragment_query_selector ( + deleted_content, ".-x-evo-signature-wrapper", NULL); + remove_node (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element))); + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (right_signature), TRUE, NULL), + WEBKIT_DOM_NODE (element), + NULL); + } +} + +/* This will fix the structure after the situations where some text + * inside the quoted content is selected and afterwards deleted with + * BackSpace or Delete. */ +void +e_editor_dom_body_key_up_event_process_backspace_or_delete (EEditorPage *editor_page, + gboolean delete) +{ + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *parent, *node; + gint level; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (e_editor_page_get_html_mode (editor_page)) { + if (!delete) { + e_editor_dom_selection_save (editor_page); + e_editor_dom_merge_siblings_if_necessary (editor_page, NULL); + e_editor_dom_selection_restore (editor_page); + } + return; + } + + document = e_editor_page_get_document (editor_page); + e_editor_dom_disable_quote_marks_select (editor_page); + /* Remove empty blocks if presented. */ + remove_empty_blocks (document); + + e_editor_dom_selection_save (editor_page); + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + /* If we deleted a selection the caret will be inside the quote marks, fix it. */ + parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker)); + if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character")) { + parent = webkit_dom_node_get_parent_node (parent); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE (selection_end_marker), + webkit_dom_node_get_next_sibling (parent), + NULL); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE (selection_start_marker), + webkit_dom_node_get_next_sibling (parent), + NULL); + } + + /* Under some circumstances we will end with block inside the citation + * that has the quote marks removed and we have to reinsert them back. */ + level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker)); + node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)); + if (level > 0 && node && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) { + WebKitDOMElement *block; + + block = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child ( + WEBKIT_DOM_NODE (selection_start_marker))); + + e_editor_dom_remove_quoting_from_element (block); + if (webkit_dom_element_has_attribute (block, "data-evo-paragraph")) { + gint length, word_wrap_length; + + word_wrap_length = e_editor_page_get_word_wrap_length (editor_page); + length = word_wrap_length - 2 * level; + block = e_editor_dom_wrap_paragraph_length (editor_page, block, length); + webkit_dom_node_normalize (WEBKIT_DOM_NODE (block)); + } + e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, block, level); + } else if (level > 0 && !node) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (selection_start_marker)); + if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) && + element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted") && + !webkit_dom_node_get_previous_sibling (prev_sibling)) { + webkit_dom_node_append_child ( + webkit_dom_node_get_parent_node (parent), + WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)), + NULL); + } + } + + e_editor_dom_merge_siblings_if_necessary (editor_page, NULL); + + e_editor_dom_selection_restore (editor_page); + e_editor_dom_force_spell_check_for_current_paragraph (editor_page); +} + +void +e_editor_dom_body_key_up_event_process_return_key (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *parent; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + /* If the return is pressed in an unordered list in plain text mode + * the caret is moved to the "*" character before the newly inserted + * item. It looks like it is not enough that the item has BR element + * inside, but we have to again use the zero width space character + * to fix the situation. */ + if (e_editor_page_get_html_mode (editor_page)) + return; + + document = e_editor_page_get_document (editor_page); + e_editor_dom_selection_save (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker)); + if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent) || + !WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (webkit_dom_node_get_parent_node (parent))) { + e_editor_dom_selection_restore (editor_page); + return; + } + + if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)) && + (!webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)) || + WEBKIT_DOM_IS_HTML_BR_ELEMENT (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker))))) + webkit_dom_element_insert_adjacent_text ( + WEBKIT_DOM_ELEMENT (parent), + "afterbegin", + UNICODE_ZERO_WIDTH_SPACE, + NULL); + + e_editor_dom_selection_restore (editor_page); +} + +static void +body_keyup_event_cb (WebKitDOMElement *element, + WebKitDOMUIEvent *event, + EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + glong key_code; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element)); + if (!e_editor_page_is_composition_in_progress (editor_page)) + e_editor_dom_register_input_event_listener_on_body (editor_page); + + if (!e_editor_dom_selection_is_collapsed (editor_page)) + return; + + key_code = webkit_dom_ui_event_get_key_code (event); + if (key_code == HTML_KEY_CODE_BACKSPACE || key_code == HTML_KEY_CODE_DELETE) { + e_editor_dom_body_key_up_event_process_backspace_or_delete (editor_page, key_code == HTML_KEY_CODE_DELETE); + + /* The content was wrapped and the coordinates + * of caret could be changed, so renew them. But + * only do that when we are not redoing a history + * event, otherwise it would modify the history. */ + if (e_editor_page_get_renew_history_after_coordinates (editor_page)) { + EEditorHistoryEvent *ev = NULL; + EEditorUndoRedoManager *manager; + + manager = e_editor_page_get_undo_redo_manager (editor_page); + ev = e_editor_undo_redo_manager_get_current_history_event (manager); + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + } + + e_editor_page_emit_content_changed (editor_page); + } else if (key_code == HTML_KEY_CODE_CONTROL) + dom_set_links_active (document, FALSE); + else if (key_code == HTML_KEY_CODE_RETURN) + e_editor_dom_body_key_up_event_process_return_key (editor_page); +} + +static void +fix_structure_after_pasting_multiline_content (WebKitDOMNode *node) +{ + WebKitDOMNode *first_child, *parent; + + /* When pasting content that does not contain just the + * one line text WebKit inserts all the content after the + * first line into one element. So we have to take it out + * of this element and insert it after that element. */ + parent = webkit_dom_node_get_parent_node (node); + if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) + return; + first_child = webkit_dom_node_get_first_child (parent); + while (first_child) { + WebKitDOMNode *next_child = + webkit_dom_node_get_next_sibling (first_child); + if (webkit_dom_node_has_child_nodes (first_child)) + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + first_child, + parent, + NULL); + first_child = next_child; + } +} + +static gboolean +delete_hidden_space (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker, *selection_end_marker, *block; + gint citation_level; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + document = e_editor_page_get_document (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + if (!selection_start_marker || !selection_end_marker) + return FALSE; + + block = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child ( + WEBKIT_DOM_NODE (selection_start_marker))); + + citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker)); + + if (selection_start_marker && citation_level > 0) { + EEditorUndoRedoManager *manager; + EEditorHistoryEvent *ev = NULL; + WebKitDOMNode *node; + WebKitDOMDocumentFragment *fragment; + + manager = e_editor_page_get_undo_redo_manager (editor_page); + + node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)); + if (!(WEBKIT_DOM_IS_ELEMENT (node) && + element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted"))) + return FALSE; + + node = webkit_dom_node_get_previous_sibling (node); + if (!(WEBKIT_DOM_IS_ELEMENT (node) && + element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br"))) + return FALSE; + + node = webkit_dom_node_get_previous_sibling (node); + if (!(WEBKIT_DOM_IS_ELEMENT (node) && + webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-hidden-space"))) + return FALSE; + + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_DELETE; + + e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y); + + remove_node (node); + + e_editor_dom_wrap_and_quote_element (editor_page, block); + + fragment = webkit_dom_document_create_document_fragment (document); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (fragment), + WEBKIT_DOM_NODE ( + webkit_dom_document_create_text_node (document, " ")), + NULL); + ev->data.fragment = g_object_ref (fragment); + + e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y); + + e_editor_undo_redo_manager_insert_history_event (manager, ev); + + return TRUE; + } + + return FALSE; +} + +static gboolean +caret_is_on_the_line_beginning_html (WebKitDOMDocument *document) +{ + gboolean ret_val = FALSE; + WebKitDOMDOMWindow *dom_window = NULL; + WebKitDOMDOMSelection *dom_selection = NULL; + WebKitDOMRange *tmp_range = NULL, *actual_range = NULL; + + dom_window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (dom_window); + + actual_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + webkit_dom_dom_selection_modify (dom_selection, "move", "left", "lineBoundary"); + + tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_START_TO_START, actual_range, NULL) == 0) + ret_val = TRUE; + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, actual_range); + + g_clear_object (&tmp_range); + g_clear_object (&actual_range); + + g_clear_object (&dom_window); + g_clear_object (&dom_selection); + + return ret_val; +} + +static gboolean +is_empty_quoted_element (WebKitDOMElement *element) +{ + WebKitDOMNode *node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)); + + if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted")) + return FALSE; + + if (!(node = webkit_dom_node_get_next_sibling (node))) + return TRUE; + + if (WEBKIT_DOM_IS_TEXT (node)) { + gchar *content; + + content = webkit_dom_node_get_text_content (node); + if (content && *content) { + g_free (content); + return FALSE; + } + + g_free (content); + return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE; + } + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) + return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE; + + if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-start-marker")) + return FALSE; + + if (!(node = webkit_dom_node_get_next_sibling (node))) + return FALSE; + + if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-end-marker")) + return FALSE; + + if (!(node = webkit_dom_node_get_next_sibling (node))) + return TRUE; + + if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) { + if (WEBKIT_DOM_IS_TEXT (node)) { + gchar *content; + + content = webkit_dom_node_get_text_content (node); + if (content && *content) { + g_free (content); + return FALSE; + } + + g_free (content); + return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE; + } + return FALSE; + } + + if (!(node = webkit_dom_node_get_next_sibling (node))) + return TRUE; + + return TRUE; +} + +gboolean +e_editor_dom_move_quoted_block_level_up (EEditorPage *editor_page) +{ + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *block; + EEditorHistoryEvent *ev = NULL; + EEditorUndoRedoManager *manager; + gboolean html_mode; + gint citation_level, success = FALSE; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE); + + document = e_editor_page_get_document (editor_page); + manager = e_editor_page_get_undo_redo_manager (editor_page); + html_mode = e_editor_page_get_html_mode (editor_page); + + selection_start_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + selection_end_marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + + if (!selection_start_marker || !selection_end_marker) + return FALSE; + + block = e_editor_dom_get_parent_block_node_from_child (WEBKIT_DOM_NODE (selection_start_marker)); + + citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker)); + + if (selection_start_marker && citation_level > 0) { + if (webkit_dom_element_query_selector ( + WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) { + + WebKitDOMNode *prev_sibling; + + webkit_dom_node_normalize (block); + + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (selection_start_marker)); + + if (!prev_sibling) { + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent)) + prev_sibling = webkit_dom_node_get_previous_sibling (parent); + } + + if (WEBKIT_DOM_IS_ELEMENT (prev_sibling)) + success = element_has_class ( + WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted"); + + /* We really have to be in the beginning of paragraph and + * not on the beginning of some line in the paragraph */ + if (success && webkit_dom_node_get_previous_sibling (prev_sibling)) + success = FALSE; + } + + if (html_mode) { + webkit_dom_node_normalize (block); + + success = caret_is_on_the_line_beginning_html (document); + if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker))) + block = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)); + } + } + + if (!success) + return FALSE; + + if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) { + ev = g_new0 (EEditorHistoryEvent, 1); + ev->type = HISTORY_UNQUOTE; + + e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y); + ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error (block, TRUE, NULL)); + } + + if (citation_level == 1) { + gboolean is_empty_quoted_block = FALSE; + gchar *inner_html = NULL; + WebKitDOMElement *paragraph, *element; + + if (WEBKIT_DOM_IS_ELEMENT (block)) { + is_empty_quoted_block = is_empty_quoted_element (WEBKIT_DOM_ELEMENT (block)); + inner_html = webkit_dom_element_get_inner_html (WEBKIT_DOM_ELEMENT (block)); + webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (block), "-x-evo-to-remove"); + } + + paragraph = e_editor_dom_insert_new_line_into_citation (editor_page, inner_html); + g_free (inner_html); + + if (paragraph) { + if (!(webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-start-marker", NULL))) + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (paragraph), + WEBKIT_DOM_NODE (selection_start_marker), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (paragraph)), + NULL); + + if (!(webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-end-marker", NULL))) + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (paragraph), + WEBKIT_DOM_NODE (selection_end_marker), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (paragraph)), + NULL); + + e_editor_dom_remove_quoting_from_element (paragraph); + e_editor_dom_remove_wrapping_from_element (paragraph); + + /* Moving PRE block from citation to body */ + if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block) && !is_empty_quoted_block) { + WebKitDOMElement *pre; + WebKitDOMNode *child; + + pre = webkit_dom_document_create_element (document, "pre", NULL); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (paragraph)), + WEBKIT_DOM_NODE (pre), + WEBKIT_DOM_NODE (paragraph), + NULL); + + while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)))) + webkit_dom_node_append_child (WEBKIT_DOM_NODE (pre), child, NULL); + + remove_node (WEBKIT_DOM_NODE (paragraph)); + paragraph = pre; + } + } + + if (block) + remove_node (block); + + while ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-to-remove"))) + remove_node (WEBKIT_DOM_NODE (element)); + + if (paragraph) + remove_node_if_empty ( + webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (paragraph))); + } + + if (citation_level > 1) { + WebKitDOMNode *parent; + + if (html_mode) { + webkit_dom_node_insert_before ( + block, + WEBKIT_DOM_NODE (selection_start_marker), + webkit_dom_node_get_first_child (block), + NULL); + webkit_dom_node_insert_before ( + block, + WEBKIT_DOM_NODE (selection_end_marker), + webkit_dom_node_get_first_child (block), + NULL); + + } + + e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block)); + e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block)); + + parent = webkit_dom_node_get_parent_node (block); + + if (!webkit_dom_node_get_previous_sibling (block)) { + /* Currect block is in the beginning of citation, just move it + * before the citation where already is */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + block, + parent, + NULL); + } else if (!webkit_dom_node_get_next_sibling (block)) { + /* Currect block is at the end of the citation, just move it + * after the citation where already is */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + block, + webkit_dom_node_get_next_sibling (parent), + NULL); + } else { + /* Current block is somewhere in the middle of the citation + * so we need to split the citation and insert the block into + * the citation that is one level lower */ + WebKitDOMNode *clone, *child; + + clone = webkit_dom_node_clone_node_with_error (parent, FALSE, NULL); + + /* Move nodes that are after the currect block into the + * new blockquote */ + child = webkit_dom_node_get_next_sibling (block); + while (child) { + WebKitDOMNode *next = webkit_dom_node_get_next_sibling (child); + webkit_dom_node_append_child (clone, child, NULL); + child = next; + } + + clone = webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + clone, + webkit_dom_node_get_next_sibling (parent), + NULL); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + block, + clone, + NULL); + } + + e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (block)); + } + + remove_empty_blocks (document); + + if (ev) { + e_editor_dom_selection_get_coordinates (editor_page, + &ev->after.start.x, + &ev->after.start.y, + &ev->after.end.x, + &ev->after.end.y); + e_editor_undo_redo_manager_insert_history_event (manager, ev); + } + + return success; +} + +static gboolean +prevent_from_deleting_last_element_in_body (WebKitDOMDocument *document) +{ + gboolean ret_val = FALSE; + WebKitDOMHTMLElement *body; + WebKitDOMNode *node; + + body = webkit_dom_document_get_body (document); + + node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + if (!node || !webkit_dom_node_get_next_sibling (node)) { + gchar *content; + + content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body)); + + if (!content || !*content) + ret_val = TRUE; + + g_free (content); + + if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL)) + ret_val = FALSE; + } + + return ret_val; +} + +static void +insert_quote_symbols (WebKitDOMDocument *document, + WebKitDOMHTMLElement *element, + gint quote_level) +{ + gchar *quotation; + WebKitDOMElement *quote_element; + + if (!WEBKIT_DOM_IS_ELEMENT (element)) + return; + + quotation = get_quotation_for_level (quote_level); + + quote_element = webkit_dom_document_create_element (document, "span", NULL); + element_add_class (quote_element, "-x-evo-quoted"); + + webkit_dom_element_set_inner_html (quote_element, quotation, NULL); + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (element), + WEBKIT_DOM_NODE (quote_element), + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)), + NULL); + + g_free (quotation); +} + +static void +quote_node (WebKitDOMDocument *document, + WebKitDOMNode *node, + gint quote_level) +{ + WebKitDOMNode *parent, *next_sibling; + + /* Don't quote when we are not in citation */ + if (quote_level == 0) + return; + + if (WEBKIT_DOM_IS_COMMENT (node)) + return; + + if (WEBKIT_DOM_IS_ELEMENT (node)) { + insert_quote_symbols (document, WEBKIT_DOM_HTML_ELEMENT (node), quote_level); + return; + } + + next_sibling = webkit_dom_node_get_next_sibling (node); + + /* Skip the BR between first blockquote and pre */ + if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling)) + return; + + parent = webkit_dom_node_get_parent_node (node); + + insert_quote_symbols ( + document, WEBKIT_DOM_HTML_ELEMENT (parent), quote_level); +} + +static void +insert_quote_symbols_before_node (WebKitDOMDocument *document, + WebKitDOMNode *node, + gint quote_level, + gboolean is_html_node) +{ + gboolean skip, wrap_br; + gchar *quotation; + WebKitDOMElement *element; + + quotation = get_quotation_for_level (quote_level); + element = webkit_dom_document_create_element (document, "SPAN", NULL); + element_add_class (element, "-x-evo-quoted"); + webkit_dom_element_set_inner_html (element, quotation, NULL); + + /* Don't insert temporary BR before BR that is used for wrapping */ + skip = WEBKIT_DOM_IS_HTML_BR_ELEMENT (node); + wrap_br = element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br"); + skip = skip && wrap_br; + + if (is_html_node && !skip) { + WebKitDOMElement *new_br; + + new_br = webkit_dom_document_create_element (document, "br", NULL); + element_add_class (new_br, "-x-evo-temp-br"); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (new_br), + node, + NULL); + } + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + node, + NULL); + + if (is_html_node && !wrap_br) + remove_node (node); + + g_free (quotation); +} + +static gboolean +check_if_suppress_next_node (WebKitDOMNode *node) +{ + if (!node) + return FALSE; + + if (node && WEBKIT_DOM_IS_ELEMENT (node)) + if (e_editor_dom_is_selection_position_node (node)) + if (!webkit_dom_node_get_previous_sibling (node)) + return FALSE; + + return TRUE; +} + +static void +quote_br_node (WebKitDOMNode *node, + gint quote_level) +{ + gchar *quotation, *content; + + quotation = get_quotation_for_level (quote_level); + + content = g_strconcat ( + "", + quotation, + "
", + NULL); + + webkit_dom_element_set_outer_html ( + WEBKIT_DOM_ELEMENT (node), content, NULL); + + g_free (content); + g_free (quotation); +} + +static void +quote_plain_text_recursive (WebKitDOMDocument *document, + WebKitDOMNode *block, + WebKitDOMNode *start_node, + gint quote_level) +{ + gboolean skip_node = FALSE; + gboolean move_next = FALSE; + gboolean suppress_next = FALSE; + gboolean is_html_node = FALSE; + gboolean next = FALSE; + WebKitDOMNode *node, *next_sibling, *prev_sibling; + + node = webkit_dom_node_get_first_child (block); + + while (node) { + skip_node = FALSE; + move_next = FALSE; + is_html_node = FALSE; + + if (WEBKIT_DOM_IS_COMMENT (node) || + WEBKIT_DOM_IS_HTML_META_ELEMENT (node) || + WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node) || + WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) { + + move_next = TRUE; + goto next_node; + } + + prev_sibling = webkit_dom_node_get_previous_sibling (node); + next_sibling = webkit_dom_node_get_next_sibling (node); + + if (WEBKIT_DOM_IS_TEXT (node)) { + /* Start quoting after we are in blockquote */ + if (quote_level > 0 && !suppress_next) { + /* When quoting text node, we are wrappering it and + * afterwards replacing it with that wrapper, thus asking + * for next_sibling after quoting will return NULL bacause + * that node don't exist anymore */ + quote_node (document, node, quote_level); + node = next_sibling; + skip_node = TRUE; + } + + goto next_node; + } + + if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node))) + goto next_node; + + if (e_editor_dom_is_selection_position_node (node)) { + /* If there is collapsed selection in the beginning of line + * we cannot suppress first text that is after the end of + * selection */ + suppress_next = check_if_suppress_next_node (prev_sibling); + if (suppress_next) + next = FALSE; + move_next = TRUE; + goto next_node; + } + + if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) && + webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (node)) != 0) + goto with_children; + + /* Even in plain text mode we can have some basic html element + * like anchor and others. When Forwaring e-mail as Quoted EMFormat + * generates header that contains tags (bold font). + * We have to treat these elements separately to avoid + * modifications of theirs inner texts */ + is_html_node = + WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) || + element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") || + element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") || + element_has_tag (WEBKIT_DOM_ELEMENT (node), "u") || + element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span"); + + if (is_html_node) { + gboolean wrap_br; + + wrap_br = + prev_sibling && + WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) && + element_has_class ( + WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br"); + + if (!prev_sibling || wrap_br) { + insert_quote_symbols_before_node ( + document, node, quote_level, FALSE); + if (!prev_sibling && next_sibling && WEBKIT_DOM_IS_TEXT (next_sibling)) + suppress_next = TRUE; + } + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) && !wrap_br) + insert_quote_symbols_before_node ( + document, prev_sibling, quote_level, TRUE); + + move_next = TRUE; + goto next_node; + } + + /* If element doesn't have children, we can quote it */ + if (e_editor_dom_node_is_citation_node (node)) { + /* Citation with just text inside */ + quote_node (document, node, quote_level + 1); + + move_next = TRUE; + goto next_node; + } + + if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) { + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) { + move_next = TRUE; + goto next_node; + } + goto not_br; + } else if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-first-br") || + element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-last-br")) { + quote_br_node (node, quote_level); + node = next_sibling; + skip_node = TRUE; + goto next_node; + } + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling)) { + quote_br_node (prev_sibling, quote_level); + node = next_sibling; + skip_node = TRUE; + goto next_node; + } + + if (!prev_sibling && !next_sibling) { + WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node); + + if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) || + WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) || + (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) && + !e_editor_dom_node_is_citation_node (parent))) { + insert_quote_symbols_before_node ( + document, node, quote_level, FALSE); + + goto next_node; + } + } + + if (e_editor_dom_node_is_citation_node (prev_sibling)) { + insert_quote_symbols_before_node ( + document, node, quote_level, FALSE); + goto next_node; + } + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node) && + !next_sibling && WEBKIT_DOM_IS_ELEMENT (prev_sibling) && + e_editor_dom_is_selection_position_node (prev_sibling)) { + insert_quote_symbols_before_node ( + document, node, quote_level, FALSE); + goto next_node; + } + + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) { + if (!prev_sibling && !next_sibling) { + insert_quote_symbols_before_node ( + document, node, quote_level, FALSE); + } else + move_next = TRUE; + goto next_node; + } + + not_br: + quote_node (document, node, quote_level); + + move_next = TRUE; + goto next_node; + + with_children: + if (e_editor_dom_node_is_citation_node (node)) { + /* Go deeper and increase level */ + quote_plain_text_recursive ( + document, node, start_node, quote_level + 1); + move_next = TRUE; + } else { + quote_plain_text_recursive ( + document, node, start_node, quote_level); + move_next = TRUE; + } + next_node: + if (next) { + suppress_next = FALSE; + next = FALSE; + } + + if (suppress_next) + next = TRUE; + + if (!skip_node) { + /* Move to next node */ + if (!move_next && webkit_dom_node_has_child_nodes (node)) { + node = webkit_dom_node_get_first_child (node); + } else if (webkit_dom_node_get_next_sibling (node)) { + node = webkit_dom_node_get_next_sibling (node); + } else { + return; + } + } + } +} + +WebKitDOMElement * +e_editor_dom_quote_plain_text_element (EEditorPage *editor_page, + WebKitDOMElement *element) +{ + WebKitDOMDocument *document; + WebKitDOMNode *element_clone; + WebKitDOMHTMLCollection *collection = NULL; + gint ii, level; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL); + + document = e_editor_page_get_document (editor_page); + element_clone = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), TRUE, NULL); + level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element)); + + /* Remove old quote characters if the exists */ + collection = webkit_dom_element_get_elements_by_class_name_as_html_collection ( + WEBKIT_DOM_ELEMENT (element_clone), "-x-evo-quoted"); + for (ii = webkit_dom_html_collection_get_length (collection); ii--;) + remove_node (webkit_dom_html_collection_item (collection, ii)); + g_clear_object (&collection); + + webkit_dom_node_normalize (element_clone); + quote_plain_text_recursive ( + document, element_clone, element_clone, level); + + /* Replace old element with one, that is quoted */ + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + element_clone, + WEBKIT_DOM_NODE (element), + NULL); + + return WEBKIT_DOM_ELEMENT (element_clone); +} + +/* + * dom_quote_plain_text: + * + * Quote text inside citation blockquotes in plain text mode. + * + * As this function is cloning and replacing all citation blockquotes keep on + * mind that any pointers to nodes inside these blockquotes will be invalidated. + */ +static WebKitDOMElement * +dom_quote_plain_text (WebKitDOMDocument *document) +{ + WebKitDOMHTMLElement *body; + WebKitDOMNode *body_clone; + WebKitDOMNamedNodeMap *attributes = NULL; + WebKitDOMNodeList *list = NULL; + WebKitDOMElement *element; + gint ii; + gulong attributes_length; + + /* Check if the document is already quoted */ + element = webkit_dom_document_query_selector ( + document, ".-x-evo-quoted", NULL); + if (element) + return NULL; + + body = webkit_dom_document_get_body (document); + body_clone = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (body), TRUE, NULL); + + /* Clean unwanted spaces before and after blockquotes */ + list = webkit_dom_element_query_selector_all ( + WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL); + for (ii = webkit_dom_node_list_get_length (list); ii--;) { + WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii); + WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote); + WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote); + + if (prev_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling)) + remove_node (prev_sibling); + + if (next_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling)) + remove_node (next_sibling); + + if (webkit_dom_node_has_child_nodes (blockquote)) { + WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote); + if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)) + remove_node (child); + } + } + g_clear_object (&list); + + webkit_dom_node_normalize (body_clone); + quote_plain_text_recursive (document, body_clone, body_clone, 0); + + /* Copy attributes */ + attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body)); + attributes_length = webkit_dom_named_node_map_get_length (attributes); + for (ii = 0; ii < attributes_length; ii++) { + gchar *name, *value; + WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii); + + name = webkit_dom_attr_get_name (WEBKIT_DOM_ATTR (node)); + value = webkit_dom_node_get_node_value (node); + + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL); + + g_free (name); + g_free (value); + } + g_clear_object (&attributes); + + /* Replace old BODY with one, that is quoted */ + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)), + body_clone, + WEBKIT_DOM_NODE (body), + NULL); + + return WEBKIT_DOM_ELEMENT (body_clone); +} + +/* + * dom_dequote_plain_text: + * + * Dequote already quoted plain text in editor. + * Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise + * it's not working. + */ +static void +dom_dequote_plain_text (WebKitDOMDocument *document) +{ + WebKitDOMNodeList *paragraphs = NULL; + gint ii; + + paragraphs = webkit_dom_document_query_selector_all ( + document, "blockquote[type=cite]", NULL); + for (ii = webkit_dom_node_list_get_length (paragraphs); ii--;) { + WebKitDOMElement *element; + + element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii)); + + if (e_editor_dom_node_is_citation_node (WEBKIT_DOM_NODE (element))) + e_editor_dom_remove_quoting_from_element (element); + } + g_clear_object (¶graphs); +} + +static gboolean +create_anchor_for_link (const GMatchInfo *info, + GString *res, + gpointer data) +{ + gboolean link_surrounded, ending_with_nbsp = FALSE; + gint offset = 0, truncate_from_end = 0; + gint match_start, match_end; + gchar *match; + const gchar *end_of_match = NULL; + const gchar *nbsp_match = NULL; + + match = g_match_info_fetch (info, 0); + g_match_info_fetch_pos (info, 0, &match_start, &match_end); + + if (g_str_has_suffix (match, " ")) { + ending_with_nbsp = TRUE; + truncate_from_end = 6; + } + + if (g_str_has_prefix (match, " ")) + offset += 6; + + end_of_match = match + match_end - match_start - 1; + /* Taken from camel-url-scanner.c */ + /* URLs are extremely unlikely to end with any punctuation, so + * strip any trailing punctuation off from link and put it after + * the link. Do the same for any closing double-quotes as well. */ + while (end_of_match && end_of_match != match && strchr (URL_INVALID_TRAILING_CHARS, *end_of_match)) { + truncate_from_end++; + end_of_match--; + } + end_of_match++; + + link_surrounded = + g_str_has_suffix (res->str, "<"); + + if (link_surrounded) { + if (end_of_match && *end_of_match && strlen (match) > strlen (end_of_match) + 3) + link_surrounded = link_surrounded && g_str_has_prefix (end_of_match - 3, ">"); + else + link_surrounded = link_surrounded && g_str_has_suffix (match, ">"); + + if (link_surrounded) { + truncate_from_end += 4; + end_of_match -= 4; + } + } + + /* The ending ';' was counted when looking for the invalid trailing characters, substract it. */ + if (link_surrounded || ending_with_nbsp) { + truncate_from_end -= 1; + end_of_match += 1; + } + + /* If there is non-breaking space in the match, remove it and everything + * after it from the match */ + if (!g_str_has_prefix (match, " ") && !g_str_has_suffix (match, " ") && (nbsp_match = strstr (match, " "))) { + glong after_nbsp_length = g_utf8_strlen (nbsp_match, -1); + truncate_from_end = after_nbsp_length; + end_of_match -= after_nbsp_length; + if (link_surrounded) + end_of_match += 4; + } + + g_string_append (res, " 0) + g_string_truncate (res, res->len - truncate_from_end); + + g_string_append (res, "\">"); + g_string_append (res, match + offset); + if (truncate_from_end > 0) + g_string_truncate (res, res->len - truncate_from_end); + + g_string_append (res, ""); + + if (truncate_from_end > 0) + g_string_append (res, end_of_match); + + if (ending_with_nbsp) + g_string_append (res, " "); + + g_free (match); + + return FALSE; +} + +static gboolean +replace_to_nbsp (const GMatchInfo *info, + GString *res) +{ + gchar *match; + gint ii = 0; + + match = g_match_info_fetch (info, 0); + + while (match[ii] != '\0') { + if (match[ii] == ' ') { + /* Alone spaces or spaces before/after tabulator. */ + g_string_append (res, " "); + } else if (match[ii] == '\t') { + /* Replace tabs with their WebKit HTML representation. */ + g_string_append (res, "\t"); + } + + ii++; + } + + g_free (match); + + return FALSE; +} + +static gboolean +surround_links_with_anchor (const gchar *text) +{ + return (strstr (text, "http") || strstr (text, "ftp") || + strstr (text, "www") || strstr (text, "@")); +} + +static void +append_new_block (WebKitDOMElement *parent, + WebKitDOMElement **block) +{ + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (*block), + NULL); + + *block = NULL; +} + +static WebKitDOMElement * +create_and_append_new_block (EEditorPage *editor_page, + WebKitDOMElement *parent, + WebKitDOMElement *block_template, + const gchar *content) +{ + WebKitDOMElement *block; + + g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL); + + block = WEBKIT_DOM_ELEMENT (webkit_dom_node_clone_node_with_error ( + WEBKIT_DOM_NODE (block_template), FALSE, NULL)); + + webkit_dom_element_set_inner_html (block, content, NULL); + + append_new_block (parent, &block); + + return block; +} + +static void +append_citation_mark (WebKitDOMDocument *document, + WebKitDOMElement *parent, + const gchar *citation_mark_text) +{ + WebKitDOMText *text; + + text = webkit_dom_document_create_text_node (document, citation_mark_text); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (text), + NULL); +} + +static void +replace_selection_markers (gchar **text) +{ + if (!text) + return; + + if (strstr (*text, "##SELECTION_START##")) { + GString *tmp; + + tmp = e_str_replace_string ( + *text, + "##SELECTION_START##", + ""); + + g_free (*text); + *text = g_string_free (tmp, FALSE); + } + + if (strstr (*text, "##SELECTION_END##")) { + GString *tmp; + + tmp = e_str_replace_string ( + *text, + "##SELECTION_END##", + ""); + + g_free (*text); + *text = g_string_free (tmp, FALSE); + } +} + +static GString * +remove_new_lines_around_citations (const gchar *input) +{ + GString *str = NULL; + const gchar *p, *next; + + str = g_string_new (""); + + /* Remove the new lines around citations: + * Replace

##CITATION_START## with
##CITATION_START## + * Replace ##CITATION_START##

with ##CITATION_START##
+ * Replace ##CITATION_END##

with ##CITATION_END##
+ * Replace
##CITATION_END## with ##CITATION_END## + * Replace
##CITATION_START## with ##CITATION_START## */ + p = input; + while (next = strstr (p, "##CITATION_"), next) { + gchar citation_type = 0; + + if (p < next) + g_string_append_len (str, p, next - p); + + if (next + 11) + citation_type = next[11]; + /* ##CITATION_START## */ + if (citation_type == 'S') { + if (g_str_has_suffix (str->str, "

") || + g_str_has_suffix (str->str, "
")) + g_string_truncate (str, str->len - 4); + + if (g_str_has_prefix (next + 11, "START##

")) { + g_string_append (str, "##CITATION_START##
"); + p = next + 26; + continue; + } + } else if (citation_type == 'E') { + if (g_str_has_suffix (str->str, "
")) + g_string_truncate (str, str->len - 4); + + if (g_str_has_prefix (next + 11, "END##

")) { + g_string_append (str, "##CITATION_END##
"); + p = next + 24; + continue; + } + } + + g_string_append (str, "##CITATION_"); + + p = next + 11; + } + + g_string_append (str, p); + + if (camel_debug ("webkit:editor")) { + printf ("EWebKitContentEditor - %s\n", G_STRFUNC); + printf ("\toutput: '%s'\n", str->str); + } + + return str; +} + +static GString * +replace_citation_marks_to_citations (const gchar *input) +{ + GString *str = NULL; + const gchar *p, *next; + + str = g_string_new (""); + + /* Replaces text markers with actual HTML blockquotes */ + p = input; + while (next = strstr (p, "##CITATION_"), next) { + gchar citation_type = 0; + + if (p < next) + g_string_append_len (str, p, next - p); + + if (next + 11) + citation_type = next[11]; + /* ##CITATION_START## */ + if (citation_type == 'S') { + g_string_append (str, "
"); + p = next + 18; + } else if (citation_type == 'E') { + g_string_append (str, "
"); + p = next + 16; + } else + p = next + 11; + } + + g_string_append (str, p); + + return str; +} + +/* This parses the HTML code (that contains just text,   and BR elements) + * into blocks. + * HTML code in that format we can get by taking innerText from some element, + * setting it to another one and finally getting innerHTML from it */ +static void +parse_html_into_blocks (EEditorPage *editor_page, + WebKitDOMElement *parent, + WebKitDOMElement *passed_block_template, + const gchar *input) +{ + gboolean has_citation = FALSE, processing_last = FALSE; + const gchar *prev_token, *next_token; + const gchar *next_br_token = NULL, *next_citation_token = NULL; + GString *html = NULL; + GRegex *regex_nbsp = NULL, *regex_link = NULL, *regex_email = NULL; + WebKitDOMDocument *document; + WebKitDOMElement *block_template = passed_block_template; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!(input && *input)) + return; + + document = e_editor_page_get_document (editor_page); + webkit_dom_element_set_inner_html (parent, "", NULL); + + if (!block_template) { + gboolean use_paragraphs; + GSettings *settings; + + settings = e_util_ref_settings ("org.gnome.evolution.mail"); + + use_paragraphs = g_settings_get_boolean ( + settings, "composer-wrap-quoted-text-in-replies"); + + if (use_paragraphs) + block_template = e_editor_dom_get_paragraph_element (editor_page, -1, 0); + else + block_template = webkit_dom_document_create_element (document, "pre", NULL); + + g_object_unref (settings); + } + + /* Replace the tabulators with SPAN elements that corresponds to them. + * If not inserting the content into the PRE element also replace single + * spaces on the beginning of line, 2+ spaces and with non breaking + * spaces. */ + if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block_template)) + regex_nbsp = g_regex_new ("\x9", 0, 0, NULL); + else + regex_nbsp = g_regex_new ("^\\s{1}|\\s{2,}|\x9|\\s$", 0, 0, NULL); + + if (camel_debug ("webkit:editor")) { + printf ("EWebKitContentEditor - %s\n", G_STRFUNC); + printf ("\tinput: '%s'\n", input); + } + html = remove_new_lines_around_citations (input); + + prev_token = html->str; + next_br_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "
") : NULL; + next_citation_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "##CITATION_") : NULL; + if (next_br_token) { + if (next_citation_token) + next_token = next_br_token < next_citation_token ? next_br_token : next_citation_token; + else + next_token = next_br_token; + } else { + next_token = next_citation_token; + } + processing_last = !next_token; + + while (next_token || processing_last) { + const gchar *citation_start = NULL, *citation_end = NULL; + const gchar *rest = NULL, *with_br = NULL; + gchar *to_process = NULL, *to_insert = NULL; + guint to_insert_start = 0, to_insert_end = 0; + + if (!next_token) { + to_process = g_strdup (prev_token); + processing_last = TRUE; + } else if ((to_process = g_utf8_substring (prev_token, 0, g_utf8_pointer_to_offset (prev_token, next_token))) && + !*to_process && !processing_last) { + g_free (to_process); + to_process = g_strdup (next_token); + next_token = NULL; + processing_last = TRUE; + } + + if (camel_debug ("webkit:editor")) + printf ("\tto_process: '%s'\n", to_process); + + if (to_process && !*to_process && processing_last) { + g_free (to_process); + to_process = g_strdup (next_token); + next_token = NULL; + } + + to_insert_end = g_utf8_strlen (to_process, -1); + + if ((with_br = strstr (to_process, "
"))) { + if (with_br == to_process) + to_insert_start += 4; + } + + if ((citation_start = strstr (to_process, "##CITATION_START"))) { + if (with_br && citation_start == with_br + 4) + to_insert_start += 18; /* + ## */ + else if (!with_br && citation_start == to_process) + to_insert_start += 18; /* + ## */ + else + to_insert_end -= 18; /* + ## */ + has_citation = TRUE; + } + + if ((citation_end = strstr (to_process, "##CITATION_END"))) { + if (citation_end == to_process) + to_insert_start += 16; + else + to_insert_end -= 16; /* + ## */ + } + + /* First BR */ + if (with_br && prev_token == html->str) + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + + if (with_br && citation_start && citation_start == with_br + 4) { + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + + append_citation_mark (document, parent, "##CITATION_START##"); + } else if (!with_br && citation_start == to_process) { + append_citation_mark (document, parent, "##CITATION_START##"); + } + + if (citation_end && citation_end == to_process) { + append_citation_mark (document, parent, "##CITATION_END##"); + } + + if ((to_insert = g_utf8_substring (to_process, to_insert_start, to_insert_end)) && *to_insert) { + gboolean empty = FALSE; + gchar *truncated = g_strdup (to_insert); + gchar *rest_to_insert; + + if (camel_debug ("webkit:editor")) + printf ("\tto_insert: '%s'\n", to_insert); + + empty = !*truncated && strlen (to_insert) > 0; + + rest_to_insert = g_regex_replace_eval ( + regex_nbsp, + empty ? rest : truncated, + -1, + 0, + 0, + (GRegexEvalCallback) replace_to_nbsp, + NULL, + NULL); + g_free (truncated); + + replace_selection_markers (&rest_to_insert); + + if (surround_links_with_anchor (rest_to_insert)) { + gboolean is_email_address = + strstr (rest_to_insert, "@") && + !strstr (rest_to_insert, "://"); + + if (is_email_address && !regex_email) + regex_email = g_regex_new (E_MAIL_PATTERN, 0, 0, NULL); + if (!is_email_address && !regex_link) + regex_link = g_regex_new (URL_PATTERN, 0, 0, NULL); + + truncated = g_regex_replace_eval ( + is_email_address ? regex_email : regex_link, + rest_to_insert, + -1, + 0, + G_REGEX_MATCH_NOTEMPTY, + create_anchor_for_link, + NULL, + NULL); + + g_free (rest_to_insert); + rest_to_insert = truncated; + } + + create_and_append_new_block ( + editor_page, parent, block_template, rest_to_insert); + + g_free (rest_to_insert); + } else if (to_insert) { + if (!citation_start && (with_br || !citation_end)) + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + else if (citation_end && citation_end == to_process && + next_token && g_str_has_prefix (next_token, "
")) { + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + } + } + + g_free (to_insert); + + if (with_br && citation_start && citation_start != with_br + 4) + append_citation_mark (document, parent, "##CITATION_START##"); + + if (!with_br && citation_start && citation_start != to_process) + append_citation_mark (document, parent, "##CITATION_START##"); + + if (citation_end && citation_end != to_process) + append_citation_mark (document, parent, "##CITATION_END##"); + + g_free (to_process); + + prev_token = next_token; + next_br_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "
") : NULL; + next_citation_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "##CITATION_") : NULL; + if (next_br_token) { + if (next_citation_token) + next_token = next_br_token < next_citation_token ? next_br_token : next_citation_token; + else + next_token = next_br_token; + } else { + next_token = next_citation_token; + } + + if (!next_token && !processing_last) { + if (!prev_token) + break; + + if (g_utf8_strlen (prev_token, -1) > 4) { + next_token = prev_token; + } else { + WebKitDOMNode *child; + + if (g_strcmp0 (prev_token, "
") == 0) + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + + child = webkit_dom_node_get_last_child ( + WEBKIT_DOM_NODE (parent)); + if (child) { + child = webkit_dom_node_get_first_child (child); + if (child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)) { + /* If the processed HTML contained just + * the BR don't overwrite its id. */ + if (!element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-first-br")) + webkit_dom_element_set_id ( + WEBKIT_DOM_ELEMENT (child), + "-x-evo-last-br"); + } + } else { + create_and_append_new_block ( + editor_page, parent, block_template, "
"); + } + break; + } + processing_last = TRUE; + } else if (processing_last && !prev_token && !next_token) { + break; + } + } + + if (has_citation) { + gchar *inner_html; + GString *parsed; + + /* Replace text markers with actual HTML blockquotes */ + inner_html = webkit_dom_element_get_inner_html (parent); + parsed = replace_citation_marks_to_citations (inner_html); + webkit_dom_element_set_inner_html (parent, parsed->str, NULL); + + if (camel_debug ("webkit:editor")) + printf ("\tparsed content: '%s'\n", inner_html); + + g_free (inner_html); + g_string_free (parsed, TRUE); + } else if (camel_debug ("webkit:editor")) { + gchar *inner_html; + + inner_html = webkit_dom_element_get_inner_html (parent); + printf ("\tparsed content: '%s'\n", inner_html); + g_free (inner_html); + } + + g_string_free (html, TRUE); + + if (regex_email != NULL) + g_regex_unref (regex_email); + if (regex_link != NULL) + g_regex_unref (regex_link); + g_regex_unref (regex_nbsp); +} + +void +e_editor_dom_quote_and_insert_text_into_selection (EEditorPage *editor_page, + const gchar *text, + gboolean is_html) +{ + WebKitDOMDocument *document; + WebKitDOMElement *blockquote, *element, *selection_start; + WebKitDOMNode *node; + EEditorHistoryEvent *ev = NULL; + EEditorUndoRedoManager *manager; + gchar *inner_html; + gboolean node_added = FALSE; + + g_return_if_fail (E_IS_EDITOR_PAGE (editor_page)); + + if (!text || !*text) + return; + + document = e_editor_page_get_document (editor_page); + + if (is_html) { + element = webkit_dom_document_create_element (document, "div", NULL); + webkit_dom_element_set_inner_html (element, text, NULL); + } else { + /* This is a trick to escape any HTML characters (like <, > or &). + *