diff --git a/400_xowa/src/gplx/core/net/Http_request_parser.java b/400_xowa/src/gplx/core/net/Http_request_parser.java index a924ca46d..f6e614e66 100644 --- a/400_xowa/src/gplx/core/net/Http_request_parser.java +++ b/400_xowa/src/gplx/core/net/Http_request_parser.java @@ -20,11 +20,10 @@ public class Http_request_parser { private int type, content_length; private byte[] url, protocol, host, user_agent, accept, accept_language, accept_encoding, x_requested_with, cookie, referer, content_type, - content_type_boundary, connection, pragma, cache_control, origin, - upgrade_request, x_host, x_real_ip; + content_type_boundary, connection, pragma, cache_control, origin; private Http_post_data_hash post_data_hash; - private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); private final Btrie_rv trv = new Btrie_rv(); - private final Http_server_wtr server_wtr; private final boolean log; + private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(255); private final Btrie_rv trv = new Btrie_rv(); + private final Http_server_wtr server_wtr; private final boolean log; public Http_request_parser(Http_server_wtr server_wtr, boolean log) {this.server_wtr = server_wtr; this.log = log;} public void Clear() { this.dnt = false; @@ -33,7 +32,6 @@ public class Http_request_parser { = this.accept_language = this.accept_encoding = this.x_requested_with = this.cookie = this.referer = this.content_type = this.content_type_boundary = this.connection = this.pragma = this.cache_control = this.origin - = this.upgrade_request = this.x_host = this.x_real_ip = null; this.post_data_hash = null; } @@ -89,9 +87,9 @@ public class Http_request_parser { case Tid_pragma: this.pragma = Bry_.Mid(line, val_bgn, line_len); break; case Tid_cache_control: this.cache_control = Bry_.Mid(line, val_bgn, line_len); break; case Tid_origin: this.origin = Bry_.Mid(line, val_bgn, line_len); break; - case Tid_upgrade_request: this.upgrade_request = Bry_.Mid(line, val_bgn, line_len); break; - case Tid_x_host: this.x_host = Bry_.Mid(line, val_bgn, line_len); break; - case Tid_x_real_ip: this.x_real_ip = Bry_.Mid(line, val_bgn, line_len); break; + case Tid_upgrade_request: break; + case Tid_x_host: break; + case Tid_x_real_ip: break; case Tid_accept_charset: break; default: throw Err_.new_unhandled(tid); } @@ -157,7 +155,7 @@ public class Http_request_parser { private static final int Tid_get = 1, Tid_post = 2, Tid_host = 3, Tid_user_agent = 4, Tid_accept = 5, Tid_accept_language = 6, Tid_accept_encoding = 7, Tid_dnt = 8 , Tid_x_requested_with = 9, Tid_cookie = 10, Tid_referer = 11, Tid_content_length = 12, Tid_content_type = 13, Tid_connection = 14, Tid_pragma = 15, Tid_cache_control = 16 , Tid_origin = 17, Tid_accept_charset = 188, Tid_upgrade_request = 19, Tid_x_host = 20, Tid_x_real_ip = 21; - private static final Btrie_slim_mgr trie = Btrie_slim_mgr.ci_a7() + private static final Btrie_slim_mgr trie = Btrie_slim_mgr.ci_a7() .Add_str_int("GET" , Tid_get) .Add_str_int("POST" , Tid_post) .Add_str_int("Host:" , Tid_host) @@ -180,7 +178,7 @@ public class Http_request_parser { .Add_str_int("X-Host:" , Tid_x_host) .Add_str_int("X-Real-IP:" , Tid_x_real_ip) ; - private static final byte[] Tkn_boundary = Bry_.new_a7("boundary="), Tkn_content_type_boundary_end = Bry_.new_a7("--") + private static final byte[] Tkn_boundary = Bry_.new_a7("boundary="), Tkn_content_type_boundary_end = Bry_.new_a7("--") , Tkn_content_disposition = Bry_.new_a7("Content-Disposition:"), Tkn_form_data = Bry_.new_a7("form-data;") , Tkn_name = Bry_.new_a7("name=") ; diff --git a/400_xowa/src/gplx/xowa/Xoa_url_.java b/400_xowa/src/gplx/xowa/Xoa_url_.java index 558ff3a32..67f538f2f 100644 --- a/400_xowa/src/gplx/xowa/Xoa_url_.java +++ b/400_xowa/src/gplx/xowa/Xoa_url_.java @@ -28,7 +28,9 @@ public class Xoa_url_ { Qarg__redirect = Bry_.new_a7("redirect") , Qarg__redirect__no = Bry_.new_a7("no") , Qarg__action = Bry_.new_a7("action") + , Qarg__action__read = Bry_.new_a7("read") , Qarg__action__edit = Bry_.new_a7("edit") + , Qarg__action__html = Bry_.new_a7("html") , Qarg__curid = Bry_.new_a7("curid") ; } diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java index 44b61f356..2a9ebe6fe 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_mgr.java @@ -32,6 +32,7 @@ package gplx.xowa.apps.servers.http; import gplx.*; import gplx.xowa.*; import g import gplx.core.threads.*; import gplx.core.net.*; import gplx.core.primitives.*; import gplx.core.envs.*; import gplx.langs.jsons.*; import gplx.langs.htmls.encoders.*; import gplx.xowa.wikis.pages.*; +import gplx.xowa.addons.wikis.searchs.gui.htmlbars.*; public class Http_server_mgr implements Gfo_invk { private final Object thread_lock = new Object(); private final Gfo_usr_dlg usr_dlg; @@ -76,7 +77,7 @@ public class Http_server_mgr implements Gfo_invk { } else Note("HTTP Server not started"); - } + } running = val; } public void Run() { @@ -90,7 +91,7 @@ public class Http_server_mgr implements Gfo_invk { String cmd = url_converter.Decode_str(url_encoded_str); app.Gfs_mgr().Run_str(cmd); } - public String Parse_page_to_html(Http_data__client data__client, byte[] wiki_domain, byte[] ttl_bry) { + public String Parse_page_to_html(Http_data__client data__client, byte[] wiki_domain, byte[] ttl_bry, byte mode) { synchronized (thread_lock) { // create a shim gui to automatically handle default XOWA gui JS calls if (init_gui_needed) { @@ -101,7 +102,7 @@ public class Http_server_mgr implements Gfo_invk { // get the wiki Xowe_wiki wiki = (Xowe_wiki)app.Wiki_mgr().Get_by_or_make_init_y(wiki_domain); // assert init for Main_Page; EX:click zh.w on wiki sidebar; DATE:2015-07-19 if (Runtime_.Memory_total() > Io_mgr.Len_gb) Xowe_wiki_.Rls_mem(wiki, true); // release memory at 1 GB; DATE:2015-09-11 - + // get the url / ttl if (Bry_.Len_eq_0(ttl_bry)) ttl_bry = wiki.Props().Main_page(); Xoa_url url = wiki.Utl__url_parser().Parse(ttl_bry); @@ -118,7 +119,7 @@ public class Http_server_mgr implements Gfo_invk { page.Html_data().Head_mgr().Itm__server().Init_by_http(data__client).Enabled_y_(); // generate html - String rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, Xopg_page_.Tid_read)); // NOTE: must generate HTML now in order for "wait" and "async_server" to work with text_dbs; DATE:2016-07-10 + String rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, mode)); // NOTE: must generate HTML now in order for "wait" and "async_server" to work with text_dbs; DATE:2016-07-10 boolean rebuild_html = false; switch (retrieve_mode) { case File_retrieve_mode.Mode_skip: // noop @@ -133,7 +134,7 @@ public class Http_server_mgr implements Gfo_invk { page = wiki.Page_mgr().Load_page(url, ttl, tab); // HACK: fetch page again so that HTML will now include img data break; } - if (rebuild_html) rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, Xopg_page_.Tid_read)); + if (rebuild_html) rv = String_.new_u8(wiki.Html_mgr().Page_wtr_mgr().Gen(page, mode)); return rv; } } diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java index c2cdd5abe..1f60a4ea9 100644 --- a/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_server_wkr.java @@ -18,6 +18,7 @@ import gplx.core.ios.*; import gplx.core.ios.streams.*; import gplx.core.primitives.*; import gplx.core.net.*; import gplx.langs.htmls.encoders.*; import gplx.xowa.apps.*; import gplx.xowa.htmls.js.*; +import gplx.xowa.wikis.pages.*; class Http_server_wkr implements Gfo_invk { private final int uid; private final Http_server_mgr server_mgr; @@ -98,20 +99,15 @@ class Http_server_wkr implements Gfo_invk { app.Http_server().Run_xowa_cmd(app, String_.new_u8(cmd)); } private void Write_wiki(byte[] req) { - String wiki_domain = ""; String page_name = ""; - String[] req_split = String_.Split(String_.new_u8(req), "/"); - if(req_split.length >= 1){ - wiki_domain = req_split[1]; + Http_url_parser url_parser = new Http_url_parser(url_encoder); + String page_html = ""; + if (!url_parser.Parse(req)) { + page_html = url_parser.Err_msg(); } - if(req_split.length >= 4){ - page_name = req_split[3]; - for(int i = 4; i <= req_split.length-1; i++){ - page_name += "/" + req_split[i]; - } - page_name = url_encoder.Decode_str(page_name); + else { + page_html = app.Http_server().Parse_page_to_html(data__client, url_parser.Wiki(), url_parser.Page(), url_parser.Action()); + page_html = Convert_page(page_html, root_dir_http, String_.new_u8(url_parser.Wiki())); } - String page_html = app.Http_server().Parse_page_to_html(data__client, Bry_.new_u8(wiki_domain), Bry_.new_u8(page_name)); - page_html = Convert_page(page_html, root_dir_http, wiki_domain); Xosrv_http_wkr_.Write_response_as_html(client_wtr, Bool_.N, page_html); } private void Process_post(Http_request_itm request) { diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser.java new file mode 100644 index 000000000..1ded55dda --- /dev/null +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser.java @@ -0,0 +1,99 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.htmls.hrefs.*; +import gplx.xowa.wikis.pages.*; +class Http_url_parser { + private final Gfo_url_encoder url_encoder; + public Http_url_parser(Gfo_url_encoder url_encoder) { + this.url_encoder = url_encoder; + } + public byte[] Wiki() {return wiki;} public Http_url_parser Wiki_(String v) {this.wiki = Bry_.new_u8(v); return this;} private byte[] wiki; + public byte[] Page() {return page;} public Http_url_parser Page_(String v) {this.page = Bry_.new_u8(v); return this;} private byte[] page; + public byte Action() {return action;} public Http_url_parser Action_(byte v) {this.action = v; return this;} private byte action; + public String Err_msg() {return err_msg;} public Http_url_parser Err_msg_(String v) {this.err_msg = v; return this;} private String err_msg; + + public String To_str() { + Bry_bfr bfr = Bry_bfr_.New(); + bfr.Add_str_a7("wiki=").Add_safe(wiki).Add_byte_nl(); + bfr.Add_str_a7("page=").Add_safe(page).Add_byte_nl(); + bfr.Add_str_a7("action=").Add_byte(action).Add_byte_nl(); + bfr.Add_str_a7("err_msg=").Add_str_u8_null(err_msg).Add_byte_nl(); + return bfr.To_str_and_clear(); + } + + // Parse urls of form "/wiki_name/wiki/Page_name?action=val" + public boolean Parse(byte[] url) { + try { + // initial validations + if (url == null) return Fail(url, "invalid url; url is null"); + int url_len = url.length; + if (url_len == 0) return Fail(url, "invalid url; url is empty"); + if (url[0] != Byte_ascii.Slash) return Fail(url, "invalid url; must start with '/'"); + + // get wiki + int wiki_bgn = 1; // skip initial "/" + int wiki_end = Bry_find_.Find_fwd_or(url, Byte_ascii.Slash, wiki_bgn, url_len, url_len); + this.wiki = Bry_.Mid(url, wiki_bgn, wiki_end); + if (wiki_end == url_len) {// no slash found; url is wiki-only; EX: "/en.wikipedia.org" + return true; + } + + // get page after "/wiki/" + byte[] wiki_separator = Xoh_href_.Bry__wiki; + int page_bgn = wiki_end + wiki_separator.length; + if (!Bry_.Eq(url, wiki_end, wiki_end + wiki_separator.length, Xoh_href_.Bry__wiki)) + return Fail(url, "invalid url; must have '/wiki/' after wiki_domain"); + int page_end = url_len; + + // search for action arg + this.action = Xopg_page_.Tid_read; + int action_key_bgn = Bry_find_.Find_bwd(url, Qarg__action__frag, url_len); + if (action_key_bgn != Bry_find_.Not_found) { + int action_val_bgn = action_key_bgn + Qarg__action__frag.length; + int action_val_end = url_len; + boolean trim_page = true; + if (Bry_.Eq(url, action_val_bgn, action_val_end, Xoa_url_.Qarg__action__read)) + this.action = Xopg_page_.Tid_read; + else if (Bry_.Eq(url, action_val_bgn, action_val_end, Xoa_url_.Qarg__action__edit)) + this.action = Xopg_page_.Tid_edit; + else if (Bry_.Eq(url, action_val_bgn, action_val_end, Xoa_url_.Qarg__action__html)) + this.action = Xopg_page_.Tid_html; + else + trim_page = false; + if (trim_page) + page_end = action_key_bgn; + } + + this.page = url_encoder.Decode(Bry_.Mid(url, page_bgn, page_end)); + return true; + } + catch (Exception e) { + this.err_msg = Err_.Message_gplx_log(e); + return false; + } + } + private boolean Fail(byte[] url, String err_msg) { + this.wiki = null; + this.page = null; + this.err_msg = err_msg + "; url=" + String_.new_u8(url); + return false; + } + private static final byte[] + Qarg__action__frag = Bry_.Add(Byte_ascii.Question_bry, Xoa_url_.Qarg__action, Byte_ascii.Eq_bry) // "?action=" + ; +} diff --git a/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser_tst.java b/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser_tst.java new file mode 100644 index 000000000..cd1553067 --- /dev/null +++ b/400_xowa/src/gplx/xowa/apps/servers/http/Http_url_parser_tst.java @@ -0,0 +1,65 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.apps.servers.http; import gplx.*; import gplx.xowa.*; import gplx.xowa.apps.*; import gplx.xowa.apps.servers.*; +import org.junit.*; import gplx.core.tests.*; +import gplx.langs.htmls.encoders.*; +import gplx.xowa.wikis.pages.*; +public class Http_url_parser_tst { + private final Http_url_parser_fxt fxt = new Http_url_parser_fxt(); + @Test public void Parse() { + // wiki-only + fxt.Test__parse("/en.wikipedia.org", fxt.Make().Wiki_("en.wikipedia.org")); + + // wiki + page + fxt.Test__parse("/en.wikipedia.org/wiki/Page_1", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page_1")); + + // url-decoded; "%20" -> " " + fxt.Test__parse("/en.wikipedia.org/wiki/Page%201", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page 1")); + + // page with multiple slashes + fxt.Test__parse("/en.wikipedia.org/wiki/Page_1/A/B/C", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page_1/A/B/C")); + + // action=edit + fxt.Test__parse("/en.wikipedia.org/wiki/Page_1?action=edit", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page_1").Action_(Xopg_page_.Tid_edit)); + + // action=html + fxt.Test__parse("/en.wikipedia.org/wiki/Page_1?action=html", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page_1").Action_(Xopg_page_.Tid_html)); + + // action=N/A + fxt.Test__parse("/en.wikipedia.org/wiki/Page_1?action=a", fxt.Make().Wiki_("en.wikipedia.org").Page_("Page_1?action=a")); + + // fail: null + fxt.Test__parse(null, fxt.Make().Err_msg_("invalid url; url is null; url=")); + + // fail: empty + fxt.Test__parse("", fxt.Make().Err_msg_("invalid url; url is empty; url=")); + + // fail: missing '/' at start + fxt.Test__parse("en.wikipedia.org", fxt.Make().Err_msg_("invalid url; must start with '/'; url=en.wikipedia.org")); + + // fail: missing '/wiki/' + fxt.Test__parse("/en.wikipedia.org/Page_1", fxt.Make().Err_msg_("invalid url; must have '/wiki/' after wiki_domain; url=/en.wikipedia.org/Page_1")); + } +} +class Http_url_parser_fxt { + private final Gfo_url_encoder url_encoder = Gfo_url_encoder_.New__http_url().Make(); + public Http_url_parser Make() {return new Http_url_parser(url_encoder);} + public void Test__parse(String url, Http_url_parser expd) { + Http_url_parser actl = new Http_url_parser(url_encoder); + actl.Parse(url == null ? null : Bry_.new_u8(url)); + Gftest.Eq__ary__lines(expd.To_str(), actl.To_str()); + } +} diff --git a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java index 971a0ffcc..34bc065c4 100644 --- a/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java +++ b/400_xowa/src/gplx/xowa/guis/views/Xog_tab_itm_read_mgr.java @@ -23,8 +23,17 @@ public class Xog_tab_itm_read_mgr { public static void Show_page(Xog_tab_itm tab, Xoae_page new_page, boolean reset_to_read, boolean new_page_is_same, boolean show_is_err, byte history_nav_type) { if (reset_to_read) tab.View_mode_(Xopg_page_.Tid_read); - if (new_page.Url().Qargs_mgr().Match(Xoa_url_.Qarg__action, Xoa_url_.Qarg__action__edit)) - tab.View_mode_(Xopg_page_.Tid_edit); + + // set View_mode based on "action="; DATE:2018-11-03 + byte[] action_val = new_page.Url().Qargs_mgr().Get_val_bry_or(Xoa_url_.Qarg__action, Xoa_url_.Qarg__action__read); + byte view_mode = Xopg_page_.Tid_read; + if (Bry_.Eq(action_val, Xoa_url_.Qarg__action__read)) + view_mode = Xopg_page_.Tid_read; + else if (Bry_.Eq(action_val, Xoa_url_.Qarg__action__edit)) + view_mode = Xopg_page_.Tid_edit; + else if (Bry_.Eq(action_val, Xoa_url_.Qarg__action__html)) + view_mode = Xopg_page_.Tid_html; + tab.View_mode_(view_mode); Xoae_page cur_page = tab.Page(); Xog_html_itm html_itm = tab.Html_itm(); Gfui_html html_box = html_itm.Html_box(); Xog_win_itm win = tab.Tab_mgr().Win(); diff --git a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java index 63ed1b0ef..89772ccd6 100644 --- a/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java +++ b/400_xowa/src/gplx/xowa/htmls/portal/Xow_portal_mgr.java @@ -129,7 +129,7 @@ public class Xow_portal_mgr implements Gfo_invk { Xow_ns ns = ns_mgr.Ids_get_or_null(ns_id); return ns == null || ns.Exists() ? Bry_.Empty : missing_ns_cls; } - public byte[] Div_view_bry(Bry_bfr_mkr bfr_mkr, byte output_tid, byte[] search_text) { + public byte[] Div_view_bry(Bry_bfr_mkr bfr_mkr, byte output_tid, byte[] search_text, Xoa_ttl ttl) { Bry_bfr tmp_bfr = bfr_mkr.Get_k004(); byte[] read_cls = Bry_.Empty, edit_cls = Bry_.Empty, html_cls = Bry_.Empty; switch (output_tid) { @@ -137,7 +137,14 @@ public class Xow_portal_mgr implements Gfo_invk { case Xopg_page_.Tid_edit: edit_cls = Cls_selected_y; break; case Xopg_page_.Tid_html: html_cls = Cls_selected_y; break; } - div_view_fmtr.Bld_bfr_many(tmp_bfr, read_cls, edit_cls, html_cls, search_text); + + // build url_fragment with action query argument; EX: "/wiki/Page_name?action=" + byte[] url_frag_w_action_qarg = Bry_.Add(Xoh_href_.Bry__wiki, ttl.Page_db(), Byte_ascii.Question_bry, Xoa_url_.Qarg__action, Byte_ascii.Eq_bry); + div_view_fmtr.Bld_bfr_many(tmp_bfr, read_cls, edit_cls, html_cls, search_text + , ttl.Page_db() + , Bry_.Add(url_frag_w_action_qarg, Xoa_url_.Qarg__action__edit) + , Bry_.Add(url_frag_w_action_qarg, Xoa_url_.Qarg__action__html)); + return tmp_bfr.To_bry_and_rls(); } public static final byte[] Cls_selected_y = Bry_.new_a7("selected"), Cls_new = Bry_.new_a7("new"), Cls_display_none = Bry_.new_a7("xowa_display_none"); public byte[] Div_logo_bry(boolean nightmode) {return nightmode ? div_logo_night : div_logo_day;} private byte[] div_logo_day = Bry_.Empty, div_logo_night = Bry_.Empty; @@ -161,7 +168,7 @@ public class Xow_portal_mgr implements Gfo_invk { private final Bry_fmtr div_personal_fmtr = Bry_fmtr.new_("~{portal_personal_subj_href};~{portal_personal_subj_text};~{portal_personal_talk_cls};~{portal_personal_talk_href};~{portal_personal_talk_cls};", "portal_personal_subj_href", "portal_personal_subj_text", "portal_personal_subj_cls", "portal_personal_talk_href", "portal_personal_talk_cls") , div_ns_fmtr = Bry_fmtr.new_("~{portal_ns_subj_href};~{portal_ns_subj_cls};~{portal_ns_talk_href};~{portal_ns_talk_cls};~{portal_div_vnts}", "portal_ns_subj_href", "portal_ns_subj_cls", "portal_ns_talk_href", "portal_ns_talk_cls", "portal_div_vnts") - , div_view_fmtr = Bry_fmtr.new_("", "portal_view_read_cls", "portal_view_edit_cls", "portal_view_html_cls", "search_text") + , div_view_fmtr = Bry_fmtr.new_("", "portal_view_read_cls", "portal_view_edit_cls", "portal_view_html_cls", "search_text", "portal_view_read_href", "portal_view_edit_href", "portal_view_html_href") , div_logo_fmtr = Bry_fmtr.new_("", "portal_nav_main_href", "portal_logo_url") , div_sync_fmtr = Bry_fmtr.new_("", "page_url") , div_wikis_fmtr = Bry_fmtr.new_("", "toggle_btn", "toggle_hdr") diff --git a/res/bin/any/xowa/cfg/app/xowa.gfs b/res/bin/any/xowa/cfg/app/xowa.gfs index 5f31c618c..e9ef51edf 100644 --- a/res/bin/any/xowa/cfg/app/xowa.gfs +++ b/res/bin/any/xowa/cfg/app/xowa.gfs @@ -164,10 +164,9 @@ app.wikis.get('~{wiki_key}').html.portal {

~{<>msgs.get('views');<>}