Wikipedia testwiki https://test.wikipedia.org/wiki/Main_Page MediaWiki 1.46.0-wmf.26 first-letter Media Special Talk User User talk Wikipedia Wikipedia talk File File talk MediaWiki MediaWiki talk Template Template talk Help Help talk Category Category talk Thread Thread talk Summary Summary talk Test namespace 1 Test namespace 1 talk Test namespace 2 Test namespace 2 talk Draft Draft talk Campaign Campaign talk TimedText TimedText talk Module Module talk SecurePoll SecurePoll talk CNBanner CNBanner talk Translations Translations talk Event Event talk Topic Newsletter Newsletter talk Wikipedia:Requests/Permissions 4 32559 740055 739360 2026-05-01T18:21:59Z Namoroka 19627 /* Requests for user rights */ 740055 wikitext text/x-wiki <noinclude>{{Shortcut|WP:R/P|WP:RfP|WP:RfA|WP:PERM|WP:RFR|WP:RFPERM}}</noinclude> {{Wikipedia:Requests/Top}} == Requests for user rights == * Subpages: [[Wikipedia:Requests/Permissions/All|All (current and archived)]] * Request: <inputbox> type=create prefix=Wikipedia:Requests/Permissions/ preload=Template:PA2 buttonlabel=Requests for user rights placeholder=Enter your username </inputbox> After creating the subpage, come back here and transclude the page below (<code><nowiki>{{Wikipedia:Requests/Permissions/Example}}</nowiki></code>). <!-- Please transclude your requests below this line, LATEST AT THE TOP, in the form {{Wikipedia:Requests/Permissions/USERNAME}} --> {{Wikipedia:Requests/Permissions/Namoroka}} <!-- NEW ENTRIES AT THE TOP, NOT HERE --> r0wiqrmlkx4dl7otrxevuds2qk1c7gf Wikipedia:Requests/Bot status 4 32566 740046 658436 2026-05-01T17:19:02Z Alachuckthebuck 49745 /* Request bot status */ transcluding my request for bot acess 740046 wikitext text/x-wiki <noinclude>{{/Top}}</noinclude> == Request bot status == <inputbox> type=create default=Wikipedia:Requests/Bot status/Your Bot Name preload=Template:PA2 buttonlabel=Requests for bot status </inputbox> [[Wikipedia:Requests/Bot status/All|All (current and archived)]] ---- {{Wikipedia:Requests/Bot status/Alachuckthebuck}} [[Category:!Requests]] 2mueynjl7ydc7bac43wbvou6ap7c1z1 Template:Hidden 10 44109 740074 584312 2024-08-22T17:15:23Z en>Ahecht 0 safesubst 740074 wikitext text/x-wiki {{hidden begin|toggle={{{showhide|}}}{{{toggle|}}}|expanded={{{expand|{{{expanded|}}}}}}|class={{{class|}}}|border={{{border|}}}|style={{{framestyle|{{{style|{{{css|}}}}}}}}} |titlebgcolor={{{titlebgcolor|{{{background|{{{bg1|}}}}}}}}}|ta1={{{titlealign|{{{ta1|center}}}}}}|titlestyle={{safesubst<noinclude/>:#if:{{{multiline|{{{multi-line|}}}}}}|height:auto;}}{{safesubst<noinclude/>:#ifeq:{{{fw1|bold}}}|bold||font-weight:{{{fw1|bold}}};}}{{{headercss|{{{headerstyle|{{{titlestyle|}}}}}}}}} |title={{safesubst<noinclude/>:#if:{{{multiline|{{{multi-line|}}}}}}|<div style="margin-right:4em;line-height:125%;height:auto;">{{{title|{{{header|{{{1}}}}}}}}}</div>|{{{title|{{{header|{{{1}}}}}}}}}}} |contentbgcolor={{{contentbgcolor|{{{bg2|}}}}}}|ta2={{{ta2|}}}|contentstyle={{safesubst<noinclude/>:#ifeq:{{{fw2|normal}}}|normal||font-weight:{{{fw2|normal}}};}}{{{contentcss|{{{contentstyle|}}}}}} }} {{{content|{{{contents|{{{text|{{{2}}}}}}}}}}}}{{hidden end}}{{safesubst<noinclude/>:#if:{{{background|}}}{{{bg1|}}}{{{bg2|}}}{{{titlealign|}}}{{{ta1|}}}{{{ta2|}}}{{{fw1|}}}{{{fw2|}}}|[[Category:Hidden templates using styles|{{NAMESPACE}}{{PAGENAME}}]]}}<noinclude> {{documentation}} </noinclude> p5xonrpu5ow58rghncb9ny8veaty7fm 740075 740074 2026-05-01T19:28:18Z Novem Linguae 49714 1 revision imported from [[:en:Template:Hidden]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740074 wikitext text/x-wiki {{hidden begin|toggle={{{showhide|}}}{{{toggle|}}}|expanded={{{expand|{{{expanded|}}}}}}|class={{{class|}}}|border={{{border|}}}|style={{{framestyle|{{{style|{{{css|}}}}}}}}} |titlebgcolor={{{titlebgcolor|{{{background|{{{bg1|}}}}}}}}}|ta1={{{titlealign|{{{ta1|center}}}}}}|titlestyle={{safesubst<noinclude/>:#if:{{{multiline|{{{multi-line|}}}}}}|height:auto;}}{{safesubst<noinclude/>:#ifeq:{{{fw1|bold}}}|bold||font-weight:{{{fw1|bold}}};}}{{{headercss|{{{headerstyle|{{{titlestyle|}}}}}}}}} |title={{safesubst<noinclude/>:#if:{{{multiline|{{{multi-line|}}}}}}|<div style="margin-right:4em;line-height:125%;height:auto;">{{{title|{{{header|{{{1}}}}}}}}}</div>|{{{title|{{{header|{{{1}}}}}}}}}}} |contentbgcolor={{{contentbgcolor|{{{bg2|}}}}}}|ta2={{{ta2|}}}|contentstyle={{safesubst<noinclude/>:#ifeq:{{{fw2|normal}}}|normal||font-weight:{{{fw2|normal}}};}}{{{contentcss|{{{contentstyle|}}}}}} }} {{{content|{{{contents|{{{text|{{{2}}}}}}}}}}}}{{hidden end}}{{safesubst<noinclude/>:#if:{{{background|}}}{{{bg1|}}}{{{bg2|}}}{{{titlealign|}}}{{{ta1|}}}{{{ta2|}}}{{{fw1|}}}{{{fw2|}}}|[[Category:Hidden templates using styles|{{NAMESPACE}}{{PAGENAME}}]]}}<noinclude> {{documentation}} </noinclude> p5xonrpu5ow58rghncb9ny8veaty7fm Template:Columns-list 10 70788 740078 584318 2023-12-22T03:07:49Z en>Ahecht 0 If we're making the meat of the template substable, the rest should be too 740078 wikitext text/x-wiki {{<includeonly>safesubst:</includeonly>#if:{{{1|}}}|{{<includeonly>safesubst:</includeonly>#invoke:Template wrapper|wrap|_template=div col|_alias-map=1:content|colwidth=30em}}}}{{<includeonly>safesubst:</includeonly>#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using columns-list with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Columns-list]] with unknown parameter "_VALUE_"|ignoreblank=y| 1 | class | content | colwidth | gap | rules | small | style }}<noinclude> {{documentation}} </noinclude> bncwuudalvclxc2ss44oddbl9xjm222 740079 740078 2026-05-01T19:28:19Z Novem Linguae 49714 1 revision imported from [[:en:Template:Columns-list]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740078 wikitext text/x-wiki {{<includeonly>safesubst:</includeonly>#if:{{{1|}}}|{{<includeonly>safesubst:</includeonly>#invoke:Template wrapper|wrap|_template=div col|_alias-map=1:content|colwidth=30em}}}}{{<includeonly>safesubst:</includeonly>#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using columns-list with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Columns-list]] with unknown parameter "_VALUE_"|ignoreblank=y| 1 | class | content | colwidth | gap | rules | small | style }}<noinclude> {{documentation}} </noinclude> bncwuudalvclxc2ss44oddbl9xjm222 User:MiszaBot/config 2 74217 740054 184999 2026-05-01T18:04:54Z Ludo Games-T 73707 740054 wikitext text/x-wiki {{#if:{{{key|}}}|[[Category:Pages archived using a key]]|{{#ifeq:{{#invoke:String|find|source={{{archive|}}}|target={{#titleparts:{{FULLPAGENAME}}}}/}}|1|<nowiki />|<includeonly>{{Preview warning|Page using [[User:MiszaBot/config]] with [[:Category:Pages where archive parameter is not a subpage|archive parameter not a subpage]]}}[[Category:Pages where archive parameter is not a subpage]]</includeonly>}}}}<!-- -->{{#ifeq:{{{algo|}}}|{{#invoke:String|match|s={{{algo|}}}|pattern=old%(%d+[hd]?%)|show-pattern=no}}|<nowiki />|<includeonly>{{Preview warning|Page using [[User:MiszaBot/config]] with invalid duration in {{para|algo|old()}} parameter: must use a duration in hours (h) or days (d)}}</includeonly>}}<!-- --> 0y078ktpsr7elk6b38v45f7o70x9lkf Template:Module other 10 77521 740084 736467 2026-04-18T03:45:36Z en>Pppery 0 Changed protection settings for "[[Template:Module other]]" ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 740084 wikitext text/x-wiki {{safesubst:<noinclude/>#switch: <noinclude><!-- If no or empty "demospace" parameter then detect namespace --></noinclude> {{safesubst:<noinclude/>#if:{{{demospace|}}} | {{safesubst:<noinclude/>lc: {{{demospace}}} }} <noinclude><!-- Use lower case "demospace" --></noinclude> | {{safesubst:<noinclude/>#ifeq:{{safesubst:<noinclude/>NAMESPACE}}|{{safesubst:<noinclude/>ns:Module}} | module | other }} }} | module = {{{module|{{{1|}}}}}} | other | #default = {{{other|{{{2|}}}}}} }}<noinclude><!--End switch--> {{documentation}} <!-- Add categories to the /doc subpage, not here! --> </noinclude> 6mo4qg9nwfuq6vj2r8irxnjnd71296k 740085 740084 2026-05-01T19:28:20Z Novem Linguae 49714 1 revision imported from [[:en:Template:Module_other]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740084 wikitext text/x-wiki {{safesubst:<noinclude/>#switch: <noinclude><!-- If no or empty "demospace" parameter then detect namespace --></noinclude> {{safesubst:<noinclude/>#if:{{{demospace|}}} | {{safesubst:<noinclude/>lc: {{{demospace}}} }} <noinclude><!-- Use lower case "demospace" --></noinclude> | {{safesubst:<noinclude/>#ifeq:{{safesubst:<noinclude/>NAMESPACE}}|{{safesubst:<noinclude/>ns:Module}} | module | other }} }} | module = {{{module|{{{1|}}}}}} | other | #default = {{{other|{{{2|}}}}}} }}<noinclude><!--End switch--> {{documentation}} <!-- Add categories to the /doc subpage, not here! --> </noinclude> 6mo4qg9nwfuq6vj2r8irxnjnd71296k Template:Hidden begin 10 78002 740076 609494 2025-10-04T17:15:00Z en>Jonesey95 0 fix text color in dark mode; this generally works but may cause problems, in which case revert or try specifying a different CSS value 740076 wikitext text/x-wiki <includeonly><templatestyles src="Template:Hidden begin/styles.css"/><div class="hidden-begin mw-collapsible {{#ifeq:{{{showhide|{{{toggle}}}}}}|left|mw-collapsible-leftside-toggle}} {{#if:{{{expanded|}}}||mw-collapsed}} {{{class|}}}" style="{{#if:{{{width|}}}|width:{{{width}}};}} {{#if:{{{border|}}}|border:{{{border}}};}} {{#if:{{{bgcolor|}}}|background-color:{{{bgcolor}}};color:inherit;}} {{{style|}}}"><!-- --><div class="hidden-title skin-nightmode-reset-color" style="{{#if:{{{ta1|}}}|text-align:{{{ta1}}};}} {{#if:{{{titlebgcolor|}}}|background-color:{{{titlebgcolor}}};color:inherit;}} {{{titlestyle|}}}">{{{title|{{{header|}}}}}}</div><!-- --><div class="hidden-content mw-collapsible-content" style="{{#if:{{{ta2|}}}|text-align:{{{ta2}}};}} {{#if:{{{contentbgcolor|}}}|background-color:{{{contentbgcolor}}};color:inherit;}} {{{contentstyle|{{{bodystyle|}}}}}}"><!-- Content added after the template --></includeonly><noinclude> {{Documentation}} </noinclude> 3fk8uj9th66zvww6bd58rbapuyb6ij4 740077 740076 2026-05-01T19:28:19Z Novem Linguae 49714 1 revision imported from [[:en:Template:Hidden_begin]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740076 wikitext text/x-wiki <includeonly><templatestyles src="Template:Hidden begin/styles.css"/><div class="hidden-begin mw-collapsible {{#ifeq:{{{showhide|{{{toggle}}}}}}|left|mw-collapsible-leftside-toggle}} {{#if:{{{expanded|}}}||mw-collapsed}} {{{class|}}}" style="{{#if:{{{width|}}}|width:{{{width}}};}} {{#if:{{{border|}}}|border:{{{border}}};}} {{#if:{{{bgcolor|}}}|background-color:{{{bgcolor}}};color:inherit;}} {{{style|}}}"><!-- --><div class="hidden-title skin-nightmode-reset-color" style="{{#if:{{{ta1|}}}|text-align:{{{ta1}}};}} {{#if:{{{titlebgcolor|}}}|background-color:{{{titlebgcolor}}};color:inherit;}} {{{titlestyle|}}}">{{{title|{{{header|}}}}}}</div><!-- --><div class="hidden-content mw-collapsible-content" style="{{#if:{{{ta2|}}}|text-align:{{{ta2}}};}} {{#if:{{{contentbgcolor|}}}|background-color:{{{contentbgcolor}}};color:inherit;}} {{{contentstyle|{{{bodystyle|}}}}}}"><!-- Content added after the template --></includeonly><noinclude> {{Documentation}} </noinclude> 3fk8uj9th66zvww6bd58rbapuyb6ij4 Lotus Pond 0 78685 740008 446056 2026-05-01T14:30:49Z Cryptocurrency777 73698 740008 wikitext text/x-wiki exists. dsyy0d0s4fxuacmaqdz5dxjf4z9xryy Template:Oldid 10 83044 740082 238645 2018-12-10T20:41:24Z en>Jonesey95 0 rm stray trailing space, per talk page request 740082 wikitext text/x-wiki <span class="plainlinks">[{{fullurl:{{{page|{{{1|Main Page}}}}}}|oldid={{{oldid|{{{2|}}}}}}}} {{{label|{{{title|{{{3|{{#if:{{{oldid|{{{2|}}}}}}|Old revision|Current version}} of {{#if:{{{page|{{{1|}}}}}}|'''{{{page|{{{1}}}}}}'''|a page}}}}}}}}}}}]</span><noinclude> {{documentation}} </noinclude> 8oa36sx98rpl6fqzvwz7o9hp304i7yz 740083 740082 2026-05-01T19:28:19Z Novem Linguae 49714 1 revision imported from [[:en:Template:Oldid]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740082 wikitext text/x-wiki <span class="plainlinks">[{{fullurl:{{{page|{{{1|Main Page}}}}}}|oldid={{{oldid|{{{2|}}}}}}}} {{{label|{{{title|{{{3|{{#if:{{{oldid|{{{2|}}}}}}|Old revision|Current version}} of {{#if:{{{page|{{{1|}}}}}}|'''{{{page|{{{1}}}}}}'''|a page}}}}}}}}}}}]</span><noinclude> {{documentation}} </noinclude> 8oa36sx98rpl6fqzvwz7o9hp304i7yz Module:Protection banner/config 828 83704 740061 637555 2026-04-18T02:24:50Z en>Santiago Claudio 0 Sync with sandbox per edit request 740061 Scribunto text/plain -- This module provides configuration data for [[Module:Protection banner]]. return { -------------------------------------------------------------------------------- -- -- BANNER DATA -- -------------------------------------------------------------------------------- --[[ -- Banner data consists of six fields: -- * text - the main protection text that appears at the top of protection -- banners. -- * explanation - the text that appears below the main protection text, used -- to explain the details of the protection. -- * tooltip - the tooltip text you see when you move the mouse over a small -- padlock icon. -- * link - the page that the small padlock icon links to. -- * alt - the alt text for the small padlock icon. This is also used as tooltip -- text for the large protection banners. -- * image - the padlock image used in both protection banners and small padlock -- icons. -- -- The module checks in three separate tables to find a value for each field. -- First it checks the banners table, which has values specific to the reason -- for the page being protected. Then the module checks the defaultBanners -- table, which has values specific to each protection level. Finally, the -- module checks the masterBanner table, which holds data for protection -- templates to use if no data has been found in the previous two tables. -- -- The values in the banner data can take parameters. These are specified -- using ${TEXTLIKETHIS} (a dollar sign preceding a parameter name -- enclosed in curly braces). -- -- Available parameters: -- -- ${CURRENTVERSION} - a link to the page history or the move log, with the -- display message "current-version-edit-display" or -- "current-version-move-display". -- -- ${EDITREQUEST} - a link to create an edit request for the current page. -- -- ${EXPLANATIONBLURB} - an explanation blurb, e.g. "Please discuss any changes -- on the talk page; you may submit a request to ask an administrator to make -- an edit if it is minor or supported by consensus." -- -- ${IMAGELINK} - a link to set the image to, depending on the protection -- action and protection level. -- -- ${INTROBLURB} - the PROTECTIONBLURB parameter, plus the expiry if an expiry -- is set. E.g. "Editing of this page by new or unregistered users is currently -- disabled until dd Month YYYY." -- -- ${INTROFRAGMENT} - the same as ${INTROBLURB}, but without final punctuation -- so that it can be used in run-on sentences. -- -- ${PAGETYPE} - the type of the page, e.g. "article" or "template". -- Defined in the cfg.pagetypes table. -- -- ${PROTECTIONBLURB} - a blurb explaining the protection level of the page, e.g. -- "Editing of this page by new or unregistered users is currently disabled" -- -- ${PROTECTIONDATE} - the protection date, if it has been supplied to the -- template. -- -- ${PROTECTIONLEVEL} - the protection level, e.g. "fully protected" or -- "semi-protected". -- -- ${PROTECTIONLOG} - a link to the protection log or the pending changes log, -- depending on the protection action. -- -- ${TALKPAGE} - a link to the talk page. If a section is specified, links -- straight to that talk page section. -- -- ${TOOLTIPBLURB} - uses the PAGETYPE, PROTECTIONTYPE and EXPIRY parameters to -- create a blurb like "This template is semi-protected", or "This article is -- move-protected until DD Month YYYY". -- -- ${VANDAL} - links for the specified username (or the root page name) -- using Module:Vandal-m. -- -- Functions -- -- For advanced users, it is possible to use Lua functions instead of strings -- in the banner config tables. Using functions gives flexibility that is not -- possible just by using parameters. Functions take two arguments, the -- protection object and the template arguments, and they must output a string. -- -- For example: -- -- text = function (protectionObj, args) -- if protectionObj.level == 'autoconfirmed' then -- return 'foo' -- else -- return 'bar' -- end -- end -- -- Some protection object properties and methods that may be useful: -- protectionObj.action - the protection action -- protectionObj.level - the protection level -- protectionObj.reason - the protection reason -- protectionObj.expiry - the expiry. Nil if unset, the string "indef" if set -- to indefinite, and the protection time in unix time if temporary. -- protectionObj.protectionDate - the protection date in unix time, or nil if -- unspecified. -- protectionObj.bannerConfig - the banner config found by the module. Beware -- of editing the config field used by the function, as it could create an -- infinite loop. -- protectionObj:isProtected - returns a boolean showing whether the page is -- protected. -- protectionObj:isTemporary - returns a boolean showing whether the expiry is -- temporary. -- protectionObj:isIncorrect - returns a boolean showing whether the protection -- template is incorrect. --]] -- The master banner data, used if no values have been found in banners or -- defaultBanners. masterBanner = { text = '${INTROBLURB}', explanation = '${EXPLANATIONBLURB}', tooltip = '${TOOLTIPBLURB}', link = '${IMAGELINK}', alt = 'Page ${PROTECTIONLEVEL}' }, -- The default banner data. This holds banner data for different protection -- levels. -- *required* - this table needs edit, move, autoreview and upload subtables. defaultBanners = { edit = {}, move = {}, autoreview = { default = { alt = 'Page protected with pending changes', tooltip = 'All edits by unregistered and new users are subject to review prior to becoming visible to unregistered users', image = 'Pending-protection-shackle.svg' } }, upload = {} }, -- The banner data. This holds banner data for different protection reasons. -- In fact, the reasons specified in this table control which reasons are -- valid inputs to the first positional parameter. -- -- There is also a non-standard "description" field that can be used for items -- in this table. This is a description of the protection reason for use in the -- module documentation. -- -- *required* - this table needs edit, move, autoreview and upload subtables. banners = { edit = { blp = { description = 'For pages protected to promote compliance with the' .. ' [[Wikipedia:Biographies of living persons' .. '|biographies of living persons]] policy', text = '${INTROFRAGMENT} to promote compliance with' .. ' [[Wikipedia:Biographies of living persons' .. "|Wikipedia's&nbsp;policy on&nbsp;the&nbsp;biographies" .. ' of&nbsp;living&nbsp;people]].', tooltip = '${TOOLTIPFRAGMENT} to promote compliance with the policy on' .. ' biographies of living persons', }, deceased = { description = 'For user pages of Wikipedia users who are deceased', text = '${INTROFRAGMENT} to prevent vandalism of a deceased' .. ' Wikipedian\'s user page.' .. ' A family member who wishes to edit this user page can use this' .. ' user\'s ${TALKPAGE} or submit a request to [[Wikipedia:VRT|the' .. ' Volunteer Response Team]].', tooltip = '${TOOLTIPFRAGMENT} because this Wikipedian is deceased' }, dmca = { description = 'For pages protected by the Wikimedia Foundation' .. ' due to [[Digital Millennium Copyright Act]] takedown requests', explanation = function (protectionObj, args) local ret = 'Pursuant to a rights owner notice under the Digital' .. ' Millennium Copyright Act (DMCA) regarding some content' .. ' in this article, the Wikimedia Foundation acted under' .. ' applicable law and took down and restricted the content' .. ' in question.' if args.notice then ret = ret .. ' A copy of the received notice can be found here: ' .. args.notice .. '.' end ret = ret .. ' For more information, including websites discussing' .. ' how to file a counter-notice, please see' .. " [[Wikipedia:Office actions]] and the article's ${TALKPAGE}." .. "'''Do not remove this template from the article until the" .. " restrictions are withdrawn'''." return ret end, image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, dispute = { description = 'For pages protected due to editing disputes', text = function (protectionObj, args) -- Find the value of "disputes". local display = 'disputes' local disputes if args.section then disputes = string.format( '[[%s:%s#%s|%s]]', mw.site.namespaces[protectionObj.title.namespace].talk.name, protectionObj.title.text, args.section, display ) else disputes = display end -- Make the blurb, depending on the expiry. local msg if type(protectionObj.expiry) == 'number' then msg = '${INTROFRAGMENT} or until editing %s have been resolved.' else msg = '${INTROFRAGMENT} until editing %s have been resolved.' end return string.format(msg, disputes) end, explanation = "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', tooltip = '${TOOLTIPFRAGMENT} due to editing disputes', }, ecp = { description = 'For articles in topic areas authorized by' .. ' [[Wikipedia:Arbitration Committee|ArbCom]] or' .. ' meets the criteria for community use', alt = 'Extended-protected ${PAGETYPE}', }, mainpage = { description = 'For pages protected for being displayed on the [[Main Page]]', text = 'This file is currently' .. ' [[Wikipedia:This page is protected|protected]] from' .. ' editing because it is currently or will soon be displayed' .. ' on the [[Main Page]].', explanation = 'Images on the Main Page are protected due to their high' .. ' visibility. Please discuss any necessary changes on the ${TALKPAGE}.' .. '<br /><span style="font-size:90%;">' .. "'''Administrators:''' Once this image is definitely off the Main Page," .. ' please unprotect this file, or reduce to semi-protection,' .. ' as appropriate.</span>', }, office = { description = 'For pages protected by the Wikimedia Foundation', text = function (protectionObj, args) local ret = 'This ${PAGETYPE} is currently under the' .. ' scrutiny of the' .. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]' .. ' and is protected.' if protectionObj.protectionDate then ret = ret .. ' It has been protected since ${PROTECTIONDATE}.' end return ret end, explanation = "If you can edit this page, please discuss all changes and" .. " additions on the ${TALKPAGE} first. '''Do not remove protection from this" .. " page unless you are authorized by the Wikimedia Foundation to do" .. " so.'''", image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, reset = { description = 'For pages protected by the Wikimedia Foundation and' .. ' "reset" to a bare-bones version', text = 'This ${PAGETYPE} is currently under the' .. ' scrutiny of the' .. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]' .. ' and is protected.', explanation = function (protectionObj, args) local ret = '' if protectionObj.protectionDate then ret = ret .. 'On ${PROTECTIONDATE} this ${PAGETYPE} was' else ret = ret .. 'This ${PAGETYPE} has been' end ret = ret .. ' reduced to a' .. ' simplified, "bare bones" version so that it may be completely' .. ' rewritten to ensure it meets the policies of' .. ' [[WP:NPOV|Neutral Point of View]] and [[WP:V|Verifiability]].' .. ' Standard Wikipedia policies will apply to its rewriting—which' .. ' will eventually be open to all editors—and will be strictly' .. ' enforced. The ${PAGETYPE} has been ${PROTECTIONLEVEL} while' .. ' it is being rebuilt.\n\n' .. 'Any insertion of material directly from' .. ' pre-protection revisions of the ${PAGETYPE} will be removed, as' .. ' will any material added to the ${PAGETYPE} that is not properly' .. ' sourced. The associated talk page(s) were also cleared on the' .. " same date.\n\n" .. "If you can edit this page, please discuss all changes and" .. " additions on the ${TALKPAGE} first. '''Do not override" .. " this action, and do not remove protection from this page," .. " unless you are authorized by the Wikimedia Foundation" .. " to do so. No editor may remove this notice.'''" return ret end, image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, sock = { description = 'For pages protected due to' .. ' [[Wikipedia:Sock puppetry|sock puppetry]]', text = '${INTROFRAGMENT} to prevent [[Wikipedia:Sock puppetry|sock puppets]] of' .. ' [[Wikipedia:Blocking policy|blocked]] or' .. ' [[Wikipedia:Banning policy|banned users]]' .. ' from editing it.', tooltip = '${TOOLTIPFRAGMENT} to prevent sock puppets of blocked or banned users from' .. ' editing it', }, template = { description = 'For [[Wikipedia:High-risk templates|high-risk]]' .. ' templates and Lua modules', text = 'This is a permanently [[Wikipedia:Protection policy|protected]] ${PAGETYPE},' .. ' as it is [[Wikipedia:High-risk templates|high-risk]].', explanation = 'Please discuss any changes on the ${TALKPAGE}; you may' .. ' ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] or' .. ' [[Wikipedia:Template editor|template editor]] to make an edit if' .. ' it is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by' .. ' [[Wikipedia:Consensus|consensus]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.', tooltip = 'This high-risk ${PAGETYPE} is permanently ${PROTECTIONLEVEL}' .. ' to prevent vandalism', alt = 'Permanently protected ${PAGETYPE}', }, usertalk = { description = 'For pages protected against disruptive edits by a' .. ' particular user', text = '${INTROFRAGMENT} to prevent ${VANDAL} from using it to make disruptive edits,' .. ' such as abusing the' .. ' &#123;&#123;[[Template:unblock|unblock]]&#125;&#125; template.', explanation = 'If you cannot edit this user talk page and you need to' .. ' make a change or leave a message, you can' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for edits to a protected page' .. '|request an edit]],' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]],' .. ' [[Special:Userlogin|log in]],' .. ' or [[Special:UserLogin/signup|create an account]].', }, vandalism = { description = 'For pages protected against' .. ' [[Wikipedia:Vandalism|vandalism]]', text = '${INTROFRAGMENT} due to [[Wikipedia:Vandalism|vandalism]].', explanation = function (protectionObj, args) local ret = '' if protectionObj.level == 'sysop' then ret = ret .. "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ' end return ret .. '${EXPLANATIONBLURB}' end, tooltip = '${TOOLTIPFRAGMENT} due to vandalism', } }, move = { dispute = { description = 'For pages protected against page moves due to' .. ' disputes over the page title', explanation = "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', image = 'Move-protection-shackle.svg' }, vandalism = { description = 'For pages protected against' .. ' [[Wikipedia:Vandalism#Page-move vandalism' .. ' |page-move vandalism]]' } }, autoreview = {}, upload = {} }, -------------------------------------------------------------------------------- -- -- GENERAL DATA TABLES -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Protection blurbs -------------------------------------------------------------------------------- -- This table produces the protection blurbs available with the -- ${PROTECTIONBLURB} parameter. It is sorted by protection action and -- protection level, and is checked by the module in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. protectionBlurbs = { edit = { default = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#full|' .. 'protected]] from editing', templateeditor = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#template' .. '|protected]] from editing', autoconfirmed = 'Editing of this ${PAGETYPE} by [[Wikipedia:User access' .. ' levels#New users|new]] or [[Wikipedia:User access levels#Unregistered' .. ' users|unregistered]] users is currently [[Wikipedia:Protection' .. ' policy#semi|disabled]]', extendedconfirmed = 'This ${PAGETYPE} is currently under [[Wikipedia:Protection' .. ' policy#extended|extended confirmed protection]]', }, move = { default = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#Move' .. ' protection|protected]] from [[Help:Moving a page|page moves]]' }, autoreview = { default = 'All edits made to this ${PAGETYPE} by' .. ' [[Wikipedia:User access levels#New users|new]] or' .. ' [[Wikipedia:User access levels#Unregistered users|unregistered]]' .. ' users are currently' .. ' [[Wikipedia:Pending changes|subject to review]]' }, upload = { default = 'Uploading new versions of this ${PAGETYPE} is currently disabled' } }, -------------------------------------------------------------------------------- -- Explanation blurbs -------------------------------------------------------------------------------- -- This table produces the explanation blurbs available with the -- ${EXPLANATIONBLURB} parameter. It is sorted by protection action, -- protection level, and whether the page is a talk page or not. If the page is -- a talk page it will have a talk key of "talk"; otherwise it will have a talk -- key of "subject". The table is checked in the following order: -- 1. page's protection action, page's protection level, page's talk key -- 2. page's protection action, page's protection level, default talk key -- 3. page's protection action, default protection level, page's talk key -- 4. page's protection action, default protection level, default talk key -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. explanationBlurbs = { edit = { autoconfirmed = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details. If you' .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' .. ' ${EDITREQUEST}, discuss changes on the ${TALKPAGE},' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details. If you' .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].', }, extendedconfirmed = { default = 'Extended confirmed protection prevents edits from all unregistered editors' .. ' and registered users with fewer than 30 days tenure and 500 edits.' .. ' The [[Wikipedia:Protection policy#extended|policy on community use]]' .. ' specifies that extended confirmed protection can be applied to combat' .. ' disruption, if semi-protection has proven to be ineffective.' .. ' Extended confirmed protection may also be applied to enforce' .. ' [[Wikipedia:Arbitration Committee|arbitration sanctions]].' .. ' Please discuss any changes on the ${TALKPAGE}; you may' .. ' ${EDITREQUEST} to ask for uncontroversial changes supported by' .. ' [[Wikipedia:Consensus|consensus]].' }, default = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Please discuss any changes on the ${TALKPAGE}; you' .. ' may ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] to make an edit if it' .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by [[Wikipedia:Consensus' .. '|consensus]]. You may also [[Wikipedia:Requests for' .. ' page protection#Current requests for reduction in protection level' .. '|request]] that this page be unprotected.', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' You may [[Wikipedia:Requests for page' .. ' protection#Current requests for edits to a protected page|request an' .. ' edit]] to this page, or [[Wikipedia:Requests for' .. ' page protection#Current requests for reduction in protection level' .. '|ask]] for it to be unprotected.' } }, move = { default = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but cannot be moved' .. ' until unprotected. Please discuss any suggested moves on the' .. ' ${TALKPAGE} or at [[Wikipedia:Requested moves]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but cannot be moved' .. ' until unprotected. Please discuss any suggested moves at' .. ' [[Wikipedia:Requested moves]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.' } }, autoreview = { default = { default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Edits to this ${PAGETYPE} by new and unregistered users' .. ' will not be visible to readers until they are accepted by' .. ' a reviewer. To avoid the need for your edits to be' .. ' reviewed, you may' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].' }, }, upload = { default = { default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but new versions of the file' .. ' cannot be uploaded until it is unprotected. You can' .. ' request that a new version be uploaded by using a' .. ' [[Wikipedia:Edit requests|protected edit request]], or you' .. ' can [[Wikipedia:Requests for page protection|request]]' .. ' that the file be unprotected.' } } }, -------------------------------------------------------------------------------- -- Protection levels -------------------------------------------------------------------------------- -- This table provides the data for the ${PROTECTIONLEVEL} parameter, which -- produces a short label for different protection levels. It is sorted by -- protection action and protection level, and is checked in the following -- order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. protectionLevels = { edit = { default = 'protected', templateeditor = 'template-protected', extendedconfirmed = 'extended-confirmed-protected', autoconfirmed = 'semi-protected', }, move = { default = 'move-protected' }, autoreview = { }, upload = { default = 'upload-protected' } }, -------------------------------------------------------------------------------- -- Images -------------------------------------------------------------------------------- -- This table lists different padlock images for each protection action and -- protection level. It is used if an image is not specified in any of the -- banner data tables, and if the page does not satisfy the conditions for using -- the ['image-filename-indef'] image. It is checked in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level images = { edit = { default = 'Full-protection-shackle.svg', templateeditor = 'Template-protection-shackle.svg', extendedconfirmed = 'Extended-protection-shackle.svg', autoconfirmed = 'Semi-protection-shackle.svg' }, move = { default = 'Move-protection-shackle.svg', }, autoreview = { default = 'Pending-protection-shackle.svg' }, upload = { default = 'Upload-protection-shackle.svg' } }, -- Pages with a reason specified in this table will show the special "indef" -- padlock, defined in the 'image-filename-indef' message, if no expiry is set. indefImageReasons = { template = true }, -------------------------------------------------------------------------------- -- Image links -------------------------------------------------------------------------------- -- This table provides the data for the ${IMAGELINK} parameter, which gets -- the image link for small padlock icons based on the page's protection action -- and protection level. It is checked in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. imageLinks = { edit = { default = 'Wikipedia:Protection policy#full', templateeditor = 'Wikipedia:Protection policy#template', extendedconfirmed = 'Wikipedia:Protection policy#extended', autoconfirmed = 'Wikipedia:Protection policy#semi' }, move = { default = 'Wikipedia:Protection policy#move' }, autoreview = { default = 'Wikipedia:Protection policy#pending' }, upload = { default = 'Wikipedia:Protection policy#upload' } }, -------------------------------------------------------------------------------- -- Padlock indicator names -------------------------------------------------------------------------------- -- This table provides the "name" attribute for the <indicator> extension tag -- with which small padlock icons are generated. All indicator tags on a page -- are displayed in alphabetical order based on this attribute, and with -- indicator tags with duplicate names, the last tag on the page wins. -- The attribute is chosen based on the protection action; table keys must be a -- protection action name or the string "default". padlockIndicatorNames = { autoreview = 'pp-autoreview', default = 'pp-default' }, -------------------------------------------------------------------------------- -- Protection categories -------------------------------------------------------------------------------- --[[ -- The protection categories are stored in the protectionCategories table. -- Keys to this table are made up of the following strings: -- -- 1. the expiry date -- 2. the namespace -- 3. the protection reason (e.g. "dispute" or "vandalism") -- 4. the protection level (e.g. "sysop" or "autoconfirmed") -- 5. the action (e.g. "edit" or "move") -- -- When the module looks up a category in the table, first it will will check to -- see a key exists that corresponds to all five parameters. For example, a -- user page semi-protected from vandalism for two weeks would have the key -- "temp-user-vandalism-autoconfirmed-edit". If no match is found, the module -- changes the first part of the key to "all" and checks the table again. It -- keeps checking increasingly generic key combinations until it finds the -- field, or until it reaches the key "all-all-all-all-all". -- -- The module uses a binary matrix to determine the order in which to search. -- This is best demonstrated by a table. In this table, the "0" values -- represent "all", and the "1" values represent the original data (e.g. -- "indef" or "file" or "vandalism"). -- -- expiry namespace reason level action -- order -- 1 1 1 1 1 1 -- 2 0 1 1 1 1 -- 3 1 0 1 1 1 -- 4 0 0 1 1 1 -- 5 1 1 0 1 1 -- 6 0 1 0 1 1 -- 7 1 0 0 1 1 -- 8 0 0 0 1 1 -- 9 1 1 1 0 1 -- 10 0 1 1 0 1 -- 11 1 0 1 0 1 -- 12 0 0 1 0 1 -- 13 1 1 0 0 1 -- 14 0 1 0 0 1 -- 15 1 0 0 0 1 -- 16 0 0 0 0 1 -- 17 1 1 1 1 0 -- 18 0 1 1 1 0 -- 19 1 0 1 1 0 -- 20 0 0 1 1 0 -- 21 1 1 0 1 0 -- 22 0 1 0 1 0 -- 23 1 0 0 1 0 -- 24 0 0 0 1 0 -- 25 1 1 1 0 0 -- 26 0 1 1 0 0 -- 27 1 0 1 0 0 -- 28 0 0 1 0 0 -- 29 1 1 0 0 0 -- 30 0 1 0 0 0 -- 31 1 0 0 0 0 -- 32 0 0 0 0 0 -- -- In this scheme the action has the highest priority, as it is the last -- to change, and the expiry has the least priority, as it changes the most. -- The priorities of the expiry, the protection level and the action are -- fixed, but the priorities of the reason and the namespace can be swapped -- through the use of the cfg.bannerDataNamespaceHasPriority table. --]] -- If the reason specified to the template is listed in this table, -- namespace data will take priority over reason data in the protectionCategories -- table. reasonsWithNamespacePriority = { vandalism = true, }, -- The string to use as a namespace key for the protectionCategories table for each -- namespace number. categoryNamespaceKeys = { [ 2] = 'user', [ 3] = 'user', [ 4] = 'project', [ 6] = 'file', [ 8] = 'mediawiki', [ 10] = 'template', [ 12] = 'project', [ 14] = 'category', [100] = 'portal', [828] = 'module', }, protectionCategories = { ['all|all|all|all|all'] = 'Wikipedia fully protected pages', ['all|all|office|all|all'] = 'Wikipedia Office-protected pages', ['all|all|reset|all|all'] = 'Wikipedia Office-protected pages', ['all|all|dmca|all|all'] = 'Wikipedia Office-protected pages', ['all|all|mainpage|all|all'] = 'Wikipedia fully protected main page files', ['all|all|all|extendedconfirmed|all'] = 'Wikipedia extended-confirmed-protected pages', ['all|all|ecp|extendedconfirmed|all'] = 'Wikipedia extended-confirmed-protected pages', ['all|template|all|all|edit'] = 'Wikipedia fully protected templates', ['all|all|all|autoconfirmed|edit'] = 'Wikipedia semi-protected pages', ['indef|all|all|autoconfirmed|edit'] = 'Wikipedia indefinitely semi-protected pages', ['all|all|blp|autoconfirmed|edit'] = 'Wikipedia indefinitely semi-protected biographies of living people', ['temp|all|blp|autoconfirmed|edit'] = 'Wikipedia temporarily semi-protected biographies of living people', ['all|all|dispute|autoconfirmed|edit'] = 'Wikipedia pages semi-protected due to dispute', ['all|all|sock|autoconfirmed|edit'] = 'Wikipedia pages semi-protected from banned users', ['all|all|vandalism|autoconfirmed|edit'] = 'Wikipedia pages semi-protected against vandalism', ['all|category|all|autoconfirmed|edit'] = 'Wikipedia semi-protected categories', ['all|file|all|autoconfirmed|edit'] = 'Wikipedia semi-protected files', ['all|portal|all|autoconfirmed|edit'] = 'Wikipedia semi-protected portals', ['all|project|all|autoconfirmed|edit'] = 'Wikipedia semi-protected project pages', ['all|talk|all|autoconfirmed|edit'] = 'Wikipedia semi-protected talk pages', ['all|template|all|autoconfirmed|edit'] = 'Wikipedia semi-protected templates', ['all|user|all|autoconfirmed|edit'] = 'Wikipedia semi-protected user and user talk pages', ['all|all|all|templateeditor|move'] = 'Wikipedia template-protected pages other than templates and modules', ['all|all|all|templateeditor|edit'] = 'Wikipedia template-protected pages other than templates and modules', ['all|template|all|templateeditor|edit'] = 'Wikipedia template-protected templates', ['all|template|all|templateeditor|move'] = 'Wikipedia template-protected templates', -- move-protected templates ['all|all|blp|sysop|edit'] = 'Wikipedia indefinitely protected biographies of living people', ['temp|all|blp|sysop|edit'] = 'Wikipedia temporarily protected biographies of living people', ['all|all|dispute|sysop|edit'] = 'Wikipedia pages protected due to dispute', ['all|all|sock|sysop|edit'] = 'Wikipedia pages protected from banned users', ['all|all|vandalism|sysop|edit'] = 'Wikipedia pages protected against vandalism', ['all|category|all|sysop|edit'] = 'Wikipedia fully protected categories', ['all|file|all|sysop|edit'] = 'Wikipedia fully protected files', ['all|project|all|sysop|edit'] = 'Wikipedia fully protected project pages', ['all|talk|all|sysop|edit'] = 'Wikipedia fully protected talk pages', ['all|template|all|extendedconfirmed|edit'] = 'Wikipedia extended-confirmed-protected templates', ['all|template|all|extendedconfirmed|move'] = 'Wikipedia extended-confirmed-protected templates', ['all|template|all|sysop|edit'] = 'Wikipedia fully protected templates', ['all|user|all|sysop|edit'] = 'Wikipedia fully protected user and user talk pages', ['all|module|all|all|edit'] = 'Wikipedia fully protected modules', ['all|module|all|templateeditor|edit'] = 'Wikipedia template-protected modules', ['all|module|all|extendedconfirmed|edit'] = 'Wikipedia extended-confirmed-protected modules', ['all|module|all|autoconfirmed|edit'] = 'Wikipedia semi-protected modules', ['all|all|all|sysop|move'] = 'Wikipedia move-protected pages', ['indef|all|all|sysop|move'] = 'Wikipedia indefinitely move-protected pages', ['all|all|dispute|sysop|move'] = 'Wikipedia pages move-protected due to dispute', ['all|all|vandalism|sysop|move'] = 'Wikipedia pages move-protected due to vandalism', ['all|portal|all|sysop|move'] = 'Wikipedia move-protected portals', ['all|project|all|sysop|move'] = 'Wikipedia move-protected project pages', ['all|talk|all|sysop|move'] = 'Wikipedia move-protected talk pages', ['all|template|all|sysop|move'] = 'Wikipedia move-protected templates', ['all|user|all|sysop|move'] = 'Wikipedia move-protected user and user talk pages', ['all|all|all|autoconfirmed|autoreview'] = 'Wikipedia pending changes protected pages', ['all|file|all|all|upload'] = 'Wikipedia upload-protected files', }, -------------------------------------------------------------------------------- -- Expiry category config -------------------------------------------------------------------------------- -- This table configures the expiry category behaviour for each protection -- action. -- * If set to true, setting that action will always categorise the page if -- an expiry parameter is not set. -- * If set to false, setting that action will never categorise the page. -- * If set to nil, the module will categorise the page if: -- 1) an expiry parameter is not set, and -- 2) a reason is provided, and -- 3) the specified reason is not blacklisted in the reasonsWithoutExpiryCheck -- table. expiryCheckActions = { edit = nil, move = false, autoreview = true, upload = false }, reasonsWithoutExpiryCheck = { blp = true, template = true, }, -------------------------------------------------------------------------------- -- Pagetypes -------------------------------------------------------------------------------- -- This table produces the page types available with the ${PAGETYPE} parameter. -- Keys are namespace numbers, or the string "default" for the default value. pagetypes = { [0] = 'article', [6] = 'file', [10] = 'template', [14] = 'category', [828] = 'module', default = 'page' }, -------------------------------------------------------------------------------- -- Strings marking indefinite protection -------------------------------------------------------------------------------- -- This table contains values passed to the expiry parameter that mean the page -- is protected indefinitely. indefStrings = { ['indef'] = true, ['indefinite'] = true, ['indefinitely'] = true, ['infinite'] = true, }, -------------------------------------------------------------------------------- -- Group hierarchy -------------------------------------------------------------------------------- -- This table maps each group to all groups that have a superset of the original -- group's page editing permissions. hierarchy = { sysop = {}, reviewer = {'sysop'}, filemover = {'sysop'}, templateeditor = {'sysop'}, extendedconfirmed = {'sysop'}, autoconfirmed = {'reviewer', 'filemover', 'templateeditor', 'extendedconfirmed'}, user = {'autoconfirmed'}, ['*'] = {'user'} }, -------------------------------------------------------------------------------- -- Wrapper templates and their default arguments -------------------------------------------------------------------------------- -- This table contains wrapper templates used with the module, and their -- default arguments. Templates specified in this table should contain the -- following invocation, and no other template content: -- -- {{#invoke:Protection banner|main}} -- -- If other content is desired, it can be added between -- <noinclude>...</noinclude> tags. -- -- When a user calls one of these wrapper templates, they will use the -- default arguments automatically. However, users can override any of the -- arguments. wrappers = { ['Template:Pp'] = {}, ['Template:Protection padlock'] = {}, ['Template:Pp-extended'] = {'ecp'}, ['Template:Pp-blp'] = {'blp'}, -- we don't need Template:Pp-create ['Template:Pp-dispute'] = {'dispute'}, ['Template:Pp-main-page'] = {'mainpage'}, ['Template:Pp-move'] = {action = 'move', catonly = 'yes'}, ['Template:Pp-move-dispute'] = {'dispute', action = 'move', catonly = 'yes'}, -- we don't need Template:Pp-move-indef ['Template:Pp-move-vandalism'] = {'vandalism', action = 'move', catonly = 'yes'}, ['Template:Pp-office'] = {'office'}, ['Template:Pp-office-dmca'] = {'dmca'}, ['Template:Pp-pc'] = {action = 'autoreview', small = true}, ['Template:Pp-pc1'] = {action = 'autoreview', small = true}, ['Template:Pp-reset'] = {'reset'}, ['Template:Pp-semi-indef'] = {small = true}, ['Template:Pp-sock'] = {'sock'}, ['Template:Pp-template'] = {'template', small = true}, ['Template:Pp-upload'] = {action = 'upload'}, ['Template:Pp-usertalk'] = {'usertalk'}, ['Template:Pp-vandalism'] = {'vandalism'}, }, -------------------------------------------------------------------------------- -- -- MESSAGES -- -------------------------------------------------------------------------------- msg = { -------------------------------------------------------------------------------- -- Intro blurb and intro fragment -------------------------------------------------------------------------------- -- These messages specify what is produced by the ${INTROBLURB} and -- ${INTROFRAGMENT} parameters. If the protection is temporary they use the -- intro-blurb-expiry or intro-fragment-expiry, and if not they use -- intro-blurb-noexpiry or intro-fragment-noexpiry. -- It is possible to use banner parameters in these messages. ['intro-blurb-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY}.', ['intro-blurb-noexpiry'] = '${PROTECTIONBLURB}.', ['intro-fragment-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY},', ['intro-fragment-noexpiry'] = '${PROTECTIONBLURB}', -------------------------------------------------------------------------------- -- Tooltip blurb -------------------------------------------------------------------------------- -- These messages specify what is produced by the ${TOOLTIPBLURB} parameter. -- If the protection is temporary the tooltip-blurb-expiry message is used, and -- if not the tooltip-blurb-noexpiry message is used. -- It is possible to use banner parameters in these messages. ['tooltip-blurb-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY}.', ['tooltip-blurb-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}.', ['tooltip-fragment-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY},', ['tooltip-fragment-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}', -------------------------------------------------------------------------------- -- Special explanation blurb -------------------------------------------------------------------------------- -- An explanation blurb for pages that cannot be unprotected, e.g. for pages -- in the MediaWiki namespace. -- It is possible to use banner parameters in this message. ['explanation-blurb-nounprotect'] = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Please discuss any changes on the ${TALKPAGE}; you' .. ' may ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] to make an edit if it' .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by [[Wikipedia:Consensus' .. '|consensus]].', -------------------------------------------------------------------------------- -- Protection log display values -------------------------------------------------------------------------------- -- These messages determine the display values for the protection log link -- or the pending changes log link produced by the ${PROTECTIONLOG} parameter. -- It is possible to use banner parameters in these messages. ['protection-log-display'] = 'protection log', ['pc-log-display'] = 'pending changes log', -------------------------------------------------------------------------------- -- Current version display values -------------------------------------------------------------------------------- -- These messages determine the display values for the page history link -- or the move log link produced by the ${CURRENTVERSION} parameter. -- It is possible to use banner parameters in these messages. ['current-version-move-display'] = 'current title', ['current-version-edit-display'] = 'current version', -------------------------------------------------------------------------------- -- Talk page -------------------------------------------------------------------------------- -- This message determines the display value of the talk page link produced -- with the ${TALKPAGE} parameter. -- It is possible to use banner parameters in this message. ['talk-page-link-display'] = 'talk page', -------------------------------------------------------------------------------- -- Edit requests -------------------------------------------------------------------------------- -- This message determines the display value of the edit request link produced -- with the ${EDITREQUEST} parameter. -- It is possible to use banner parameters in this message. ['edit-request-display'] = 'submit an edit request', -------------------------------------------------------------------------------- -- Expiry date format -------------------------------------------------------------------------------- -- This is the format for the blurb expiry date. It should be valid input for -- the first parameter of the #time parser function. ['expiry-date-format'] = 'F j, Y "at" H:i e', -------------------------------------------------------------------------------- -- Tracking categories -------------------------------------------------------------------------------- -- These messages determine which tracking categories the module outputs. ['tracking-category-incorrect'] = 'Wikipedia pages with incorrect protection templates', ['tracking-category-template'] = 'Wikipedia template-protected pages other than templates and modules', -------------------------------------------------------------------------------- -- Images -------------------------------------------------------------------------------- -- These are images that are not defined by their protection action and protection level. ['image-filename-indef'] = 'Full-protection-shackle.svg', ['image-filename-default'] = 'Transparent.gif', -------------------------------------------------------------------------------- -- End messages -------------------------------------------------------------------------------- } -------------------------------------------------------------------------------- -- End configuration -------------------------------------------------------------------------------- } g4f6dkimoikv05ulbf832zu7omkf2fc 740062 740061 2026-05-01T19:16:16Z Novem Linguae 49714 1 revision imported from [[:en:Module:Protection_banner/config]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740061 Scribunto text/plain -- This module provides configuration data for [[Module:Protection banner]]. return { -------------------------------------------------------------------------------- -- -- BANNER DATA -- -------------------------------------------------------------------------------- --[[ -- Banner data consists of six fields: -- * text - the main protection text that appears at the top of protection -- banners. -- * explanation - the text that appears below the main protection text, used -- to explain the details of the protection. -- * tooltip - the tooltip text you see when you move the mouse over a small -- padlock icon. -- * link - the page that the small padlock icon links to. -- * alt - the alt text for the small padlock icon. This is also used as tooltip -- text for the large protection banners. -- * image - the padlock image used in both protection banners and small padlock -- icons. -- -- The module checks in three separate tables to find a value for each field. -- First it checks the banners table, which has values specific to the reason -- for the page being protected. Then the module checks the defaultBanners -- table, which has values specific to each protection level. Finally, the -- module checks the masterBanner table, which holds data for protection -- templates to use if no data has been found in the previous two tables. -- -- The values in the banner data can take parameters. These are specified -- using ${TEXTLIKETHIS} (a dollar sign preceding a parameter name -- enclosed in curly braces). -- -- Available parameters: -- -- ${CURRENTVERSION} - a link to the page history or the move log, with the -- display message "current-version-edit-display" or -- "current-version-move-display". -- -- ${EDITREQUEST} - a link to create an edit request for the current page. -- -- ${EXPLANATIONBLURB} - an explanation blurb, e.g. "Please discuss any changes -- on the talk page; you may submit a request to ask an administrator to make -- an edit if it is minor or supported by consensus." -- -- ${IMAGELINK} - a link to set the image to, depending on the protection -- action and protection level. -- -- ${INTROBLURB} - the PROTECTIONBLURB parameter, plus the expiry if an expiry -- is set. E.g. "Editing of this page by new or unregistered users is currently -- disabled until dd Month YYYY." -- -- ${INTROFRAGMENT} - the same as ${INTROBLURB}, but without final punctuation -- so that it can be used in run-on sentences. -- -- ${PAGETYPE} - the type of the page, e.g. "article" or "template". -- Defined in the cfg.pagetypes table. -- -- ${PROTECTIONBLURB} - a blurb explaining the protection level of the page, e.g. -- "Editing of this page by new or unregistered users is currently disabled" -- -- ${PROTECTIONDATE} - the protection date, if it has been supplied to the -- template. -- -- ${PROTECTIONLEVEL} - the protection level, e.g. "fully protected" or -- "semi-protected". -- -- ${PROTECTIONLOG} - a link to the protection log or the pending changes log, -- depending on the protection action. -- -- ${TALKPAGE} - a link to the talk page. If a section is specified, links -- straight to that talk page section. -- -- ${TOOLTIPBLURB} - uses the PAGETYPE, PROTECTIONTYPE and EXPIRY parameters to -- create a blurb like "This template is semi-protected", or "This article is -- move-protected until DD Month YYYY". -- -- ${VANDAL} - links for the specified username (or the root page name) -- using Module:Vandal-m. -- -- Functions -- -- For advanced users, it is possible to use Lua functions instead of strings -- in the banner config tables. Using functions gives flexibility that is not -- possible just by using parameters. Functions take two arguments, the -- protection object and the template arguments, and they must output a string. -- -- For example: -- -- text = function (protectionObj, args) -- if protectionObj.level == 'autoconfirmed' then -- return 'foo' -- else -- return 'bar' -- end -- end -- -- Some protection object properties and methods that may be useful: -- protectionObj.action - the protection action -- protectionObj.level - the protection level -- protectionObj.reason - the protection reason -- protectionObj.expiry - the expiry. Nil if unset, the string "indef" if set -- to indefinite, and the protection time in unix time if temporary. -- protectionObj.protectionDate - the protection date in unix time, or nil if -- unspecified. -- protectionObj.bannerConfig - the banner config found by the module. Beware -- of editing the config field used by the function, as it could create an -- infinite loop. -- protectionObj:isProtected - returns a boolean showing whether the page is -- protected. -- protectionObj:isTemporary - returns a boolean showing whether the expiry is -- temporary. -- protectionObj:isIncorrect - returns a boolean showing whether the protection -- template is incorrect. --]] -- The master banner data, used if no values have been found in banners or -- defaultBanners. masterBanner = { text = '${INTROBLURB}', explanation = '${EXPLANATIONBLURB}', tooltip = '${TOOLTIPBLURB}', link = '${IMAGELINK}', alt = 'Page ${PROTECTIONLEVEL}' }, -- The default banner data. This holds banner data for different protection -- levels. -- *required* - this table needs edit, move, autoreview and upload subtables. defaultBanners = { edit = {}, move = {}, autoreview = { default = { alt = 'Page protected with pending changes', tooltip = 'All edits by unregistered and new users are subject to review prior to becoming visible to unregistered users', image = 'Pending-protection-shackle.svg' } }, upload = {} }, -- The banner data. This holds banner data for different protection reasons. -- In fact, the reasons specified in this table control which reasons are -- valid inputs to the first positional parameter. -- -- There is also a non-standard "description" field that can be used for items -- in this table. This is a description of the protection reason for use in the -- module documentation. -- -- *required* - this table needs edit, move, autoreview and upload subtables. banners = { edit = { blp = { description = 'For pages protected to promote compliance with the' .. ' [[Wikipedia:Biographies of living persons' .. '|biographies of living persons]] policy', text = '${INTROFRAGMENT} to promote compliance with' .. ' [[Wikipedia:Biographies of living persons' .. "|Wikipedia's&nbsp;policy on&nbsp;the&nbsp;biographies" .. ' of&nbsp;living&nbsp;people]].', tooltip = '${TOOLTIPFRAGMENT} to promote compliance with the policy on' .. ' biographies of living persons', }, deceased = { description = 'For user pages of Wikipedia users who are deceased', text = '${INTROFRAGMENT} to prevent vandalism of a deceased' .. ' Wikipedian\'s user page.' .. ' A family member who wishes to edit this user page can use this' .. ' user\'s ${TALKPAGE} or submit a request to [[Wikipedia:VRT|the' .. ' Volunteer Response Team]].', tooltip = '${TOOLTIPFRAGMENT} because this Wikipedian is deceased' }, dmca = { description = 'For pages protected by the Wikimedia Foundation' .. ' due to [[Digital Millennium Copyright Act]] takedown requests', explanation = function (protectionObj, args) local ret = 'Pursuant to a rights owner notice under the Digital' .. ' Millennium Copyright Act (DMCA) regarding some content' .. ' in this article, the Wikimedia Foundation acted under' .. ' applicable law and took down and restricted the content' .. ' in question.' if args.notice then ret = ret .. ' A copy of the received notice can be found here: ' .. args.notice .. '.' end ret = ret .. ' For more information, including websites discussing' .. ' how to file a counter-notice, please see' .. " [[Wikipedia:Office actions]] and the article's ${TALKPAGE}." .. "'''Do not remove this template from the article until the" .. " restrictions are withdrawn'''." return ret end, image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, dispute = { description = 'For pages protected due to editing disputes', text = function (protectionObj, args) -- Find the value of "disputes". local display = 'disputes' local disputes if args.section then disputes = string.format( '[[%s:%s#%s|%s]]', mw.site.namespaces[protectionObj.title.namespace].talk.name, protectionObj.title.text, args.section, display ) else disputes = display end -- Make the blurb, depending on the expiry. local msg if type(protectionObj.expiry) == 'number' then msg = '${INTROFRAGMENT} or until editing %s have been resolved.' else msg = '${INTROFRAGMENT} until editing %s have been resolved.' end return string.format(msg, disputes) end, explanation = "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', tooltip = '${TOOLTIPFRAGMENT} due to editing disputes', }, ecp = { description = 'For articles in topic areas authorized by' .. ' [[Wikipedia:Arbitration Committee|ArbCom]] or' .. ' meets the criteria for community use', alt = 'Extended-protected ${PAGETYPE}', }, mainpage = { description = 'For pages protected for being displayed on the [[Main Page]]', text = 'This file is currently' .. ' [[Wikipedia:This page is protected|protected]] from' .. ' editing because it is currently or will soon be displayed' .. ' on the [[Main Page]].', explanation = 'Images on the Main Page are protected due to their high' .. ' visibility. Please discuss any necessary changes on the ${TALKPAGE}.' .. '<br /><span style="font-size:90%;">' .. "'''Administrators:''' Once this image is definitely off the Main Page," .. ' please unprotect this file, or reduce to semi-protection,' .. ' as appropriate.</span>', }, office = { description = 'For pages protected by the Wikimedia Foundation', text = function (protectionObj, args) local ret = 'This ${PAGETYPE} is currently under the' .. ' scrutiny of the' .. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]' .. ' and is protected.' if protectionObj.protectionDate then ret = ret .. ' It has been protected since ${PROTECTIONDATE}.' end return ret end, explanation = "If you can edit this page, please discuss all changes and" .. " additions on the ${TALKPAGE} first. '''Do not remove protection from this" .. " page unless you are authorized by the Wikimedia Foundation to do" .. " so.'''", image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, reset = { description = 'For pages protected by the Wikimedia Foundation and' .. ' "reset" to a bare-bones version', text = 'This ${PAGETYPE} is currently under the' .. ' scrutiny of the' .. ' [[Wikipedia:Office actions|Wikimedia Foundation Office]]' .. ' and is protected.', explanation = function (protectionObj, args) local ret = '' if protectionObj.protectionDate then ret = ret .. 'On ${PROTECTIONDATE} this ${PAGETYPE} was' else ret = ret .. 'This ${PAGETYPE} has been' end ret = ret .. ' reduced to a' .. ' simplified, "bare bones" version so that it may be completely' .. ' rewritten to ensure it meets the policies of' .. ' [[WP:NPOV|Neutral Point of View]] and [[WP:V|Verifiability]].' .. ' Standard Wikipedia policies will apply to its rewriting—which' .. ' will eventually be open to all editors—and will be strictly' .. ' enforced. The ${PAGETYPE} has been ${PROTECTIONLEVEL} while' .. ' it is being rebuilt.\n\n' .. 'Any insertion of material directly from' .. ' pre-protection revisions of the ${PAGETYPE} will be removed, as' .. ' will any material added to the ${PAGETYPE} that is not properly' .. ' sourced. The associated talk page(s) were also cleared on the' .. " same date.\n\n" .. "If you can edit this page, please discuss all changes and" .. " additions on the ${TALKPAGE} first. '''Do not override" .. " this action, and do not remove protection from this page," .. " unless you are authorized by the Wikimedia Foundation" .. " to do so. No editor may remove this notice.'''" return ret end, image = 'Office-protection-shackle.svg', link = 'Wikipedia:Protection policy#office', }, sock = { description = 'For pages protected due to' .. ' [[Wikipedia:Sock puppetry|sock puppetry]]', text = '${INTROFRAGMENT} to prevent [[Wikipedia:Sock puppetry|sock puppets]] of' .. ' [[Wikipedia:Blocking policy|blocked]] or' .. ' [[Wikipedia:Banning policy|banned users]]' .. ' from editing it.', tooltip = '${TOOLTIPFRAGMENT} to prevent sock puppets of blocked or banned users from' .. ' editing it', }, template = { description = 'For [[Wikipedia:High-risk templates|high-risk]]' .. ' templates and Lua modules', text = 'This is a permanently [[Wikipedia:Protection policy|protected]] ${PAGETYPE},' .. ' as it is [[Wikipedia:High-risk templates|high-risk]].', explanation = 'Please discuss any changes on the ${TALKPAGE}; you may' .. ' ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] or' .. ' [[Wikipedia:Template editor|template editor]] to make an edit if' .. ' it is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by' .. ' [[Wikipedia:Consensus|consensus]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.', tooltip = 'This high-risk ${PAGETYPE} is permanently ${PROTECTIONLEVEL}' .. ' to prevent vandalism', alt = 'Permanently protected ${PAGETYPE}', }, usertalk = { description = 'For pages protected against disruptive edits by a' .. ' particular user', text = '${INTROFRAGMENT} to prevent ${VANDAL} from using it to make disruptive edits,' .. ' such as abusing the' .. ' &#123;&#123;[[Template:unblock|unblock]]&#125;&#125; template.', explanation = 'If you cannot edit this user talk page and you need to' .. ' make a change or leave a message, you can' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for edits to a protected page' .. '|request an edit]],' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]],' .. ' [[Special:Userlogin|log in]],' .. ' or [[Special:UserLogin/signup|create an account]].', }, vandalism = { description = 'For pages protected against' .. ' [[Wikipedia:Vandalism|vandalism]]', text = '${INTROFRAGMENT} due to [[Wikipedia:Vandalism|vandalism]].', explanation = function (protectionObj, args) local ret = '' if protectionObj.level == 'sysop' then ret = ret .. "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ' end return ret .. '${EXPLANATIONBLURB}' end, tooltip = '${TOOLTIPFRAGMENT} due to vandalism', } }, move = { dispute = { description = 'For pages protected against page moves due to' .. ' disputes over the page title', explanation = "This protection is '''not''' an endorsement of the" .. ' ${CURRENTVERSION}. ${EXPLANATIONBLURB}', image = 'Move-protection-shackle.svg' }, vandalism = { description = 'For pages protected against' .. ' [[Wikipedia:Vandalism#Page-move vandalism' .. ' |page-move vandalism]]' } }, autoreview = {}, upload = {} }, -------------------------------------------------------------------------------- -- -- GENERAL DATA TABLES -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Protection blurbs -------------------------------------------------------------------------------- -- This table produces the protection blurbs available with the -- ${PROTECTIONBLURB} parameter. It is sorted by protection action and -- protection level, and is checked by the module in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. protectionBlurbs = { edit = { default = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#full|' .. 'protected]] from editing', templateeditor = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#template' .. '|protected]] from editing', autoconfirmed = 'Editing of this ${PAGETYPE} by [[Wikipedia:User access' .. ' levels#New users|new]] or [[Wikipedia:User access levels#Unregistered' .. ' users|unregistered]] users is currently [[Wikipedia:Protection' .. ' policy#semi|disabled]]', extendedconfirmed = 'This ${PAGETYPE} is currently under [[Wikipedia:Protection' .. ' policy#extended|extended confirmed protection]]', }, move = { default = 'This ${PAGETYPE} is currently [[Wikipedia:Protection policy#Move' .. ' protection|protected]] from [[Help:Moving a page|page moves]]' }, autoreview = { default = 'All edits made to this ${PAGETYPE} by' .. ' [[Wikipedia:User access levels#New users|new]] or' .. ' [[Wikipedia:User access levels#Unregistered users|unregistered]]' .. ' users are currently' .. ' [[Wikipedia:Pending changes|subject to review]]' }, upload = { default = 'Uploading new versions of this ${PAGETYPE} is currently disabled' } }, -------------------------------------------------------------------------------- -- Explanation blurbs -------------------------------------------------------------------------------- -- This table produces the explanation blurbs available with the -- ${EXPLANATIONBLURB} parameter. It is sorted by protection action, -- protection level, and whether the page is a talk page or not. If the page is -- a talk page it will have a talk key of "talk"; otherwise it will have a talk -- key of "subject". The table is checked in the following order: -- 1. page's protection action, page's protection level, page's talk key -- 2. page's protection action, page's protection level, default talk key -- 3. page's protection action, default protection level, page's talk key -- 4. page's protection action, default protection level, default talk key -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. explanationBlurbs = { edit = { autoconfirmed = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details. If you' .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' .. ' ${EDITREQUEST}, discuss changes on the ${TALKPAGE},' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details. If you' .. ' cannot edit this ${PAGETYPE} and you wish to make a change, you can' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].', }, extendedconfirmed = { default = 'Extended confirmed protection prevents edits from all unregistered editors' .. ' and registered users with fewer than 30 days tenure and 500 edits.' .. ' The [[Wikipedia:Protection policy#extended|policy on community use]]' .. ' specifies that extended confirmed protection can be applied to combat' .. ' disruption, if semi-protection has proven to be ineffective.' .. ' Extended confirmed protection may also be applied to enforce' .. ' [[Wikipedia:Arbitration Committee|arbitration sanctions]].' .. ' Please discuss any changes on the ${TALKPAGE}; you may' .. ' ${EDITREQUEST} to ask for uncontroversial changes supported by' .. ' [[Wikipedia:Consensus|consensus]].' }, default = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Please discuss any changes on the ${TALKPAGE}; you' .. ' may ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] to make an edit if it' .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by [[Wikipedia:Consensus' .. '|consensus]]. You may also [[Wikipedia:Requests for' .. ' page protection#Current requests for reduction in protection level' .. '|request]] that this page be unprotected.', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' You may [[Wikipedia:Requests for page' .. ' protection#Current requests for edits to a protected page|request an' .. ' edit]] to this page, or [[Wikipedia:Requests for' .. ' page protection#Current requests for reduction in protection level' .. '|ask]] for it to be unprotected.' } }, move = { default = { subject = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but cannot be moved' .. ' until unprotected. Please discuss any suggested moves on the' .. ' ${TALKPAGE} or at [[Wikipedia:Requested moves]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.', default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but cannot be moved' .. ' until unprotected. Please discuss any suggested moves at' .. ' [[Wikipedia:Requested moves]]. You can also' .. ' [[Wikipedia:Requests for page protection|request]] that the page be' .. ' unprotected.' } }, autoreview = { default = { default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Edits to this ${PAGETYPE} by new and unregistered users' .. ' will not be visible to readers until they are accepted by' .. ' a reviewer. To avoid the need for your edits to be' .. ' reviewed, you may' .. ' [[Wikipedia:Requests for page protection' .. '#Current requests for reduction in protection level' .. '|request unprotection]], [[Special:Userlogin|log in]], or' .. ' [[Special:UserLogin/signup|create an account]].' }, }, upload = { default = { default = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' The page may still be edited but new versions of the file' .. ' cannot be uploaded until it is unprotected. You can' .. ' request that a new version be uploaded by using a' .. ' [[Wikipedia:Edit requests|protected edit request]], or you' .. ' can [[Wikipedia:Requests for page protection|request]]' .. ' that the file be unprotected.' } } }, -------------------------------------------------------------------------------- -- Protection levels -------------------------------------------------------------------------------- -- This table provides the data for the ${PROTECTIONLEVEL} parameter, which -- produces a short label for different protection levels. It is sorted by -- protection action and protection level, and is checked in the following -- order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. protectionLevels = { edit = { default = 'protected', templateeditor = 'template-protected', extendedconfirmed = 'extended-confirmed-protected', autoconfirmed = 'semi-protected', }, move = { default = 'move-protected' }, autoreview = { }, upload = { default = 'upload-protected' } }, -------------------------------------------------------------------------------- -- Images -------------------------------------------------------------------------------- -- This table lists different padlock images for each protection action and -- protection level. It is used if an image is not specified in any of the -- banner data tables, and if the page does not satisfy the conditions for using -- the ['image-filename-indef'] image. It is checked in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level images = { edit = { default = 'Full-protection-shackle.svg', templateeditor = 'Template-protection-shackle.svg', extendedconfirmed = 'Extended-protection-shackle.svg', autoconfirmed = 'Semi-protection-shackle.svg' }, move = { default = 'Move-protection-shackle.svg', }, autoreview = { default = 'Pending-protection-shackle.svg' }, upload = { default = 'Upload-protection-shackle.svg' } }, -- Pages with a reason specified in this table will show the special "indef" -- padlock, defined in the 'image-filename-indef' message, if no expiry is set. indefImageReasons = { template = true }, -------------------------------------------------------------------------------- -- Image links -------------------------------------------------------------------------------- -- This table provides the data for the ${IMAGELINK} parameter, which gets -- the image link for small padlock icons based on the page's protection action -- and protection level. It is checked in the following order: -- 1. page's protection action, page's protection level -- 2. page's protection action, default protection level -- 3. "edit" protection action, default protection level -- -- It is possible to use banner parameters inside this table. -- *required* - this table needs edit, move, autoreview and upload subtables. imageLinks = { edit = { default = 'Wikipedia:Protection policy#full', templateeditor = 'Wikipedia:Protection policy#template', extendedconfirmed = 'Wikipedia:Protection policy#extended', autoconfirmed = 'Wikipedia:Protection policy#semi' }, move = { default = 'Wikipedia:Protection policy#move' }, autoreview = { default = 'Wikipedia:Protection policy#pending' }, upload = { default = 'Wikipedia:Protection policy#upload' } }, -------------------------------------------------------------------------------- -- Padlock indicator names -------------------------------------------------------------------------------- -- This table provides the "name" attribute for the <indicator> extension tag -- with which small padlock icons are generated. All indicator tags on a page -- are displayed in alphabetical order based on this attribute, and with -- indicator tags with duplicate names, the last tag on the page wins. -- The attribute is chosen based on the protection action; table keys must be a -- protection action name or the string "default". padlockIndicatorNames = { autoreview = 'pp-autoreview', default = 'pp-default' }, -------------------------------------------------------------------------------- -- Protection categories -------------------------------------------------------------------------------- --[[ -- The protection categories are stored in the protectionCategories table. -- Keys to this table are made up of the following strings: -- -- 1. the expiry date -- 2. the namespace -- 3. the protection reason (e.g. "dispute" or "vandalism") -- 4. the protection level (e.g. "sysop" or "autoconfirmed") -- 5. the action (e.g. "edit" or "move") -- -- When the module looks up a category in the table, first it will will check to -- see a key exists that corresponds to all five parameters. For example, a -- user page semi-protected from vandalism for two weeks would have the key -- "temp-user-vandalism-autoconfirmed-edit". If no match is found, the module -- changes the first part of the key to "all" and checks the table again. It -- keeps checking increasingly generic key combinations until it finds the -- field, or until it reaches the key "all-all-all-all-all". -- -- The module uses a binary matrix to determine the order in which to search. -- This is best demonstrated by a table. In this table, the "0" values -- represent "all", and the "1" values represent the original data (e.g. -- "indef" or "file" or "vandalism"). -- -- expiry namespace reason level action -- order -- 1 1 1 1 1 1 -- 2 0 1 1 1 1 -- 3 1 0 1 1 1 -- 4 0 0 1 1 1 -- 5 1 1 0 1 1 -- 6 0 1 0 1 1 -- 7 1 0 0 1 1 -- 8 0 0 0 1 1 -- 9 1 1 1 0 1 -- 10 0 1 1 0 1 -- 11 1 0 1 0 1 -- 12 0 0 1 0 1 -- 13 1 1 0 0 1 -- 14 0 1 0 0 1 -- 15 1 0 0 0 1 -- 16 0 0 0 0 1 -- 17 1 1 1 1 0 -- 18 0 1 1 1 0 -- 19 1 0 1 1 0 -- 20 0 0 1 1 0 -- 21 1 1 0 1 0 -- 22 0 1 0 1 0 -- 23 1 0 0 1 0 -- 24 0 0 0 1 0 -- 25 1 1 1 0 0 -- 26 0 1 1 0 0 -- 27 1 0 1 0 0 -- 28 0 0 1 0 0 -- 29 1 1 0 0 0 -- 30 0 1 0 0 0 -- 31 1 0 0 0 0 -- 32 0 0 0 0 0 -- -- In this scheme the action has the highest priority, as it is the last -- to change, and the expiry has the least priority, as it changes the most. -- The priorities of the expiry, the protection level and the action are -- fixed, but the priorities of the reason and the namespace can be swapped -- through the use of the cfg.bannerDataNamespaceHasPriority table. --]] -- If the reason specified to the template is listed in this table, -- namespace data will take priority over reason data in the protectionCategories -- table. reasonsWithNamespacePriority = { vandalism = true, }, -- The string to use as a namespace key for the protectionCategories table for each -- namespace number. categoryNamespaceKeys = { [ 2] = 'user', [ 3] = 'user', [ 4] = 'project', [ 6] = 'file', [ 8] = 'mediawiki', [ 10] = 'template', [ 12] = 'project', [ 14] = 'category', [100] = 'portal', [828] = 'module', }, protectionCategories = { ['all|all|all|all|all'] = 'Wikipedia fully protected pages', ['all|all|office|all|all'] = 'Wikipedia Office-protected pages', ['all|all|reset|all|all'] = 'Wikipedia Office-protected pages', ['all|all|dmca|all|all'] = 'Wikipedia Office-protected pages', ['all|all|mainpage|all|all'] = 'Wikipedia fully protected main page files', ['all|all|all|extendedconfirmed|all'] = 'Wikipedia extended-confirmed-protected pages', ['all|all|ecp|extendedconfirmed|all'] = 'Wikipedia extended-confirmed-protected pages', ['all|template|all|all|edit'] = 'Wikipedia fully protected templates', ['all|all|all|autoconfirmed|edit'] = 'Wikipedia semi-protected pages', ['indef|all|all|autoconfirmed|edit'] = 'Wikipedia indefinitely semi-protected pages', ['all|all|blp|autoconfirmed|edit'] = 'Wikipedia indefinitely semi-protected biographies of living people', ['temp|all|blp|autoconfirmed|edit'] = 'Wikipedia temporarily semi-protected biographies of living people', ['all|all|dispute|autoconfirmed|edit'] = 'Wikipedia pages semi-protected due to dispute', ['all|all|sock|autoconfirmed|edit'] = 'Wikipedia pages semi-protected from banned users', ['all|all|vandalism|autoconfirmed|edit'] = 'Wikipedia pages semi-protected against vandalism', ['all|category|all|autoconfirmed|edit'] = 'Wikipedia semi-protected categories', ['all|file|all|autoconfirmed|edit'] = 'Wikipedia semi-protected files', ['all|portal|all|autoconfirmed|edit'] = 'Wikipedia semi-protected portals', ['all|project|all|autoconfirmed|edit'] = 'Wikipedia semi-protected project pages', ['all|talk|all|autoconfirmed|edit'] = 'Wikipedia semi-protected talk pages', ['all|template|all|autoconfirmed|edit'] = 'Wikipedia semi-protected templates', ['all|user|all|autoconfirmed|edit'] = 'Wikipedia semi-protected user and user talk pages', ['all|all|all|templateeditor|move'] = 'Wikipedia template-protected pages other than templates and modules', ['all|all|all|templateeditor|edit'] = 'Wikipedia template-protected pages other than templates and modules', ['all|template|all|templateeditor|edit'] = 'Wikipedia template-protected templates', ['all|template|all|templateeditor|move'] = 'Wikipedia template-protected templates', -- move-protected templates ['all|all|blp|sysop|edit'] = 'Wikipedia indefinitely protected biographies of living people', ['temp|all|blp|sysop|edit'] = 'Wikipedia temporarily protected biographies of living people', ['all|all|dispute|sysop|edit'] = 'Wikipedia pages protected due to dispute', ['all|all|sock|sysop|edit'] = 'Wikipedia pages protected from banned users', ['all|all|vandalism|sysop|edit'] = 'Wikipedia pages protected against vandalism', ['all|category|all|sysop|edit'] = 'Wikipedia fully protected categories', ['all|file|all|sysop|edit'] = 'Wikipedia fully protected files', ['all|project|all|sysop|edit'] = 'Wikipedia fully protected project pages', ['all|talk|all|sysop|edit'] = 'Wikipedia fully protected talk pages', ['all|template|all|extendedconfirmed|edit'] = 'Wikipedia extended-confirmed-protected templates', ['all|template|all|extendedconfirmed|move'] = 'Wikipedia extended-confirmed-protected templates', ['all|template|all|sysop|edit'] = 'Wikipedia fully protected templates', ['all|user|all|sysop|edit'] = 'Wikipedia fully protected user and user talk pages', ['all|module|all|all|edit'] = 'Wikipedia fully protected modules', ['all|module|all|templateeditor|edit'] = 'Wikipedia template-protected modules', ['all|module|all|extendedconfirmed|edit'] = 'Wikipedia extended-confirmed-protected modules', ['all|module|all|autoconfirmed|edit'] = 'Wikipedia semi-protected modules', ['all|all|all|sysop|move'] = 'Wikipedia move-protected pages', ['indef|all|all|sysop|move'] = 'Wikipedia indefinitely move-protected pages', ['all|all|dispute|sysop|move'] = 'Wikipedia pages move-protected due to dispute', ['all|all|vandalism|sysop|move'] = 'Wikipedia pages move-protected due to vandalism', ['all|portal|all|sysop|move'] = 'Wikipedia move-protected portals', ['all|project|all|sysop|move'] = 'Wikipedia move-protected project pages', ['all|talk|all|sysop|move'] = 'Wikipedia move-protected talk pages', ['all|template|all|sysop|move'] = 'Wikipedia move-protected templates', ['all|user|all|sysop|move'] = 'Wikipedia move-protected user and user talk pages', ['all|all|all|autoconfirmed|autoreview'] = 'Wikipedia pending changes protected pages', ['all|file|all|all|upload'] = 'Wikipedia upload-protected files', }, -------------------------------------------------------------------------------- -- Expiry category config -------------------------------------------------------------------------------- -- This table configures the expiry category behaviour for each protection -- action. -- * If set to true, setting that action will always categorise the page if -- an expiry parameter is not set. -- * If set to false, setting that action will never categorise the page. -- * If set to nil, the module will categorise the page if: -- 1) an expiry parameter is not set, and -- 2) a reason is provided, and -- 3) the specified reason is not blacklisted in the reasonsWithoutExpiryCheck -- table. expiryCheckActions = { edit = nil, move = false, autoreview = true, upload = false }, reasonsWithoutExpiryCheck = { blp = true, template = true, }, -------------------------------------------------------------------------------- -- Pagetypes -------------------------------------------------------------------------------- -- This table produces the page types available with the ${PAGETYPE} parameter. -- Keys are namespace numbers, or the string "default" for the default value. pagetypes = { [0] = 'article', [6] = 'file', [10] = 'template', [14] = 'category', [828] = 'module', default = 'page' }, -------------------------------------------------------------------------------- -- Strings marking indefinite protection -------------------------------------------------------------------------------- -- This table contains values passed to the expiry parameter that mean the page -- is protected indefinitely. indefStrings = { ['indef'] = true, ['indefinite'] = true, ['indefinitely'] = true, ['infinite'] = true, }, -------------------------------------------------------------------------------- -- Group hierarchy -------------------------------------------------------------------------------- -- This table maps each group to all groups that have a superset of the original -- group's page editing permissions. hierarchy = { sysop = {}, reviewer = {'sysop'}, filemover = {'sysop'}, templateeditor = {'sysop'}, extendedconfirmed = {'sysop'}, autoconfirmed = {'reviewer', 'filemover', 'templateeditor', 'extendedconfirmed'}, user = {'autoconfirmed'}, ['*'] = {'user'} }, -------------------------------------------------------------------------------- -- Wrapper templates and their default arguments -------------------------------------------------------------------------------- -- This table contains wrapper templates used with the module, and their -- default arguments. Templates specified in this table should contain the -- following invocation, and no other template content: -- -- {{#invoke:Protection banner|main}} -- -- If other content is desired, it can be added between -- <noinclude>...</noinclude> tags. -- -- When a user calls one of these wrapper templates, they will use the -- default arguments automatically. However, users can override any of the -- arguments. wrappers = { ['Template:Pp'] = {}, ['Template:Protection padlock'] = {}, ['Template:Pp-extended'] = {'ecp'}, ['Template:Pp-blp'] = {'blp'}, -- we don't need Template:Pp-create ['Template:Pp-dispute'] = {'dispute'}, ['Template:Pp-main-page'] = {'mainpage'}, ['Template:Pp-move'] = {action = 'move', catonly = 'yes'}, ['Template:Pp-move-dispute'] = {'dispute', action = 'move', catonly = 'yes'}, -- we don't need Template:Pp-move-indef ['Template:Pp-move-vandalism'] = {'vandalism', action = 'move', catonly = 'yes'}, ['Template:Pp-office'] = {'office'}, ['Template:Pp-office-dmca'] = {'dmca'}, ['Template:Pp-pc'] = {action = 'autoreview', small = true}, ['Template:Pp-pc1'] = {action = 'autoreview', small = true}, ['Template:Pp-reset'] = {'reset'}, ['Template:Pp-semi-indef'] = {small = true}, ['Template:Pp-sock'] = {'sock'}, ['Template:Pp-template'] = {'template', small = true}, ['Template:Pp-upload'] = {action = 'upload'}, ['Template:Pp-usertalk'] = {'usertalk'}, ['Template:Pp-vandalism'] = {'vandalism'}, }, -------------------------------------------------------------------------------- -- -- MESSAGES -- -------------------------------------------------------------------------------- msg = { -------------------------------------------------------------------------------- -- Intro blurb and intro fragment -------------------------------------------------------------------------------- -- These messages specify what is produced by the ${INTROBLURB} and -- ${INTROFRAGMENT} parameters. If the protection is temporary they use the -- intro-blurb-expiry or intro-fragment-expiry, and if not they use -- intro-blurb-noexpiry or intro-fragment-noexpiry. -- It is possible to use banner parameters in these messages. ['intro-blurb-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY}.', ['intro-blurb-noexpiry'] = '${PROTECTIONBLURB}.', ['intro-fragment-expiry'] = '${PROTECTIONBLURB} until ${EXPIRY},', ['intro-fragment-noexpiry'] = '${PROTECTIONBLURB}', -------------------------------------------------------------------------------- -- Tooltip blurb -------------------------------------------------------------------------------- -- These messages specify what is produced by the ${TOOLTIPBLURB} parameter. -- If the protection is temporary the tooltip-blurb-expiry message is used, and -- if not the tooltip-blurb-noexpiry message is used. -- It is possible to use banner parameters in these messages. ['tooltip-blurb-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY}.', ['tooltip-blurb-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}.', ['tooltip-fragment-expiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL} until ${EXPIRY},', ['tooltip-fragment-noexpiry'] = 'This ${PAGETYPE} is ${PROTECTIONLEVEL}', -------------------------------------------------------------------------------- -- Special explanation blurb -------------------------------------------------------------------------------- -- An explanation blurb for pages that cannot be unprotected, e.g. for pages -- in the MediaWiki namespace. -- It is possible to use banner parameters in this message. ['explanation-blurb-nounprotect'] = 'See the [[Wikipedia:Protection policy|' .. 'protection policy]] and ${PROTECTIONLOG} for more details.' .. ' Please discuss any changes on the ${TALKPAGE}; you' .. ' may ${EDITREQUEST} to ask an' .. ' [[Wikipedia:Administrators|administrator]] to make an edit if it' .. ' is [[Help:Minor edit#When to mark an edit as a minor edit' .. '|uncontroversial]] or supported by [[Wikipedia:Consensus' .. '|consensus]].', -------------------------------------------------------------------------------- -- Protection log display values -------------------------------------------------------------------------------- -- These messages determine the display values for the protection log link -- or the pending changes log link produced by the ${PROTECTIONLOG} parameter. -- It is possible to use banner parameters in these messages. ['protection-log-display'] = 'protection log', ['pc-log-display'] = 'pending changes log', -------------------------------------------------------------------------------- -- Current version display values -------------------------------------------------------------------------------- -- These messages determine the display values for the page history link -- or the move log link produced by the ${CURRENTVERSION} parameter. -- It is possible to use banner parameters in these messages. ['current-version-move-display'] = 'current title', ['current-version-edit-display'] = 'current version', -------------------------------------------------------------------------------- -- Talk page -------------------------------------------------------------------------------- -- This message determines the display value of the talk page link produced -- with the ${TALKPAGE} parameter. -- It is possible to use banner parameters in this message. ['talk-page-link-display'] = 'talk page', -------------------------------------------------------------------------------- -- Edit requests -------------------------------------------------------------------------------- -- This message determines the display value of the edit request link produced -- with the ${EDITREQUEST} parameter. -- It is possible to use banner parameters in this message. ['edit-request-display'] = 'submit an edit request', -------------------------------------------------------------------------------- -- Expiry date format -------------------------------------------------------------------------------- -- This is the format for the blurb expiry date. It should be valid input for -- the first parameter of the #time parser function. ['expiry-date-format'] = 'F j, Y "at" H:i e', -------------------------------------------------------------------------------- -- Tracking categories -------------------------------------------------------------------------------- -- These messages determine which tracking categories the module outputs. ['tracking-category-incorrect'] = 'Wikipedia pages with incorrect protection templates', ['tracking-category-template'] = 'Wikipedia template-protected pages other than templates and modules', -------------------------------------------------------------------------------- -- Images -------------------------------------------------------------------------------- -- These are images that are not defined by their protection action and protection level. ['image-filename-indef'] = 'Full-protection-shackle.svg', ['image-filename-default'] = 'Transparent.gif', -------------------------------------------------------------------------------- -- End messages -------------------------------------------------------------------------------- } -------------------------------------------------------------------------------- -- End configuration -------------------------------------------------------------------------------- } g4f6dkimoikv05ulbf832zu7omkf2fc User:Sam Sailor/test.js 2 98186 740020 739972 2026-05-01T15:32:10Z Sam Sailor 26820 Test 740020 javascript text/javascript //<nowiki> /* global ComradeLib, mw, $ */ (async function() { try { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript'); } catch (e) { console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e); } window.ComradeOptions = Object.assign({ version: "0.5.0", excludedTemplates: [], nudgeTimeout: 10000, debugMode: false }, window.ComradeOptions || {}); await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; should it be?")); const snippet = '* {{anbl|' + currentTitle + '}}'; const $snippetWrapper = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'gap': '10px', 'margin-top': '4px' }); const $instructionSpan = $('<span>').append('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $snippetWrapper.append($instructionSpan, $copyBtn); $content.append($snippetWrapper); if (!hasShortDesc) { $content.append($('<span>').css({ 'color': '#d33', 'font-weight': 'bold', 'margin-top': '5px' }).text('⚠️ Missing short description: Please add a concise one before listing.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); try { const [pageData, backlinkData] = await Promise.all([ api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }), api.get({ action: 'query', list: 'backlinks', blfilterredir: 'nonredirects', bllimit: 50, blnamespace: 0, bltitle: mw.config.get("wgPageName"), formatversion: 2 }) ]); const page = pageData.query.pages[0]; if (!page || page.missing || !page.revisions) return; const wikitext = page.revisions[0].content; // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ascii) { let label = "ASCII-only"; let category = "R from ASCII-only"; if (/[æÆœŒijIJßẞ]/.test(currentTitle)) { label = "Ligature"; category = "R to ligature"; } else if (currentTitle.normalize("NFD").match(/[\u0300-\u036f]/)) { label = "Diacritic"; category = "R to diacritic"; } await ComradeLib.checkAndRenderRedirect(norms.ascii, currentTitle, category, label); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext); const backLinkCount = backlinkData.query.backlinks.length; if (isOrphanTagged && backLinkCount >= 1) { const $container = $('<div>').addClass('comrade-container comrade-status'); const $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const isRedirect = !!mw.config.get("wgIsRedirect"); const userExclusions = window.comradeQualityExclusions || []; const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions]; const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'); const exclusionRegex = new RegExp(exclusionRegexString, 'i'); if (isRedirect || exclusionRegex.test(wikitext)) { console.log("Comrade check skipped: Page is a redirect or excluded via options."); return; } ComradeLib.checkQualityConsistency(); } catch (err) { console.error("Comrade error:", err); } } init(); })(); (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); //</nowiki> tqmdw6mlum43zfigcx55bd2xyo5u9tm 740048 740020 2026-05-01T17:29:04Z Sam Sailor 26820 Test 740048 javascript text/javascript //<nowiki> /* global ComradeLib, mw, $ */ (async function() { try { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript'); } catch (e) { console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e); } window.ComradeOptions = Object.assign({ version: "0.5.0", excludedTemplates: [], nudgeTimeout: 10000, debugMode: false }, window.ComradeOptions || {}); await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; should it be?")); const snippet = '* {{anbl|' + currentTitle + '}}'; const $snippetWrapper = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'gap': '10px', 'margin-top': '4px' }); const $instructionSpan = $('<span>').append('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $snippetWrapper.append($instructionSpan, $copyBtn); $content.append($snippetWrapper); if (!hasShortDesc) { $content.append($('<span>').css({ 'color': '#d33', 'font-weight': 'bold', 'margin-top': '5px' }).text('⚠️ Missing short description: Please add a concise one before listing.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); try { const [pageData, backlinkData] = await Promise.all([ api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }), api.get({ action: 'query', list: 'backlinks', blfilterredir: 'nonredirects', bllimit: 50, blnamespace: 0, bltitle: mw.config.get("wgPageName"), formatversion: 2 }) ]); const page = pageData.query.pages[0]; if (!page || page.missing || !page.revisions) return; const wikitext = page.revisions[0].content; ComradeLib.checkLeadCapitalization(currentTitle); // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ascii) { let label = "ASCII-only"; let category = "R from ASCII-only"; if (/[æÆœŒijIJßẞ]/.test(currentTitle)) { label = "Ligature"; category = "R to ligature"; } else if (currentTitle.normalize("NFD").match(/[\u0300-\u036f]/)) { label = "Diacritic"; category = "R to diacritic"; } await ComradeLib.checkAndRenderRedirect(norms.ascii, currentTitle, category, label); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic const isOrphanTagged = /\{\{\s*(Orphan|Do-attempt|Lonely|Orp|Orphaned article)/i.test(wikitext) || /\| *orphan *=/i.test(wikitext); const backLinkCount = backlinkData.query.backlinks.length; if (isOrphanTagged && backLinkCount >= 1) { const $container = $('<div>').addClass('comrade-container comrade-status'); const $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const isRedirect = !!mw.config.get("wgIsRedirect"); const userExclusions = window.comradeQualityExclusions || []; const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions]; const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'); const exclusionRegex = new RegExp(exclusionRegexString, 'i'); if (isRedirect || exclusionRegex.test(wikitext)) { console.log("Comrade check skipped: Page is a redirect or excluded via options."); return; } ComradeLib.checkQualityConsistency(); } catch (err) { console.error("Comrade error:", err); } } init(); })(); (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); //</nowiki> q3dm2pt8ukvxilg7hfok006868oo417 User:MrJaroslavik/userinfo.js 2 108875 739975 739973 2026-05-01T12:27:54Z MrJaroslavik 44012 + 739975 javascript text/javascript /** * @Description: Modified Userstatus.js with Former Groups (Local & Global) * Original by Steef389, Perhelion. **/ (function ($, mw) { 'use strict'; var project = window.project || mw.config.get('wgDBname'); var msg, i18n = { en: { noReason: 'reason removed', blockCmt: 'no block comment', never: 'never', not: ' not ', block: 'blocked', and: '$1 and$2', count: 'Count', noedit: 'None (only deleted contributions?)', nodb: '‹not in database›', noGrp: 'no extended group', dates: ['years', 'months', 'days', 'hours', 'minutes', 'seconds'], date: ['year', 'month', 'day', 'hour', 'minute', 'second'], nosec: 'less than one ', curtimeDiff: ' Locale time difference to server time in s: ', thxGn: 'given', thxRd: 'received', nova: 'new', ago: '$1 ago', blocklog: 'Block log', contrib: 'Edits: ', usub: 'Subpages⬇', thxGvng: 'Thanksgivings: ', reviews: 'Active reviews: ', regdate: 'Registration: ', laedit: 'Last edited: ', lala: 'Last log activity', fiedit: 'First edit: ', blocks: 'Block-status: ', loGrp: 'Local user-groups: ', glGrp: 'Global user-groups: ', formerLoGrp: 'Former local user-groups: ', formerGlGrp: 'Former global user-groups: ', blockEnd: 'Block-end: ', blocker: 'Blocker: ', blockReason: 'Block-reason: ', laact: 'Last action: ', laadmin: 'Last admin action: ', lacu: 'Last CU action: ', laos: 'Last OS action: ', today: 'today', yesterday: 'yesterday' }, cs: { noReason: 'důvod odstraněn', blockCmt: 'bez komentáře', never: 'nikdy', not: ' ne ', block: 'zablokován', and: '$1 a$2', count: 'Počet', noedit: 'Žádné (jen smazané?)', nodb: '‹není v databázi›', noGrp: 'žádná rozšířená skupina', dates: ['let', 'měsíců', 'dní', 'hodin', 'minut', 'sekund'], date: ['rok', 'měsíc', 'den', 'hodina', 'minuta', 'sekunda'], nosec: 'méně než ', curtimeDiff: ' Rozdíl lokálního času k serveru v s: ', thxGn: 'poděkoval', thxRd: 'obdržel poděkování', nova: 'nové', ago: 'před $1', days: 'dní', blocklog: 'Kniha zablokování', contrib: 'Editace: ', usub: 'Podstránky⬇', thxGvng: 'Poděkování: ', reviews: 'Aktivní prověření: ', regdate: 'Registrace: ', laedit: 'Poslední editace: ', lala: 'Poslední aktivita v protokolech', fiedit: 'První editace: ', blocks: 'Stav bloku: ', loGrp: 'Lokální skupiny: ', glGrp: 'Globální skupiny: ', formerLoGrp: 'Bývalé lokální skupiny: ', formerGlGrp: 'Bývalé globální skupiny: ', blockEnd: 'Konec bloku: ', blocker: 'Zablokoval: ', blockReason: 'Důvod bloku: ', laact: 'Poslední akce: ', laadmin: 'Poslední admin akce: ', lacu: 'Poslední CU akce: ', laos: 'Poslední OS akce: ', today: 'dnes', yesterday: 'včera' } }; var data; var us = mw.libs.userstatus = { name: 'Userstatus', version: 1.80, // Bumping version for former groups update lastEditSeconds: false, styleMissingData: 'color:#999;font-size:90%;', styleLoading: 'font-style:italic;', styleBlocked: 'color:#c00', styleNotBlocked: 'color:#080', styleFormer: 'margin-left:15px; color:#72777d; font-style:italic; font-size:0.9em;', viewPatrolNumber: false, lang: mw.config.get('wgUserLanguage'), user: mw.config.get('wgTitle'), cookie: [], thanks: 0, patrols: 0, actions: [], dbVersion: 0, implicitGroups: ['*', 'user', 'autoconfirmed'], getLocalNames: function (groups, specialpage) { if (groups) { var arr = []; specialpage = specialpage || 'ListGroupRights'; for (var i = 0; i < groups.length; ++i) { var g = groups[i]; var n = us.groupNames[g]; if (n) arr.push('<a href="/wiki/Special:' + specialpage + '#' + g + '">' + n + '</a>'); else arr.push(g); } groups = arr.join(', ').replace(/(.*),([^,]*)$/, msg.and); } return groups; }, writeGroups: function (groups) { groups = us.getLocalNames(groups); if (!groups) { groups = $('<span>', { style: us.styleMissingData + us.styleLoading, text: msg.noGrp }); } us.loading_groups.replaceWith(groups); // After local groups are written, fetch former local groups us.fetchFormerRights(false); }, getGroupNames: function (aw) { if (!aw || !aw.query) return; aw = aw.query.allmessages; var al = aw.length; var groups = {}; while (al--) { var an = aw[al]; var name = an.name; if (/^group-/.test(name)) { name = RegExp.rightContext || name.substring(6); groups[name] = an['*']; } } us.groupNames = groups; us.init(); }, ajaxRequest: function (params, onSuccess, trial) { var url = us.api; if (!(params instanceof Object)) { url += params + '&maxage=2419200&smaxage=2419200'; params = {}; } else { params.maxlag = 3; params.maxage = 2419200; params.smaxage = 2419200; if (trial) params.maxlag *= trial; } var api = $.getJSON(url, params, onSuccess) .fail(function (jqXHR, status) { var note = 'Timeout fail, maybe try again!'; if (trial) { if (!status.textStatus || (status.textStatus !== 'timeout' && jqXHR !== 'maxlag')) note = $('<span>Fehler bitte <a href="' + mw.util.getUrl('User_talk:Perhelion') + '">Perhelion</a> melden.</span>'); mw.notify(note, { title: us.name + ':', type: 'error' }); } else if (status.textStatus === 'timeout' || jqXHR === 'maxlag') { api.abort(); return us.ajaxRequest(params, onSuccess, 2); } }); }, // --- NEW FORMER RIGHTS LOGIC --- fetchFormerRights: function(isGlobal) { var apiTarget = isGlobal ? 'https://meta.wikimedia.org/w/api.php' : mw.util.wikiScript('api'); var logType = isGlobal ? 'gblrights' : 'rights'; var userTarget = 'User:' + us.user; var containerId = isGlobal ? 'us_former_global_groups' : 'us_former_local_groups'; // Globální override pro smazané skupiny (skupina: datum smazání) var globalOverrides = { 'oathauth-tester': '2026-01-12T10:58:00Z' }; var params = { action: 'query', list: 'logevents', letype: logType, letitle: userTarget, lelimit: 500, format: 'json', origin: isGlobal ? '*' : undefined }; $.getJSON(apiTarget, params, function(res) { if(!res || !res.query || !res.query.logevents) return; var logs = res.query.logevents; var former = {}; var currentGroups = isGlobal ? (data.glGrp || []) : (data.groups || []); logs.forEach(function(log) { var p = log.params; if (!p) return; var date = log.timestamp; var oldRaw = p.oldgroups || p.oldGroups || p.remove || p["0"] || ""; var newRaw = p.newgroups || p.newGroups || p.add || p["1"] || ""; var parse = function(val) { if (Array.isArray(val)) return val; if (typeof val === 'string') { if (val === "(none)" || val === "") return []; return val.split(/[,|]\s*/); } return []; }; var oldG = parse(oldRaw); var newG = parse(newRaw); // 1. Nejdřív kontrolujeme udělení (v logu jdeme od nejnovějších k nejstarším) newG.forEach(function(g) { g = g.trim(); if (us.implicitGroups.indexOf(g) !== -1 || !g) return; // Speciální případ: Skupina byla udělena, uživatel ji nemá a víme, že globálně zanikla if (currentGroups.indexOf(g) === -1 && globalOverrides[g] && !former[g]) { former[g] = { to: globalOverrides[g], from: date }; } // Standardní případ: Už známe konec z oldG a teď jsme našli, kdy to začalo else if (former[g] && !former[g].from) { former[g].from = date; } }); // 2. Poté kontrolujeme odebrání (standardní cesta) oldG.forEach(function(g) { g = g.trim(); if (us.implicitGroups.indexOf(g) !== -1 || !g) return; // Pokud skupinu nemá a ještě jsme o ní v tomto průchodu neslyšeli if (currentGroups.indexOf(g) === -1 && !former[g]) { former[g] = { to: date, from: null }; } }); }); var entries = []; Object.keys(former).sort().forEach(function(g) { var item = former[g]; // Aplikace globálního overridu, pokud chybí datum odebrání a skupina je v seznamu smazaných if (!item.to && globalOverrides[g]) { item.to = globalOverrides[g]; } var dateTo = new Date(item.to); var dateFrom = item.from ? new Date(item.from) : null; if(dateFrom && item.to) { var diffText = us.getDateDiff(dateTo, dateFrom).replace(msg.ago.replace('$1', ''), '').trim(); entries.push(g + ' (' + dateFrom.toISOString().split('T')[0] + ' – ' + dateTo.toISOString().split('T')[0] + ', ' + diffText + ')'); } else if (item.to) { entries.push(g + ' (do ' + dateTo.toISOString().split('T')[0] + ')'); } }); if(entries.length > 0) { $('#' + containerId).show().find('.us_former_content').text(entries.join(', ')); } }); }, getThanks: function (type, lecontinue, currentCount) { var params = '&list=logevents&continue=-%7C%7C&leprop=&letype=thanks&ledir=newer&lelimit=max'; if (lecontinue) params += '&lecontinue=' + lecontinue; if (type === 'gn') params += '&leuser=' + us.user; else params += '&letitle=User:' + us.user; // Používáme přímý ajax místo zařazení do fronty, aby nedocházelo k zamrznutí us.ajaxRequest(params, function(uq) { us.writeThanks(uq, type, currentCount || 0); }); }, writeThanks: function (uq, type, currentCount) { if (!uq.query || !uq.query.logevents) return mw.log.warn(uq); currentCount += uq.query.logevents.length; // Stránkování výsledků, pokud má uživatel více poděkování než limit if (uq.continue && uq.continue.lecontinue) { return us.getThanks(type, uq.continue.lecontinue, currentCount); } var e = type === 'gn' ? $('#us_thx_gn_val') : $('#us_thx_rd_val'); var userParam = type === 'gn' ? '&user=' + us.user : '&page=User:' + us.user; // Po sečtení přepíšeme načítací tečky výsledkem e.text(currentCount).css({'font-style': 'normal', 'cursor': 'pointer'}); e[0].target = '_blank'; e[0].href = '/w/index.php?title=Special%3ALog&type=thanks' + userParam + '&year=&month=-1&tagfilter=&hide_thanks_log=0'; }, getUploads: function (e) { var params = '&list=logevents&letype=upload&ledir=newer&lelimit=max&leuser=' + us.user; function _start(u) { var text = u.text(); u.prop('title', text); u.text(''); u.injectSpinner('upload' + text); u.data('upl', 0); u.data('del', 0); } if (e instanceof Object) { if (e.target) { if (us.e) return mw.notify('Synchron requests are not yet supported.', { title: us.name + ':', type: 'error' }); e = e.target; us.e = $(e); if (us.e.text() !== msg.nova) { us.e2 = us.e.nextAll('a').eq(0); if (us.e2.text() === msg.nova) _start(us.e2); else delete us.e2; } _start(us.e); } else if (e.lecontinue) { params += '&lecontinue=' + e.lecontinue; } params += '&leprop=ids'; if (us.e.prop('title') === msg.nova) params += '&leaction=upload/upload'; else if (us.e2) params += '|type'; } us.actions.push({ params: params, func: us.writeUploads }); us.doRequest(); }, writeUploads: function (uq) { if (!uq.query || !uq.query.logevents || !us.e) return mw.log(uq, us.e); function _insert(e) { $.removeSpinner('upload' + e.prop('title')); e.text(''); e.off('click'); e.append(e.data('del'), $('<span>', { style: us.styleMissingData + us.styleLoading, text: ' (+' + (e.data('upl') - e.data('del')) + ' del.)' })); e[0].target = '_blank'; e[0].href = '/w/index.php?title=Special:Log&type=upload&user=' + us.user + '&subtype=' + ((e[0].title === msg.nova) ? 'upload' : ''); } var e = us.e; var e2 = us.e2; var aw = uq.query.logevents; var alen = aw.length, i = 0; var del = e.data('del'); if (e2) { var upl = e2.data('upl'); var del2 = e2.data('del'); for (i; i < alen; ++i) { var a = aw[i]; var c = a.pageid ? 1 : 0; if (a.action === 'upload') { upl++; del2 += c; } del += c; } e2.data('upl', upl); e2.data('del', del2); } else { for (i; i < alen; ++i) if (aw[i].pageid) del++; } e.data('upl', e.data('upl') + alen); e.data('del', del); if (uq.continue) return us.getUploads(uq.continue); _insert(e); if (e2) { _insert(e2); delete us.e2; } delete us.e; }, writeCommonInfo: function (uq) { var aw = uq.query; if (!aw || !aw.users[0] || Object.prototype.hasOwnProperty.call(aw, 'missing') || Object.prototype.hasOwnProperty.call(aw.users[0], 'invalid')) return us.statusBox.remove(); aw = aw.users[0]; var edits = aw.editcount, groups = data.groups || aw.groups, blocked = data.blockreason || aw.blockexpiry, gender = data.gender || aw.gender, uploads = $('<a>', { title: msg.count, text: msg.nova, style: us.styleLoading, click: us.getUploads }); if (aw.registration) data.registration = aw.registration; us.now = us.getDateFromTimestamp(uq.curtimestamp || us.now || mw.now()); us.user = us.user.replace(/ /g, '_'); us.first = 1; // POMOCNÁ FUNKCE PRO VÝPIS AKCÍ Z LOGU (S ODKAZEM A DNES/VČERA) var renderActionRow = function(valSelector, liSelector, timestamp, url) { var d = new Date(timestamp); // Logika pro dnes/včera var today = new Date(us.now); var yesterday = new Date(us.now); yesterday.setDate(yesterday.getDate() - 1); var diffText = us.getDateDiff(us.now, d); if (d.toDateString() === today.toDateString()) diffText = msg.today || 'today'; else if (d.toDateString() === yesterday.toDateString()) diffText = msg.yesterday || 'yesterday'; var formatted = us.formatDate(d); var diffNode = url ? $('<a>', { href: url, text: diffText }) : diffText; $(valSelector).empty().append(diffNode, ' ', $('<span>', { style: us.styleMissingData, text: '(' + formatted + ')' })); $(liSelector).show(); }; // A. OBECNÉ A ADMIN AKCE var adminTypes = ['block', 'delete', 'protect', 'rights', 'merge', 'abusefilter', 'renameuser', 'gblblock']; var ignoreActions = ['autopromote', 'autopatrol', 'auto', 'hit']; // Zde je chybějící definice! us.actions.push({ params: '&list=logevents&leuser=' + us.user + '&lelimit=50', func: function(res) { if(!res.query || !res.query.logevents) return; var logs = res.query.logevents; // 1. Poslední manuální akce (IGNORUJEME systémové věci z pole ignoreActions) var realAction = logs.find(function(l) { return ignoreActions.indexOf(l.action) === -1; }); if(realAction) { // Odkaz na logy s filtry vyjmutí (thanks, patrol, tag, newusers) a dynamickým jménem var actionUrl = mw.util.getUrl('Special:Log') + '?type=&user=' + encodeURIComponent(us.user) + '&page=&excludetempacct=1&wpdate=&tagfilter=&wpfilters%5B%5D=thanks&wpfilters%5B%5D=patrol&wpfilters%5B%5D=tag&wpfilters%5B%5D=newusers&wpFormIdentifier=logeventslist'; renderActionRow('#us_last_action_val', '#us_last_action_li', realAction.timestamp, actionUrl); } // 2. Najdi první admin akci (POUZE pokud je uživatel aktuálně admin nebo byrokrat) var isAdminOrCrat = groups && ($.inArray('sysop', groups) !== -1 || $.inArray('bureaucrat', groups) !== -1); if (isAdminOrCrat) { var adm = logs.find(function(l) { return adminTypes.indexOf(l.type) !== -1 && ignoreActions.indexOf(l.action) === -1; }); if(adm) { renderActionRow('#us_last_admin_val', '#us_last_admin_li', adm.timestamp, mw.util.getUrl('Special:Log', { user: us.user })); } } } }); // B. CU AKCE if (groups && $.inArray('checkuser', groups) !== -1) { us.actions.push({ params: '&list=checkuserlog&culuser=' + us.user + '&cullimit=1', func: function(res) { if(res.query && res.query.checkuserlog && res.query.checkuserlog.entries && res.query.checkuserlog.entries[0]) { var cuUrl = mw.util.getUrl('Special:CheckUserLog', { cuSearch: '', cuInitiator: us.user }); renderActionRow('#us_last_cu_val', '#us_last_cu_li', res.query.checkuserlog.entries[0].timestamp, cuUrl); } } }); } // C. OS AKCE (Ošetřen oversight i suppress) if (groups && ($.inArray('oversight', groups) !== -1 || $.inArray('suppress', groups) !== -1)) { us.actions.push({ params: '&list=logevents&leuser=' + us.user + '&lelimit=1&letype=suppress', func: function(res) { if(res.query && res.query.logevents[0]) { var osUrl = mw.util.getUrl('Special:Log', { type: 'suppress', user: us.user }); renderActionRow('#us_last_os_val', '#us_last_os_li', res.query.logevents[0].timestamp, osUrl); } } }); } if (edits) { if (data.editcount && data.lastedit && data.editcount === edits) { uq.query.usercontribs = [{ timestamp: data.lastedit }]; us.writeLastEdit(uq); } else { data.editcount = edits; us.loading_last_edit.css('display', 'block'); us.actions.push({ params: '&list=usercontribs&ucuser=' + us.user + '&uclimit=1&uccontinue=' + (new Date(us.now).toISOString().replace(/[^\d]/g, '').slice(0, 14) + '|2'), func: us.writeLastEdit }); } $('#t-contributions').remove(); } else { us.writeLastEdit({}); } if (groups) { edits = $('<a>', { title: 'Supercount', href: '//tools.wmflabs.org/supercount/index.php?user=' + us.user + '&project=' + location.hostname, text: edits }); if ($.inArray('bot', groups) === -1) { us.review = $.inArray('editor', groups) !== -1; if (us.review) { if (typeof data.reviews === 'number') us.writePatrolCount(data.reviews); } else if (us.viewPatrolNumber) { us.us_patrolcount_loading.parent().remove(); } edits = [ edits, ' • ', $('<a>', { href: '//xtools.wmflabs.org/ec' + mw.config.get('wgServer').substr(1) + '/' + us.user, title: 'XTools', text: 'XTools', style: us.styleLoading, target: '_blank' }), ' • ', $('<a>', { href: 'https://meta.wikimedia.org/wiki/Special:CentralAuth/' + us.user, title: 'CentralAuth', text: 'CentralAuth', style: us.styleLoading, target: '_blank' }), ' • ', (project !== 'commonswiki' ? $('<a>', { href: '/wiki/Special:ListFiles/' + us.user, text: 'Upload', title: 'Special:Listfiles', style: us.styleLoading }) : 'Upload'), (us.lang === 'de' ? '-' : ' ') + msg.count + ': ', uploads.clone(1).text('total'), ' / ', uploads ]; if (!$('#t-subpages').length) { edits.push($('<a>', { href: mw.util.getUrl('Special:Prefixindex/User:' + us.user + '/'), text: ' • ' + msg.usub, style: us.styleMissingData + us.styleLoading })); } } else if (us.us_patrolcount_loading) { us.us_patrolcount_loading.parent().remove(); } us.loading_editcount.replaceWith(edits); if (!aw.implicitgroups && !data.groups) { aw.implicitgroups = ['*', 'user', 'autoconfirmed']; if (groups.length < 4) aw.implicitgroups.pop(); } data.groups = $.grep(groups, function (n) { return $.inArray(n, aw.implicitgroups) === -1; }); us.writeGroups(data.groups); $('#t-userrights').remove(); } if (!data.registration) { us.actions.push({ params: '&list=logevents&leprop=timestamp|type&letype=newusers&lelimit=1&ledir=newer&leend=2005-12-30T00:00:00Z&leuser=' + us.user, func: us.writeRegistration }); if (data.firstedit) { uq.query.usercontribs = [{ timestamp: data.firstedit }]; us.writeFirstEdit(uq); } else if (edits) { us.actions.push({ params: { list: 'usercontribs', ucuser: us.user, uclimit: 1, ucend: '2005-09-08T00:00:00Z', ucdir: 'newer', ucprop: 'timestamp' }, func: us.writeFirstEdit }); } } else { us.writeRegistration(data.registration); } us.loading_blocked.replaceWith($('<a>', { style: ((blocked) ? us.styleBlocked : us.styleNotBlocked), id: 'us_block_status_span', href: '/w/index.php?title=Special:Log/block&page=User:' + us.user, title: msg.blocklog, text: ((blocked) ? ' ' : msg.not) + msg.block })); if (blocked) { data.blockreason = aw.blockreason || ' '; us.ul.append([ $('<li>', { id: 'us_block_time' }).append([$('<b>').text('• ' + msg.blockEnd), $.createSpinner('us_block_time_loading')]), $('<li>', { id: 'us_block_reason' }).append([$('<b>').text('• ' + msg.blockReason), us.parseComment(aw.comment, (aw.commenthidden))]), $('<li>', { id: 'us_blocker' }).append([$('<b>').text('• ' + msg.blocker), $.createSpinner('us_blocker_loading')]) ]); us.actions.push({ params: { list: 'logevents', letitle: 'User:' + us.user, letype: 'block', lelimit: 1 }, func: us.writeBlockDetail }); } if (data.glGrp && !data.locked) { uq.query.globaluserinfo = { groups: (data.glGrp.length) ? data.glGrp : null }; us.writeGlobalGroup(uq); } else { us.actions.push({ params: '&meta=globaluserinfo&guiprop=groups&guiuser=' + us.user, func: us.writeGlobalGroup }); } if (gender) { data.gender = gender; var genderSn = (gender === 'male') ? ' \u2642' : (gender === 'female' ? ' \u2640' : ''); $('#firstHeading').append($('<span>', { id: 'ps-gender-' + gender, style: 'font-size:80%', text: genderSn })); } // Automatické načtení poděkování do fronty dotazů us.getThanks('rd'); us.getThanks('gn'); if (us.actions.length) us.doRequest(); }, createBox: function () { var statusBox = $('<div>', { style: 'border-bottom:1px solid #aaa;text-shadow:1px 1px 1px #eff;', id: 'us_box' }); if (mw.config.get('skin') === 'vector') statusBox.css('font-size', '0.8em'); var spanFrag = $.createSpinner(); us.loading_editcount = spanFrag.clone(); us.loading_registration = spanFrag.clone(); us.loading_groups = spanFrag.clone(); us.loading_blocked = spanFrag.clone(); us.us_global_group_loading = spanFrag.clone(); us.us_last_edit_loading = $.createSpinner('contribs'); us.loading_last_edit = $('<li>'); us.us_global_group = $('<li>', { style: 'display: none' }); var thxRd = $('<a>', { id: 'us_thx_rd_val', title: msg.thxRd, style: us.styleLoading, text: '...' }); var thxGn = $('<a>', { id: 'us_thx_gn_val', title: msg.thxGn, style: us.styleLoading, text: '...' }); var $userrights = $('#t-userrights a'); var $contributions = $('#t-contributions a'); if (!$userrights.length) $userrights = $('<a>', { href: '/wiki/Special:UserRights/' + us.user }); if (!$contributions.length) $contributions = $('<a>', { href: '/wiki/Special:Contributions/' + us.user }); var ul = $('<ul>', { style: 'list-style: none' }).append($('<li>', { id: 'us_editcount' }).append([$('<b>').append($contributions.text(msg.contrib)), us.loading_editcount])); us.us_log_count = $('<span>'); if (us.viewPatrolNumber) { us.us_patrolcount_loading = $('<a>', { style: us.styleLoading, click: us.getPatrolCount, text: msg.count }); us.us_log_count = $('<span>').append([$('<b>').text(msg.reviews), us.us_patrolcount_loading, ' • ']); } ul.append(us.us_log_count) .append($('<li>', { id: 'us_reg_date' }).append([$('<b>').text(msg.regdate), us.loading_registration, $('<a>', { target: '_blank', style: 'float:right;' + us.styleMissingData, href: 'https://meta.wikimedia.org/wiki/Special:UserPages/' + us.user, text: '• Global user pages' })])) .append($('<li>').append([$('<b>').append($userrights.text(msg.loGrp)), us.loading_groups])) .append($('<li>', { id: 'us_former_local_groups', style: 'display:none;' + us.styleFormer }).append([$('<b>').text(msg.formerLoGrp), $('<span>', { 'class': 'us_former_content' })])) .append(us.us_global_group.append([$('<b>').append($('<a>', { href: 'https://meta.wikimedia.org/wiki/Special:Log?type=gblrights&user=&page=User:' + us.user, text: msg.glGrp })), us.us_global_group_loading])) .append($('<li>', { id: 'us_former_global_groups', style: 'display:none;' + us.styleFormer }).append([$('<b>').text(msg.formerGlGrp), $('<span>', { 'class': 'us_former_content' })])) .append(us.loading_last_edit.append([$('<b>').text(msg.laedit), us.us_last_edit_loading, $('<a>', { target: '_blank', style: 'float:right;' + us.styleMissingData, href: 'https://meta.wikimedia.org/wiki/Special:GlobalContributions/' + us.user, text: '• Global contributions' })])) .append($('<li>', { id: 'us_last_action_li', style: 'display:none' }).append([$('<b>').text(msg.laact), $('<span>', { id: 'us_last_action_val' })])) .append($('<li>', { id: 'us_last_admin_li', style: 'display:none' }).append([$('<b>').text(msg.laadmin), $('<span>', { id: 'us_last_admin_val' })])) .append($('<li>', { id: 'us_last_cu_li', style: 'display:none' }).append([$('<b>').text(msg.lacu), $('<span>', { id: 'us_last_cu_val' })])) .append($('<li>', { id: 'us_last_os_li', style: 'display:none' }).append([$('<b>').text(msg.laos), $('<span>', { id: 'us_last_os_val' })])); statusBox.append(ul); us.ul = $('<ul>', { style: 'list-style:none' }).append($('<li>', { id: 'us_block_status' }).append([$('<b>').text(msg.blocks), us.loading_blocked, ' • ', $('<b>').text(msg.thxGvng), msg.thxRd, ': ', thxRd, ' / ', msg.thxGn, ': ', thxGn, $('<a>', { style: 'float:right;' + us.styleMissingData, title: 'Purge data', text: 'purge', click: us.removeDataStore })])); statusBox.append(us.ul); $('#firstHeading').after(statusBox); us.statusBox = statusBox; }, removeDataStore: function (e) { var key = project + us.user, db = window.indexedDB; e.preventDefault(); if (!db || !us.dbVersion) return mw.cookie.set(us.name + us.user, null); var request = db.open(us.name, us.dbVersion + 1); request.onupgradeneeded = function () { db = this.result; if (db.objectStoreNames.contains(key)) db.deleteObjectStore(key); }; request.onsuccess = function () { db = this.result; us.dbVersion = db.version; db.close(); location.reload(); }; }, setCookie: function () { var domain = (mw.config.get('wgNoticeProject') === 'wikipedia') ? 'wikipedia.org' : ''; var name = us.name + us.user; var _saveCookie = function (k, d) { mw.cookie.set(name, JSON.stringify(d), { expires: 604800, domain: domain }); }; if (!us.actions[0] && JSON && data.editcount) { if (us.cookie.length) window.clearTimeout(us.cookie.shift()); var saveData = window.indexedDB ? function (n, JSd, v) { var key = project + us.user, db = window.indexedDB, request = db.open(us.name, v); request.onupgradeneeded = function () { db = this.result; if (!db.objectStoreNames.contains(key)) { var store = db.createObjectStore(key, { keyPath: 'name' }); store.createIndex('data', 'data', { unique: false }); } }; request.onsuccess = function () { db = this.result; us.dbVersion = db.version; if (db.objectStoreNames.contains(key)) { var store = db.transaction(key, 'readwrite').objectStore(key); store.put({ name: key, data: JSd }); } else { saveData(n, JSd, us.dbVersion + 1); } }; request.onerror = function() { _saveCookie(n, JSd); }; } : _saveCookie; us.cookie.push(setTimeout(function () { data.timestamp = new Date().valueOf(); saveData('', data); }, 500)); } }, doRequest: function () { var action = us.actions.shift(); if (action) us.ajaxRequest(action.params, action.func); if (us.actions.length) window.setTimeout(function () { us.doRequest(); }, 100); }, getDateDiff: function (now, date) { var dStr = []; if (now > date) { var years = now.getFullYear() - date.getFullYear(); var months = now.getMonth() - date.getMonth(); var days = now.getDate() - date.getDate(); if (days < 0) { months--; var prevMonth = new Date(now.getFullYear(), now.getMonth(), 0).getDate(); days += prevMonth; } if (months < 0) { years--; months += 12; } var units = [years, months, days]; var labelSingle = [msg.date[0], msg.date[1], msg.date[2]]; var labelPlural = [msg.dates[0], msg.dates[1], msg.dates[2]]; for (var i = 0; i < 3; ++i) { if (units[i] > 0) { dStr.push(units[i] + ' ' + ((units[i] > 1) ? labelPlural[i] : labelSingle[i])); } } } var mainDiff = (dStr.length) ? dStr.join(', ').replace(/(.*),([^,]*)/, msg.and).replace(/(.*)/, msg.ago) : msg.nosec + msg.date[2]; return mainDiff; }, getDateFromTimestamp: function (t) { if (!t) return false; t = new Date(t); return isNaN(t.valueOf()) ? false : t; }, writeRegistration: function (aw) { if (aw) { if (aw instanceof Object) { if (aw.query && aw.query.logevents.length) aw = aw.query.logevents[0].timestamp; } if (typeof aw === 'string') { us.loading_registration.replaceWith(us.formatDate(aw) + ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.getDateDiff(us.now, us.getDateFromTimestamp(aw)) + ')' })); data.registration = aw; return us.setCookie(); } } us.loading_registration.replaceWith($('<span>', { style: us.styleMissingData + us.styleLoading, text: msg.nodb })); }, writeFirstEdit: function (aw) { if (!aw.query || !aw.query.usercontribs.length) return; var date = data.firstedit = aw.query.usercontribs[0].timestamp; $('#us_reg_date').append($('<li>').append([$('<b>', { text: '• ' + msg.fiedit }), us.formatDate(date) + ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.getDateDiff(us.now, us.getDateFromTimestamp(date)) + ')' })])); }, writeLastEdit: function (aw) { if (!aw.query || !aw.query.usercontribs || !aw.query.usercontribs.length) return us.us_last_edit_loading.replaceWith($('<span>', { style: us.styleMissingData, text: msg.noedit })); var date = data.lastedit = aw.query.usercontribs[0].timestamp; // Logika pro dnes/včera var d = new Date(date); var today = new Date(us.now); var yesterday = new Date(us.now); yesterday.setDate(yesterday.getDate() - 1); var diffText = us.getDateDiff(us.now, d); if (d.toDateString() === today.toDateString()) diffText = msg.today || 'today'; else if (d.toDateString() === yesterday.toDateString()) diffText = msg.yesterday || 'yesterday'; var diffNode = $('<a>', { href: mw.util.getUrl('Special:Contributions/' + us.user), text: diffText }); us.us_last_edit_loading.replaceWith(diffNode, ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.formatDate(date) + ')' })); data.timediff = mw.now() - us.now; $('#us_editcount').append($('<span>', { style: 'float:right;' + us.styleMissingData, text: msg.curtimeDiff }).append($('<b>', { text: Math.round(data.timediff / 1000) }))); us.setCookie(); }, writeGlobalGroup: function (aw) { aw = aw.query.globaluserinfo; var groups = data.glGrp = aw.groups; // Pokud lokální registrace chybí, použijeme tu z globálního infa if (!data.registration && aw.registration) { us.writeRegistration(aw.registration); } if (groups && groups.length) { us.us_global_group_loading.replaceWith(us.getLocalNames(groups, 'GlobalGroupPermissions')); us.us_global_group.css('display', 'block'); // Fetch former global rights us.fetchFormerRights(true); } if (aw.locked !== undefined) { data.locked = 1; mw.loader.using('mediawiki.ForeignApi').done(function () { var fApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php'); fApi.get({ action: 'query', list: 'logevents', leprop: 'user|timestamp|comment', letype: 'globalauth', letitle: 'User:' + us.user, lelimit: '1' }).done(us.writeGlobalBlock); }); } us.setCookie(); }, writeGlobalBlock: function (aw) { if (!aw.query || !aw.query.logevents.length) return; aw = aw.query.logevents[0]; $('#us_block_status_span').replaceWith($('<a>', { style: us.styleBlocked, id: 'us_block_status_span', href: '//meta.wikimedia.org/w/index.php?title=Special:Log/&type=globalauth&user=&page=User%3A' + us.user + '%40global', text: 'locked (global)' }).append($('<span>', { style: us.styleMissingData, text: ' – ' + us.formatDate(aw.timestamp) }))); $('#us_block_status').parent().append([$('<li>').append([$('<b>').text('• ' + msg.blockReason), us.parseComment(aw.comment)]), $('<li>').append([$('<b>').text('• ' + msg.blocker), $('<a>', { 'class': 'mw-userlink', 'title': 'User:' + aw.user, 'href': mw.util.getUrl('User:' + aw.user), 'text': aw.user })])]); }, writeBlockDetail: function (aw) { var duration = 'infinite', expiry = ''; if (aw.query && aw.query.logevents.length) { aw = aw.query.logevents[0]; if (aw && aw.params) { duration = aw.params.duration; expiry = /in(de)?finite/.test(duration) ? msg.never : us.formatDate(aw.params.expiry); } $('#us_block_status_span').text(msg.block + ' (' + duration + ')').append($('<span>', { style: us.styleMissingData, text: ' – ' + us.formatDate(aw.timestamp) })); if (expiry) $('#mw-spinner-us_block_time_loading').replaceWith(expiry); else $('#us_block_time').remove(); $('#mw-spinner-us_block_reason_loading').replaceWith(us.parseComment(aw.comment, aw.commenthidden)); if (aw.user) $('#mw-spinner-us_blocker_loading').replaceWith($('<a>', { 'class': 'mw-userlink', 'title': 'User:' + aw.user, 'href': mw.util.getUrl('User:' + aw.user), 'text': aw.user })); else $('#mw-spinner-us_blocker_loading').replaceWith(msg.nodb); } }, writePatrolCount: function (aw) { if (aw instanceof Object) { if (!aw.query || !aw.query.logevents) return; us.patrols += aw.query.logevents.length; if (aw.continue) return us.getPatrolCount(aw.continue); $.removeSpinner('pat'); aw = us.patrols; } us.us_patrolcount_loading.replaceWith($('<a>', { href: '/w/index.php?title=Special:Log&type=' + (us.review ? 'review&subtype=accept' : 'patrol&subtype=patrol') + '&user=' + us.user, text: aw })); data.reviews = aw; us.setCookie(); }, getPatrolCount: function (e) { var params = '&list=logevents&leprop=&ledir=newer&lelimit=max&leuser=' + us.user + '&leaction=' + (us.review ? 'review/approve' : 'patrol/patrol'); if (e instanceof Object) { if (e.target) { $(e.target).injectSpinner('pat'); us.patrols = 0; } else if (e.lecontinue) params += '&lecontinue=' + e.lecontinue; } us.actions.push({ params: params, func: us.writePatrolCount }); us.doRequest(); }, parseComment: function (text, hidden) { var comment = $('<span>', { 'class': 'comment' }); if (typeof text === 'undefined') return comment.append(hidden ? msg.noReason : 'undefined').css('color', hidden ? '#999' : 'red'); if (!text) return comment.append(msg.blockCmt).css('color', '#999'); var intLink = /(.*?)\[\[((.*?)\|)?(.*?)\]\](.*)/, suche = text, erg; while ((erg = intLink.exec(suche)) !== null) { var link = erg[3] || erg[4]; comment.append([erg[1], $('<a>', { href: mw.util.getUrl(link), title: link }).text(erg[4])]); suche = erg[5]; } return comment.append(suche); }, formatDate: function (datum) { if (!(datum instanceof Date)) datum = new Date(datum); try { return datum.toLocaleDateString(us.lang, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); } catch (e) { return datum.toLocaleString(); } }, getStoredData: function () { var key = project + us.user, db, request; try { request = indexedDB.open(us.name); request.onsuccess = function () { db = this.result; if (db.objectStoreNames.contains(key)) { var store = db.transaction(key, 'readonly').objectStore(key); store.transaction.oncomplete = function () { us.runDataStore(); db.close(); }; store.get(key).onsuccess = function () { if (this.result) data = this.result.data; else us.getCookie(); }; } else { us.getCookie(); } }; request.onerror = function () { us.getCookie(); }; } catch (e) { us.getCookie(); } }, getCookie: function () { data = mw.cookie.get(us.name + us.user); if (data) try { data = JSON.parse(data); } catch(e) { data = null; } us.runDataStore(); }, initI18N: function (i18n) { var chain = mw.language.getFallbackLanguageChain(); for (var i = chain.length - 1; i >= 0; i--) if (chain[i] in i18n) msg = i18n[chain[i]]; }, init: function () { if (this.statusBox || (this.statusBox = $('#us_box'))[0]) this.statusBox.remove(); this.usprop = 'blockinfo|editcount|gender|groups|implicitgroups|registration'; this.self = mw.config.get('wgUserName') === this.user; this.initI18N(i18n); this.createBox(); if (!this.first) { if (mw.cookie && JSON) { if (window.indexedDB) this.getStoredData(); else this.getCookie(); } else this.run(); } else this.run(); }, runDataStore: function () { if (data) { if (typeof data === 'string') try { data = JSON.parse(data); } catch(e) { data = {}; } var q = { query: { users: [data] } }; if (data.timestamp) this.now = mw.now() - (data.timediff || 0); if (data.timestamp && Math.abs((mw.now() - data.timestamp - (data.timediff || 0)) / 1000) < 90) return this.writeCommonInfo(q); if (data.editcount) this.usprop = 'editcount'; } data = data || {}; this.run(); }, run: function () { if (this.self) { return this.writeCommonInfo({ query: { users: [{ editcount: mw.config.get('wgUserEditCount'), registration: mw.config.get('wgUserRegistration'), groups: mw.config.get('wgUserGroups'), blockreason: '' }] } }); } us.ajaxRequest({ curtimestamp: 1, list: 'users', ususers: us.user, usprop: us.usprop }, us.writeCommonInfo); } }; if ([2, 3].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && us.user.indexOf('/') === -1 && (!mw.config.get('wgArticleId') || mw.config.get('wgAction') === 'view')) { $.when(mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.language', 'mediawiki.cookie', 'jquery.spinner', 'mediawiki.ForeignApi']), $.ready).then(function () { us.api = mw.util.wikiScript('api') + '?action=query&format=json'; us.ajaxRequest('&meta=allmessages&amenableparser=1&amincludelocal=1&amprefix=group-', us.getGroupNames); if (!mw.libs.viewerInfo) mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Perhelion/viewerInfo.js&action=raw&ctype=text/javascript'); }); } }(jQuery, mediaWiki)); be2og92mkwzbasdopb1g2y6cfsjlvc0 739983 739975 2026-05-01T13:06:09Z MrJaroslavik 44012 + 739983 javascript text/javascript /** * @Description: Modified Userstatus.js with Former Groups (Local & Global) * Original by Steef389, Perhelion. **/ (function ($, mw) { 'use strict'; var project = window.project || mw.config.get('wgDBname'); var msg, i18n = { en: { noReason: 'reason removed', blockCmt: 'no block comment', never: 'never', not: ' not ', block: 'blocked', and: '$1 and$2', count: 'Count', noedit: 'None (only deleted contributions?)', nodb: '‹not in database›', noGrp: 'no extended group', dates: ['years', 'months', 'days', 'hours', 'minutes', 'seconds'], date: ['year', 'month', 'day', 'hour', 'minute', 'second'], nosec: 'less than one ', curtimeDiff: ' Locale time difference to server time in s: ', thxGn: 'given', thxRd: 'received', nova: 'new', ago: '$1 ago', blocklog: 'Block log', contrib: 'Edits: ', usub: 'Subpages⬇', thxGvng: 'Thanksgivings: ', reviews: 'Active reviews: ', regdate: 'Registration: ', laedit: 'Last edited: ', lala: 'Last log activity', fiedit: 'First edit: ', blocks: 'Block-status: ', loGrp: 'Local user-groups: ', glGrp: 'Global user-groups: ', formerLoGrp: 'Former local user-groups: ', formerGlGrp: 'Former global user-groups: ', blockEnd: 'Block-end: ', blocker: 'Blocker: ', blockReason: 'Block-reason: ', laact: 'Last action: ', laadmin: 'Last admin action: ', lacu: 'Last CU action: ', laos: 'Last OS action: ', today: 'today', yesterday: 'yesterday' }, cs: { noReason: 'důvod odstraněn', blockCmt: 'bez komentáře', never: 'nikdy', not: ' ne ', block: 'zablokován', and: '$1 a$2', count: 'Počet', noedit: 'Žádné (jen smazané?)', nodb: '‹není v databázi›', noGrp: 'žádná rozšířená skupina', dates: ['let', 'měsíců', 'dní', 'hodin', 'minut', 'sekund'], date: ['rok', 'měsíc', 'den', 'hodina', 'minuta', 'sekunda'], nosec: 'méně než ', curtimeDiff: ' Rozdíl lokálního času k serveru v s: ', thxGn: 'poděkoval', thxRd: 'obdržel poděkování', nova: 'nové', ago: 'před $1', days: 'dní', blocklog: 'Kniha zablokování', contrib: 'Editace: ', usub: 'Podstránky⬇', thxGvng: 'Poděkování: ', reviews: 'Aktivní prověření: ', regdate: 'Registrace: ', laedit: 'Poslední editace: ', lala: 'Poslední aktivita v protokolech', fiedit: 'První editace: ', blocks: 'Stav bloku: ', loGrp: 'Lokální skupiny: ', glGrp: 'Globální skupiny: ', formerLoGrp: 'Bývalé lokální skupiny: ', formerGlGrp: 'Bývalé globální skupiny: ', blockEnd: 'Konec bloku: ', blocker: 'Zablokoval: ', blockReason: 'Důvod bloku: ', laact: 'Poslední akce: ', laadmin: 'Poslední admin akce: ', lacu: 'Poslední CU akce: ', laos: 'Poslední OS akce: ', today: 'dnes', yesterday: 'včera' } }; var data; var us = mw.libs.userstatus = { name: 'Userstatus', version: 1.80, // Bumping version for former groups update lastEditSeconds: false, styleMissingData: 'color:#999;font-size:90%;', styleLoading: 'font-style:italic;', styleBlocked: 'color:#c00', styleNotBlocked: 'color:#080', styleFormer: 'margin-left:15px; color:#72777d; font-style:italic; font-size:0.9em;', viewPatrolNumber: false, lang: mw.config.get('wgUserLanguage'), user: mw.config.get('wgTitle'), cookie: [], thanks: 0, patrols: 0, actions: [], dbVersion: 0, implicitGroups: ['*', 'user', 'autoconfirmed'], getLocalNames: function (groups, specialpage) { if (groups) { var arr = []; specialpage = specialpage || 'ListGroupRights'; for (var i = 0; i < groups.length; ++i) { var g = groups[i]; var n = us.groupNames[g]; if (n) arr.push('<a href="/wiki/Special:' + specialpage + '#' + g + '">' + n + '</a>'); else arr.push(g); } groups = arr.join(', ').replace(/(.*),([^,]*)$/, msg.and); } return groups; }, writeGroups: function (groups) { groups = us.getLocalNames(groups); if (!groups) { groups = $('<span>', { style: us.styleMissingData + us.styleLoading, text: msg.noGrp }); } us.loading_groups.replaceWith(groups); // After local groups are written, fetch former local groups us.fetchFormerRights(false); }, getGroupNames: function (aw) { if (!aw || !aw.query) return; aw = aw.query.allmessages; var al = aw.length; var groups = {}; while (al--) { var an = aw[al]; var name = an.name; if (/^group-/.test(name)) { name = RegExp.rightContext || name.substring(6); groups[name] = an['*']; } } us.groupNames = groups; us.init(); }, ajaxRequest: function (params, onSuccess, trial) { var url = us.api; if (!(params instanceof Object)) { url += params + '&maxage=2419200&smaxage=2419200'; params = {}; } else { params.maxlag = 3; params.maxage = 2419200; params.smaxage = 2419200; if (trial) params.maxlag *= trial; } var api = $.getJSON(url, params, onSuccess) .fail(function (jqXHR, status) { var note = 'Timeout fail, maybe try again!'; if (trial) { if (!status.textStatus || (status.textStatus !== 'timeout' && jqXHR !== 'maxlag')) note = $('<span>Fehler bitte <a href="' + mw.util.getUrl('User_talk:Perhelion') + '">Perhelion</a> melden.</span>'); mw.notify(note, { title: us.name + ':', type: 'error' }); } else if (status.textStatus === 'timeout' || jqXHR === 'maxlag') { api.abort(); return us.ajaxRequest(params, onSuccess, 2); } }); }, // --- NEW FORMER RIGHTS LOGIC --- fetchFormerRights: function(isGlobal) { var apiTarget = isGlobal ? 'https://meta.wikimedia.org/w/api.php' : mw.util.wikiScript('api'); var logType = isGlobal ? 'gblrights' : 'rights'; var userTarget = 'User:' + us.user; var containerId = isGlobal ? 'us_former_global_groups' : 'us_former_local_groups'; // Globální override pro smazané skupiny (skupina: datum smazání) var globalOverrides = { 'oathauth-tester': '2026-01-12T10:58:00Z' }; var params = { action: 'query', list: 'logevents', letype: logType, letitle: userTarget, lelimit: 500, format: 'json', origin: isGlobal ? '*' : undefined }; $.getJSON(apiTarget, params, function(res) { if(!res || !res.query || !res.query.logevents) return; var logs = res.query.logevents; var former = {}; var currentGroups = isGlobal ? (data.glGrp || []) : (data.groups || []); logs.forEach(function(log) { var p = log.params; if (!p) return; var date = log.timestamp; var oldRaw = p.oldgroups || p.oldGroups || p.remove || p["0"] || ""; var newRaw = p.newgroups || p.newGroups || p.add || p["1"] || ""; var parse = function(val) { if (Array.isArray(val)) return val; if (typeof val === 'string') { if (val === "(none)" || val === "") return []; return val.split(/[,|]\s*/); } return []; }; var oldG = parse(oldRaw); var newG = parse(newRaw); // 1. Nejdřív kontrolujeme udělení (v logu jdeme od nejnovějších k nejstarším) newG.forEach(function(g) { g = g.trim(); if (us.implicitGroups.indexOf(g) !== -1 || !g) return; // Speciální případ: Skupina byla udělena, uživatel ji nemá a víme, že globálně zanikla if (currentGroups.indexOf(g) === -1 && globalOverrides[g] && !former[g]) { former[g] = { to: globalOverrides[g], from: date }; } // Standardní případ: Už známe konec z oldG a teď jsme našli, kdy to začalo else if (former[g] && !former[g].from) { former[g].from = date; } }); // 2. Poté kontrolujeme odebrání (standardní cesta) oldG.forEach(function(g) { g = g.trim(); if (us.implicitGroups.indexOf(g) !== -1 || !g) return; // Pokud skupinu nemá a ještě jsme o ní v tomto průchodu neslyšeli if (currentGroups.indexOf(g) === -1 && !former[g]) { former[g] = { to: date, from: null }; } }); }); var entries = []; Object.keys(former).sort().forEach(function(g) { var item = former[g]; // Aplikace globálního overridu, pokud chybí datum odebrání a skupina je v seznamu smazaných if (!item.to && globalOverrides[g]) { item.to = globalOverrides[g]; } var dateTo = new Date(item.to); var dateFrom = item.from ? new Date(item.from) : null; if(dateFrom && item.to) { var diffText = us.getDateDiff(dateTo, dateFrom).replace(msg.ago.replace('$1', ''), '').trim(); entries.push(g + ' (' + dateFrom.toISOString().split('T')[0] + ' – ' + dateTo.toISOString().split('T')[0] + ', ' + diffText + ')'); } else if (item.to) { entries.push(g + ' (do ' + dateTo.toISOString().split('T')[0] + ')'); } }); if(entries.length > 0) { $('#' + containerId).show().find('.us_former_content').text(entries.join(', ')); } }); }, getThanks: function (type, lecontinue, currentCount) { var params = '&list=logevents&continue=-%7C%7C&leprop=&letype=thanks&ledir=newer&lelimit=max'; if (lecontinue) params += '&lecontinue=' + lecontinue; if (type === 'gn') params += '&leuser=' + us.user; else params += '&letitle=User:' + us.user; // Používáme přímý ajax místo zařazení do fronty, aby nedocházelo k zamrznutí us.ajaxRequest(params, function(uq) { us.writeThanks(uq, type, currentCount || 0); }); }, writeThanks: function (uq, type, currentCount) { if (!uq.query || !uq.query.logevents) return mw.log.warn(uq); currentCount += uq.query.logevents.length; // Stránkování výsledků, pokud má uživatel více poděkování než limit if (uq.continue && uq.continue.lecontinue) { return us.getThanks(type, uq.continue.lecontinue, currentCount); } var e = type === 'gn' ? $('#us_thx_gn_val') : $('#us_thx_rd_val'); var userParam = type === 'gn' ? '&user=' + us.user : '&page=User:' + us.user; // Po sečtení přepíšeme načítací tečky výsledkem e.text(currentCount).css({'font-style': 'normal', 'cursor': 'pointer'}); e[0].target = '_blank'; e[0].href = '/w/index.php?title=Special%3ALog&type=thanks' + userParam + '&year=&month=-1&tagfilter=&hide_thanks_log=0'; }, getUploads: function (e) { var params = '&list=logevents&letype=upload&ledir=newer&lelimit=max&leuser=' + us.user; function _start(u) { var text = u.text(); u.prop('title', text); u.text(''); u.injectSpinner('upload' + text); u.data('upl', 0); u.data('del', 0); } if (e instanceof Object) { if (e.target) { if (us.e) return mw.notify('Synchron requests are not yet supported.', { title: us.name + ':', type: 'error' }); e = e.target; us.e = $(e); if (us.e.text() !== msg.nova) { us.e2 = us.e.nextAll('a').eq(0); if (us.e2.text() === msg.nova) _start(us.e2); else delete us.e2; } _start(us.e); } else if (e.lecontinue) { params += '&lecontinue=' + e.lecontinue; } params += '&leprop=ids'; if (us.e.prop('title') === msg.nova) params += '&leaction=upload/upload'; else if (us.e2) params += '|type'; } us.actions.push({ params: params, func: us.writeUploads }); us.doRequest(); }, writeUploads: function (uq) { if (!uq.query || !uq.query.logevents || !us.e) return mw.log(uq, us.e); function _insert(e) { $.removeSpinner('upload' + e.prop('title')); e.text(''); e.off('click'); e.append(e.data('del'), $('<span>', { style: us.styleMissingData + us.styleLoading, text: ' (+' + (e.data('upl') - e.data('del')) + ' del.)' })); e[0].target = '_blank'; e[0].href = '/w/index.php?title=Special:Log&type=upload&user=' + us.user + '&subtype=' + ((e[0].title === msg.nova) ? 'upload' : ''); } var e = us.e; var e2 = us.e2; var aw = uq.query.logevents; var alen = aw.length, i = 0; var del = e.data('del'); if (e2) { var upl = e2.data('upl'); var del2 = e2.data('del'); for (i; i < alen; ++i) { var a = aw[i]; var c = a.pageid ? 1 : 0; if (a.action === 'upload') { upl++; del2 += c; } del += c; } e2.data('upl', upl); e2.data('del', del2); } else { for (i; i < alen; ++i) if (aw[i].pageid) del++; } e.data('upl', e.data('upl') + alen); e.data('del', del); if (uq.continue) return us.getUploads(uq.continue); _insert(e); if (e2) { _insert(e2); delete us.e2; } delete us.e; }, writeCommonInfo: function (uq) { var aw = uq.query; if (!aw || !aw.users[0] || Object.prototype.hasOwnProperty.call(aw, 'missing') || Object.prototype.hasOwnProperty.call(aw.users[0], 'invalid')) return us.statusBox.remove(); aw = aw.users[0]; var edits = aw.editcount, groups = data.groups || aw.groups, blocked = data.blockreason || aw.blockexpiry, gender = data.gender || aw.gender, uploads = $('<a>', { title: msg.count, text: msg.nova, style: us.styleLoading, click: us.getUploads }); if (aw.registration) data.registration = aw.registration; us.now = us.getDateFromTimestamp(uq.curtimestamp || us.now || mw.now()); us.user = us.user.replace(/ /g, '_'); us.first = 1; // POMOCNÁ FUNKCE PRO VÝPIS AKCÍ Z LOGU (S ODKAZEM A DNES/VČERA) var renderActionRow = function(valSelector, liSelector, timestamp, url) { var d = new Date(timestamp); // Logika pro dnes/včera var today = new Date(us.now); var yesterday = new Date(us.now); yesterday.setDate(yesterday.getDate() - 1); var diffText = us.getDateDiff(us.now, d); if (d.toDateString() === today.toDateString()) diffText = msg.today || 'today'; else if (d.toDateString() === yesterday.toDateString()) diffText = msg.yesterday || 'yesterday'; var formatted = us.formatDate(d); var diffNode = url ? $('<a>', { href: url, text: diffText }) : diffText; $(valSelector).empty().append(diffNode, ' ', $('<span>', { style: us.styleMissingData, text: '(' + formatted + ')' })); $(liSelector).show(); }; // A. OBECNÉ A ADMIN AKCE var adminTypes = ['block', 'delete', 'protect', 'rights', 'merge', 'abusefilter', 'renameuser', 'gblblock']; var ignoreActions = ['autopromote', 'autopatrol', 'auto', 'hit']; // Zde je chybějící definice! us.actions.push({ params: '&list=logevents&leuser=' + us.user + '&lelimit=50', func: function(res) { if(!res.query || !res.query.logevents) return; var logs = res.query.logevents; // 1. Poslední manuální akce (IGNORUJEME systémové věci z pole ignoreActions) var realAction = logs.find(function(l) { return ignoreActions.indexOf(l.action) === -1; }); if(realAction) { // Odkaz na logy s filtry vyjmutí (thanks, patrol, tag, newusers) a dynamickým jménem var actionUrl = mw.util.getUrl('Special:Log') + '?type=&user=' + encodeURIComponent(us.user) + '&page=&excludetempacct=1&wpdate=&tagfilter=&wpfilters%5B%5D=thanks&wpfilters%5B%5D=patrol&wpfilters%5B%5D=tag&wpfilters%5B%5D=newusers&wpFormIdentifier=logeventslist'; renderActionRow('#us_last_action_val', '#us_last_action_li', realAction.timestamp, actionUrl); } // 2. Najdi první admin akci (POUZE pokud je uživatel aktuálně admin nebo byrokrat) var isAdminOrCrat = groups && ($.inArray('sysop', groups) !== -1 || $.inArray('bureaucrat', groups) !== -1); if (isAdminOrCrat) { var adm = logs.find(function(l) { return adminTypes.indexOf(l.type) !== -1 && ignoreActions.indexOf(l.action) === -1; }); if(adm) { renderActionRow('#us_last_admin_val', '#us_last_admin_li', adm.timestamp, mw.util.getUrl('Special:Log', { user: us.user })); } } } }); // B. CU AKCE if (groups && $.inArray('checkuser', groups) !== -1) { us.actions.push({ params: '&list=checkuserlog&culuser=' + us.user + '&cullimit=1', func: function(res) { if(res.query && res.query.checkuserlog && res.query.checkuserlog.entries && res.query.checkuserlog.entries[0]) { var cuUrl = mw.util.getUrl('Special:CheckUserLog', { cuSearch: '', cuInitiator: us.user }); renderActionRow('#us_last_cu_val', '#us_last_cu_li', res.query.checkuserlog.entries[0].timestamp, cuUrl); } } }); } // C. OS AKCE (Ošetřen oversight i suppress) if (groups && ($.inArray('oversight', groups) !== -1 || $.inArray('suppress', groups) !== -1)) { us.actions.push({ params: '&list=logevents&leuser=' + us.user + '&lelimit=1&letype=suppress', func: function(res) { if(res.query && res.query.logevents[0]) { var osUrl = mw.util.getUrl('Special:Log', { type: 'suppress', user: us.user }); renderActionRow('#us_last_os_val', '#us_last_os_li', res.query.logevents[0].timestamp, osUrl); } } }); } if (edits) { if (data.editcount && data.lastedit && data.editcount === edits) { uq.query.usercontribs = [{ timestamp: data.lastedit }]; us.writeLastEdit(uq); } else { data.editcount = edits; us.loading_last_edit.css('display', 'block'); us.actions.push({ params: '&list=usercontribs&ucuser=' + us.user + '&uclimit=1&uccontinue=' + (new Date(us.now).toISOString().replace(/[^\d]/g, '').slice(0, 14) + '|2'), func: us.writeLastEdit }); } $('#t-contributions').remove(); } else { us.writeLastEdit({}); } if (groups) { edits = $('<a>', { title: 'Supercount', href: '//tools.wmflabs.org/supercount/index.php?user=' + us.user + '&project=' + location.hostname, text: edits }); if ($.inArray('bot', groups) === -1) { us.review = $.inArray('editor', groups) !== -1; if (us.review) { if (typeof data.reviews === 'number') us.writePatrolCount(data.reviews); } else if (us.viewPatrolNumber) { us.us_patrolcount_loading.parent().remove(); } edits = [ edits, ' • ', $('<a>', { href: '//xtools.wmflabs.org/ec' + mw.config.get('wgServer').substr(1) + '/' + us.user, title: 'XTools', text: 'XTools', style: us.styleLoading, target: '_blank' }), ' • ', $('<a>', { href: 'https://meta.wikimedia.org/wiki/Special:CentralAuth/' + us.user, title: 'CentralAuth', text: 'CentralAuth', style: us.styleLoading, target: '_blank' }), ' • ', (project !== 'commonswiki' ? $('<a>', { href: '/wiki/Special:ListFiles/' + us.user, text: 'Upload', title: 'Special:Listfiles', style: us.styleLoading }) : 'Upload'), (us.lang === 'de' ? '-' : ' ') + msg.count + ': ', uploads.clone(1).text('total'), ' / ', uploads ]; if (!$('#t-subpages').length) { edits.push($('<a>', { href: mw.util.getUrl('Special:Prefixindex/User:' + us.user + '/'), text: ' • ' + msg.usub, style: us.styleMissingData + us.styleLoading })); } } else if (us.us_patrolcount_loading) { us.us_patrolcount_loading.parent().remove(); } us.loading_editcount.replaceWith(edits); if (!aw.implicitgroups && !data.groups) { aw.implicitgroups = ['*', 'user', 'autoconfirmed']; if (groups.length < 4) aw.implicitgroups.pop(); } data.groups = $.grep(groups, function (n) { return $.inArray(n, aw.implicitgroups) === -1; }); us.writeGroups(data.groups); $('#t-userrights').remove(); } if (!data.registration) { us.actions.push({ params: '&list=logevents&leprop=timestamp|type&letype=newusers&lelimit=1&ledir=newer&leend=2005-12-30T00:00:00Z&leuser=' + us.user, func: us.writeRegistration }); if (data.firstedit) { uq.query.usercontribs = [{ timestamp: data.firstedit }]; us.writeFirstEdit(uq); } else if (edits) { us.actions.push({ params: { list: 'usercontribs', ucuser: us.user, uclimit: 1, ucend: '2005-09-08T00:00:00Z', ucdir: 'newer', ucprop: 'timestamp' }, func: us.writeFirstEdit }); } } else { us.writeRegistration(data.registration); } us.loading_blocked.replaceWith($('<a>', { style: ((blocked) ? us.styleBlocked : us.styleNotBlocked), id: 'us_block_status_span', href: '/w/index.php?title=Special:Log/block&page=User:' + us.user, title: msg.blocklog, text: ((blocked) ? ' ' : msg.not) + msg.block })); if (blocked) { data.blockreason = aw.blockreason || ' '; us.ul.append([ $('<li>', { id: 'us_block_time' }).append([$('<b>').text('• ' + msg.blockEnd), $.createSpinner('us_block_time_loading')]), $('<li>', { id: 'us_block_reason' }).append([$('<b>').text('• ' + msg.blockReason), us.parseComment(aw.comment, (aw.commenthidden))]), $('<li>', { id: 'us_blocker' }).append([$('<b>').text('• ' + msg.blocker), $.createSpinner('us_blocker_loading')]) ]); us.actions.push({ params: { list: 'logevents', letitle: 'User:' + us.user, letype: 'block', lelimit: 1 }, func: us.writeBlockDetail }); } if (data.glGrp && !data.locked) { uq.query.globaluserinfo = { groups: (data.glGrp.length) ? data.glGrp : null }; us.writeGlobalGroup(uq); } else { us.actions.push({ params: '&meta=globaluserinfo&guiprop=groups&guiuser=' + us.user, func: us.writeGlobalGroup }); } if (gender) { data.gender = gender; var genderSn = (gender === 'male') ? ' \u2642' : (gender === 'female' ? ' \u2640' : ''); $('#firstHeading').append($('<span>', { id: 'ps-gender-' + gender, style: 'font-size:80%', text: genderSn })); } // Automatické načtení poděkování do fronty dotazů us.getThanks('rd'); us.getThanks('gn'); if (us.actions.length) us.doRequest(); }, createBox: function () { var statusBox = $('<div>', { style: 'border-bottom:1px solid #aaa;text-shadow:1px 1px 1px #eff;', id: 'us_box' }); if (mw.config.get('skin') === 'vector') statusBox.css('font-size', '0.8em'); var spanFrag = $.createSpinner(); us.loading_editcount = spanFrag.clone(); us.loading_registration = spanFrag.clone(); us.loading_groups = spanFrag.clone(); us.loading_blocked = spanFrag.clone(); us.us_global_group_loading = spanFrag.clone(); us.us_last_edit_loading = $.createSpinner('contribs'); us.loading_last_edit = $('<li>'); us.us_global_group = $('<li>', { style: 'display: none' }); var thxRd = $('<a>', { id: 'us_thx_rd_val', title: msg.thxRd, style: us.styleLoading, text: '...' }); var thxGn = $('<a>', { id: 'us_thx_gn_val', title: msg.thxGn, style: us.styleLoading, text: '...' }); var $userrights = $('#t-userrights a'); var $contributions = $('#t-contributions a'); if (!$userrights.length) $userrights = $('<a>', { href: '/wiki/Special:UserRights/' + us.user }); if (!$contributions.length) $contributions = $('<a>', { href: '/wiki/Special:Contributions/' + us.user }); var ul = $('<ul>', { style: 'list-style: none' }).append($('<li>', { id: 'us_editcount' }).append([$('<b>').append($contributions.text(msg.contrib)), us.loading_editcount])); us.us_log_count = $('<span>'); if (us.viewPatrolNumber) { us.us_patrolcount_loading = $('<a>', { style: us.styleLoading, click: us.getPatrolCount, text: msg.count }); us.us_log_count = $('<span>').append([$('<b>').text(msg.reviews), us.us_patrolcount_loading, ' • ']); } ul.append(us.us_log_count) .append($('<li>', { id: 'us_reg_date' }).append([$('<b>').text(msg.regdate), us.loading_registration, $('<a>', { target: '_blank', style: 'float:right;' + us.styleMissingData, href: 'https://meta.toolforge.org/userpages/' + us.user, text: '• Global user pages' })])) .append($('<li>').append([$('<b>').append($userrights.text(msg.loGrp)), us.loading_groups])) .append($('<li>', { id: 'us_former_local_groups', style: 'display:none;' + us.styleFormer }).append([$('<b>').text(msg.formerLoGrp), $('<span>', { 'class': 'us_former_content' })])) .append(us.us_global_group.append([$('<b>').append($('<a>', { href: 'https://meta.wikimedia.org/wiki/Special:Log?type=gblrights&user=&page=User:' + us.user, text: msg.glGrp })), us.us_global_group_loading])) .append($('<li>', { id: 'us_former_global_groups', style: 'display:none;' + us.styleFormer }).append([$('<b>').text(msg.formerGlGrp), $('<span>', { 'class': 'us_former_content' })])) .append(us.loading_last_edit.append([$('<b>').text(msg.laedit), us.us_last_edit_loading, $('<a>', { target: '_blank', style: 'float:right;' + us.styleMissingData, href: 'https://meta.wikimedia.org/wiki/Special:GlobalContributions/' + us.user, text: '• Global contributions' })])) .append($('<li>', { id: 'us_last_action_li', style: 'display:none' }).append([$('<b>').text(msg.laact), $('<span>', { id: 'us_last_action_val' })])) .append($('<li>', { id: 'us_last_admin_li', style: 'display:none' }).append([$('<b>').text(msg.laadmin), $('<span>', { id: 'us_last_admin_val' })])) .append($('<li>', { id: 'us_last_cu_li', style: 'display:none' }).append([$('<b>').text(msg.lacu), $('<span>', { id: 'us_last_cu_val' })])) .append($('<li>', { id: 'us_last_os_li', style: 'display:none' }).append([$('<b>').text(msg.laos), $('<span>', { id: 'us_last_os_val' })])); statusBox.append(ul); us.ul = $('<ul>', { style: 'list-style:none' }).append($('<li>', { id: 'us_block_status' }).append([$('<b>').text(msg.blocks), us.loading_blocked, ' • ', $('<b>').text(msg.thxGvng), msg.thxRd, ': ', thxRd, ' / ', msg.thxGn, ': ', thxGn, $('<a>', { style: 'float:right;' + us.styleMissingData, title: 'Purge data', text: 'purge', click: us.removeDataStore })])); statusBox.append(us.ul); $('#firstHeading').after(statusBox); us.statusBox = statusBox; }, removeDataStore: function (e) { var key = project + us.user, db = window.indexedDB; e.preventDefault(); if (!db || !us.dbVersion) return mw.cookie.set(us.name + us.user, null); var request = db.open(us.name, us.dbVersion + 1); request.onupgradeneeded = function () { db = this.result; if (db.objectStoreNames.contains(key)) db.deleteObjectStore(key); }; request.onsuccess = function () { db = this.result; us.dbVersion = db.version; db.close(); location.reload(); }; }, setCookie: function () { var domain = (mw.config.get('wgNoticeProject') === 'wikipedia') ? 'wikipedia.org' : ''; var name = us.name + us.user; var _saveCookie = function (k, d) { mw.cookie.set(name, JSON.stringify(d), { expires: 604800, domain: domain }); }; if (!us.actions[0] && JSON && data.editcount) { if (us.cookie.length) window.clearTimeout(us.cookie.shift()); var saveData = window.indexedDB ? function (n, JSd, v) { var key = project + us.user, db = window.indexedDB, request = db.open(us.name, v); request.onupgradeneeded = function () { db = this.result; if (!db.objectStoreNames.contains(key)) { var store = db.createObjectStore(key, { keyPath: 'name' }); store.createIndex('data', 'data', { unique: false }); } }; request.onsuccess = function () { db = this.result; us.dbVersion = db.version; if (db.objectStoreNames.contains(key)) { var store = db.transaction(key, 'readwrite').objectStore(key); store.put({ name: key, data: JSd }); } else { saveData(n, JSd, us.dbVersion + 1); } }; request.onerror = function() { _saveCookie(n, JSd); }; } : _saveCookie; us.cookie.push(setTimeout(function () { data.timestamp = new Date().valueOf(); saveData('', data); }, 500)); } }, doRequest: function () { var action = us.actions.shift(); if (action) us.ajaxRequest(action.params, action.func); if (us.actions.length) window.setTimeout(function () { us.doRequest(); }, 100); }, getDateDiff: function (now, date) { var dStr = []; if (now > date) { var years = now.getFullYear() - date.getFullYear(); var months = now.getMonth() - date.getMonth(); var days = now.getDate() - date.getDate(); if (days < 0) { months--; var prevMonth = new Date(now.getFullYear(), now.getMonth(), 0).getDate(); days += prevMonth; } if (months < 0) { years--; months += 12; } var units = [years, months, days]; var labelSingle = [msg.date[0], msg.date[1], msg.date[2]]; var labelPlural = [msg.dates[0], msg.dates[1], msg.dates[2]]; for (var i = 0; i < 3; ++i) { if (units[i] > 0) { dStr.push(units[i] + ' ' + ((units[i] > 1) ? labelPlural[i] : labelSingle[i])); } } } var mainDiff = (dStr.length) ? dStr.join(', ').replace(/(.*),([^,]*)/, msg.and).replace(/(.*)/, msg.ago) : msg.nosec + msg.date[2]; return mainDiff; }, getDateFromTimestamp: function (t) { if (!t) return false; t = new Date(t); return isNaN(t.valueOf()) ? false : t; }, writeRegistration: function (aw) { if (aw) { if (aw instanceof Object) { if (aw.query && aw.query.logevents.length) aw = aw.query.logevents[0].timestamp; } if (typeof aw === 'string') { us.loading_registration.replaceWith(us.formatDate(aw) + ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.getDateDiff(us.now, us.getDateFromTimestamp(aw)) + ')' })); data.registration = aw; return us.setCookie(); } } us.loading_registration.replaceWith($('<span>', { style: us.styleMissingData + us.styleLoading, text: msg.nodb })); }, writeFirstEdit: function (aw) { if (!aw.query || !aw.query.usercontribs.length) return; var date = data.firstedit = aw.query.usercontribs[0].timestamp; $('#us_reg_date').append($('<li>').append([$('<b>', { text: '• ' + msg.fiedit }), us.formatDate(date) + ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.getDateDiff(us.now, us.getDateFromTimestamp(date)) + ')' })])); }, writeLastEdit: function (aw) { if (!aw.query || !aw.query.usercontribs || !aw.query.usercontribs.length) return us.us_last_edit_loading.replaceWith($('<span>', { style: us.styleMissingData, text: msg.noedit })); var date = data.lastedit = aw.query.usercontribs[0].timestamp; // Logika pro dnes/včera var d = new Date(date); var today = new Date(us.now); var yesterday = new Date(us.now); yesterday.setDate(yesterday.getDate() - 1); var diffText = us.getDateDiff(us.now, d); if (d.toDateString() === today.toDateString()) diffText = msg.today || 'today'; else if (d.toDateString() === yesterday.toDateString()) diffText = msg.yesterday || 'yesterday'; var diffNode = $('<a>', { href: mw.util.getUrl('Special:Contributions/' + us.user), text: diffText }); us.us_last_edit_loading.replaceWith(diffNode, ' ', $('<span>', { style: us.styleMissingData, text: '(' + us.formatDate(date) + ')' })); data.timediff = mw.now() - us.now; $('#us_editcount').append($('<span>', { style: 'float:right;' + us.styleMissingData, text: msg.curtimeDiff }).append($('<b>', { text: Math.round(data.timediff / 1000) }))); us.setCookie(); }, writeGlobalGroup: function (aw) { aw = aw.query.globaluserinfo; var groups = data.glGrp = aw.groups; // Pokud lokální registrace chybí, použijeme tu z globálního infa if (!data.registration && aw.registration) { us.writeRegistration(aw.registration); } if (groups && groups.length) { us.us_global_group_loading.replaceWith(us.getLocalNames(groups, 'GlobalGroupPermissions')); us.us_global_group.css('display', 'block'); // Fetch former global rights us.fetchFormerRights(true); } if (aw.locked !== undefined) { data.locked = 1; mw.loader.using('mediawiki.ForeignApi').done(function () { var fApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php'); fApi.get({ action: 'query', list: 'logevents', leprop: 'user|timestamp|comment', letype: 'globalauth', letitle: 'User:' + us.user, lelimit: '1' }).done(us.writeGlobalBlock); }); } us.setCookie(); }, writeGlobalBlock: function (aw) { if (!aw.query || !aw.query.logevents.length) return; aw = aw.query.logevents[0]; $('#us_block_status_span').replaceWith($('<a>', { style: us.styleBlocked, id: 'us_block_status_span', href: '//meta.wikimedia.org/w/index.php?title=Special:Log/&type=globalauth&user=&page=User%3A' + us.user + '%40global', text: 'locked (global)' }).append($('<span>', { style: us.styleMissingData, text: ' – ' + us.formatDate(aw.timestamp) }))); $('#us_block_status').parent().append([$('<li>').append([$('<b>').text('• ' + msg.blockReason), us.parseComment(aw.comment)]), $('<li>').append([$('<b>').text('• ' + msg.blocker), $('<a>', { 'class': 'mw-userlink', 'title': 'User:' + aw.user, 'href': mw.util.getUrl('User:' + aw.user), 'text': aw.user })])]); }, writeBlockDetail: function (aw) { var duration = 'infinite', expiry = ''; if (aw.query && aw.query.logevents.length) { aw = aw.query.logevents[0]; if (aw && aw.params) { duration = aw.params.duration; expiry = /in(de)?finite/.test(duration) ? msg.never : us.formatDate(aw.params.expiry); } $('#us_block_status_span').text(msg.block + ' (' + duration + ')').append($('<span>', { style: us.styleMissingData, text: ' – ' + us.formatDate(aw.timestamp) })); if (expiry) $('#mw-spinner-us_block_time_loading').replaceWith(expiry); else $('#us_block_time').remove(); $('#mw-spinner-us_block_reason_loading').replaceWith(us.parseComment(aw.comment, aw.commenthidden)); if (aw.user) $('#mw-spinner-us_blocker_loading').replaceWith($('<a>', { 'class': 'mw-userlink', 'title': 'User:' + aw.user, 'href': mw.util.getUrl('User:' + aw.user), 'text': aw.user })); else $('#mw-spinner-us_blocker_loading').replaceWith(msg.nodb); } }, writePatrolCount: function (aw) { if (aw instanceof Object) { if (!aw.query || !aw.query.logevents) return; us.patrols += aw.query.logevents.length; if (aw.continue) return us.getPatrolCount(aw.continue); $.removeSpinner('pat'); aw = us.patrols; } us.us_patrolcount_loading.replaceWith($('<a>', { href: '/w/index.php?title=Special:Log&type=' + (us.review ? 'review&subtype=accept' : 'patrol&subtype=patrol') + '&user=' + us.user, text: aw })); data.reviews = aw; us.setCookie(); }, getPatrolCount: function (e) { var params = '&list=logevents&leprop=&ledir=newer&lelimit=max&leuser=' + us.user + '&leaction=' + (us.review ? 'review/approve' : 'patrol/patrol'); if (e instanceof Object) { if (e.target) { $(e.target).injectSpinner('pat'); us.patrols = 0; } else if (e.lecontinue) params += '&lecontinue=' + e.lecontinue; } us.actions.push({ params: params, func: us.writePatrolCount }); us.doRequest(); }, parseComment: function (text, hidden) { var comment = $('<span>', { 'class': 'comment' }); if (typeof text === 'undefined') return comment.append(hidden ? msg.noReason : 'undefined').css('color', hidden ? '#999' : 'red'); if (!text) return comment.append(msg.blockCmt).css('color', '#999'); var intLink = /(.*?)\[\[((.*?)\|)?(.*?)\]\](.*)/, suche = text, erg; while ((erg = intLink.exec(suche)) !== null) { var link = erg[3] || erg[4]; comment.append([erg[1], $('<a>', { href: mw.util.getUrl(link), title: link }).text(erg[4])]); suche = erg[5]; } return comment.append(suche); }, formatDate: function (datum) { if (!(datum instanceof Date)) datum = new Date(datum); try { return datum.toLocaleDateString(us.lang, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); } catch (e) { return datum.toLocaleString(); } }, getStoredData: function () { var key = project + us.user, db, request; try { request = indexedDB.open(us.name); request.onsuccess = function () { db = this.result; if (db.objectStoreNames.contains(key)) { var store = db.transaction(key, 'readonly').objectStore(key); store.transaction.oncomplete = function () { us.runDataStore(); db.close(); }; store.get(key).onsuccess = function () { if (this.result) data = this.result.data; else us.getCookie(); }; } else { us.getCookie(); } }; request.onerror = function () { us.getCookie(); }; } catch (e) { us.getCookie(); } }, getCookie: function () { data = mw.cookie.get(us.name + us.user); if (data) try { data = JSON.parse(data); } catch(e) { data = null; } us.runDataStore(); }, initI18N: function (i18n) { var chain = mw.language.getFallbackLanguageChain(); for (var i = chain.length - 1; i >= 0; i--) if (chain[i] in i18n) msg = i18n[chain[i]]; }, init: function () { if (this.statusBox || (this.statusBox = $('#us_box'))[0]) this.statusBox.remove(); this.usprop = 'blockinfo|editcount|gender|groups|implicitgroups|registration'; this.self = mw.config.get('wgUserName') === this.user; this.initI18N(i18n); this.createBox(); if (!this.first) { if (mw.cookie && JSON) { if (window.indexedDB) this.getStoredData(); else this.getCookie(); } else this.run(); } else this.run(); }, runDataStore: function () { if (data) { if (typeof data === 'string') try { data = JSON.parse(data); } catch(e) { data = {}; } var q = { query: { users: [data] } }; if (data.timestamp) this.now = mw.now() - (data.timediff || 0); if (data.timestamp && Math.abs((mw.now() - data.timestamp - (data.timediff || 0)) / 1000) < 90) return this.writeCommonInfo(q); if (data.editcount) this.usprop = 'editcount'; } data = data || {}; this.run(); }, run: function () { if (this.self) { return this.writeCommonInfo({ query: { users: [{ editcount: mw.config.get('wgUserEditCount'), registration: mw.config.get('wgUserRegistration'), groups: mw.config.get('wgUserGroups'), blockreason: '' }] } }); } us.ajaxRequest({ curtimestamp: 1, list: 'users', ususers: us.user, usprop: us.usprop }, us.writeCommonInfo); } }; if ([2, 3].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && us.user.indexOf('/') === -1 && (!mw.config.get('wgArticleId') || mw.config.get('wgAction') === 'view')) { $.when(mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.language', 'mediawiki.cookie', 'jquery.spinner', 'mediawiki.ForeignApi']), $.ready).then(function () { us.api = mw.util.wikiScript('api') + '?action=query&format=json'; us.ajaxRequest('&meta=allmessages&amenableparser=1&amincludelocal=1&amprefix=group-', us.getGroupNames); if (!mw.libs.viewerInfo) mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Perhelion/viewerInfo.js&action=raw&ctype=text/javascript'); }); } }(jQuery, mediaWiki)); k7ffucgb87k3aym9jo6tlc56bzt5eha Module:Uses TemplateStyles 828 110021 740090 609441 2026-02-16T01:11:16Z en>JPxG 0 copy slopped edit from sandbox to provide a link to Special:EditPage for the linked pages; looks great to me on all the testcases, i dont think this will bust anything but yolo [[User:Pppery]] feel free to shoot me if i break anything 740090 Scribunto text/plain local yesno = require('Module:Yesno') local mList = require('Module:List') local mTableTools = require('Module:TableTools') local mMessageBox = require('Module:Message box') local TNT = require('Module:TNT') local p = {} local function format(msg, ...) return TNT.format('I18n/Uses TemplateStyles', msg, ...) end local function getConfig() return mw.loadData('Module:Uses TemplateStyles/config') end local function makeEditSup(pageName) return string.format('<sup>&lbrack;[[Special:Edit/%s|e]]&rbrack;</sup>', pageName) end -- Build just the "(sandbox)" suffix using the i18n message, without duplicating the main link. local function makeSandboxSuffix(tsSandboxPrefixed) -- i18n message is: "$1 ([[$2|sandbox]])" -- Passing empty $1 yields " ([[:...|sandbox]])" -> trim leading whitespace to get "(sandbox)" portion. local s = format('sandboxlink', '', ':' .. tsSandboxPrefixed) return (s:gsub('^%s+', '')) end local function renderBox(tStyles) local boxArgs = { type = 'notice', small = true, image = string.format('[[File:Farm-Fresh css add.svg|32px|alt=%s]]', format('logo-alt')) } if #tStyles < 1 then boxArgs.text = string.format('<strong class="error">%s</strong>', format('error-emptylist')) else local cfg = getConfig() local tStylesLinks = {} for i, ts in ipairs(tStyles) do local viewLink = string.format('[[:%s]]', ts) local editSup = makeEditSup(ts) local out = viewLink .. editSup -- Optional sandbox link + sandbox edit link local tsTitle = mw.title.new(ts) if tsTitle and cfg['sandbox_title'] then local tsSandboxTitle = mw.title.new(string.format( '%s:%s/%s/%s', tsTitle.nsText, tsTitle.baseText, cfg['sandbox_title'], tsTitle.subpageText )) if tsSandboxTitle and tsSandboxTitle.exists then local sandboxSuffix = makeSandboxSuffix(tsSandboxTitle.prefixedText) local sandboxEditSup = makeEditSup(tsSandboxTitle.prefixedText) out = out .. ' ' .. sandboxSuffix .. ' ' .. sandboxEditSup end end tStylesLinks[i] = out end local tStylesList = mList.makeList('bulleted', tStylesLinks) boxArgs.text = format( mw.title.getCurrentTitle():inNamespaces(828, 829) and 'header-module' or 'header-template' ) .. '\n' .. tStylesList end return mMessageBox.main('mbox', boxArgs) end local function renderTrackingCategories(args, tStyles, titleObj) if yesno(args.nocat) then return '' end local cfg = getConfig() local cats = {} -- Error category if #tStyles < 1 and cfg['error_category'] then cats[#cats + 1] = cfg['error_category'] end -- TemplateStyles category titleObj = titleObj or mw.title.getCurrentTitle() if (titleObj.namespace == 10 or titleObj.namespace == 828) and not cfg['subpage_blacklist'][titleObj.subpageText] then local category = args.category or cfg['default_category'] if category then cats[#cats + 1] = category end if not yesno(args.noprotcat) and (cfg['protection_conflict_category'] or cfg['padlock_pattern']) then local currentProt = titleObj.protectionLevels["edit"] and titleObj.protectionLevels["edit"][1] or nil local addedLevelCat = false local addedPadlockCat = false for _, ts in ipairs(tStyles) do local tsTitleObj = mw.title.new(ts) local tsProt = tsTitleObj.protectionLevels["edit"] and tsTitleObj.protectionLevels["edit"][1] or nil if cfg['padlock_pattern'] and tsProt and not addedPadlockCat then local content = tsTitleObj:getContent() if not content:find(cfg['padlock_pattern']) then cats[#cats + 1] = cfg['missing_padlock_category'] addedPadlockCat = true end end if cfg['protection_conflict_category'] and currentProt and tsProt ~= currentProt and not addedLevelCat then currentProt = cfg['protection_hierarchy'][currentProt] or 0 tsProt = cfg['protection_hierarchy'][tsProt] or 0 if tsProt < currentProt then addedLevelCat = true cats[#cats + 1] = cfg['protection_conflict_category'] end end end end end for i, cat in ipairs(cats) do cats[i] = string.format('[[Category:%s]]', cat) end return table.concat(cats) end function p._main(args) local cfg = getConfig() if #args == 0 then local prefixed = mw.title.getCurrentTitle().prefixedText prefixed = prefixed:gsub("/doc", "") args[1] = prefixed .. "/" .. cfg["default_subpage_name"] end local tStyles = mTableTools.compressSparseArray(args) local box = renderBox(tStyles) local trackingCategories = renderTrackingCategories(args, tStyles) return box .. trackingCategories end function p.main(frame) local origArgs = frame:getParent().args local args = {} for k, v in pairs(origArgs) do v = v:match('^%s*(.-)%s*$') if v ~= '' then args[k] = v end end return p._main(args) end return p fzxzfes95mnfacwuhc6k9zeaixdtemr 740091 740090 2026-05-01T19:28:21Z Novem Linguae 49714 1 revision imported from [[:en:Module:Uses_TemplateStyles]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740090 Scribunto text/plain local yesno = require('Module:Yesno') local mList = require('Module:List') local mTableTools = require('Module:TableTools') local mMessageBox = require('Module:Message box') local TNT = require('Module:TNT') local p = {} local function format(msg, ...) return TNT.format('I18n/Uses TemplateStyles', msg, ...) end local function getConfig() return mw.loadData('Module:Uses TemplateStyles/config') end local function makeEditSup(pageName) return string.format('<sup>&lbrack;[[Special:Edit/%s|e]]&rbrack;</sup>', pageName) end -- Build just the "(sandbox)" suffix using the i18n message, without duplicating the main link. local function makeSandboxSuffix(tsSandboxPrefixed) -- i18n message is: "$1 ([[$2|sandbox]])" -- Passing empty $1 yields " ([[:...|sandbox]])" -> trim leading whitespace to get "(sandbox)" portion. local s = format('sandboxlink', '', ':' .. tsSandboxPrefixed) return (s:gsub('^%s+', '')) end local function renderBox(tStyles) local boxArgs = { type = 'notice', small = true, image = string.format('[[File:Farm-Fresh css add.svg|32px|alt=%s]]', format('logo-alt')) } if #tStyles < 1 then boxArgs.text = string.format('<strong class="error">%s</strong>', format('error-emptylist')) else local cfg = getConfig() local tStylesLinks = {} for i, ts in ipairs(tStyles) do local viewLink = string.format('[[:%s]]', ts) local editSup = makeEditSup(ts) local out = viewLink .. editSup -- Optional sandbox link + sandbox edit link local tsTitle = mw.title.new(ts) if tsTitle and cfg['sandbox_title'] then local tsSandboxTitle = mw.title.new(string.format( '%s:%s/%s/%s', tsTitle.nsText, tsTitle.baseText, cfg['sandbox_title'], tsTitle.subpageText )) if tsSandboxTitle and tsSandboxTitle.exists then local sandboxSuffix = makeSandboxSuffix(tsSandboxTitle.prefixedText) local sandboxEditSup = makeEditSup(tsSandboxTitle.prefixedText) out = out .. ' ' .. sandboxSuffix .. ' ' .. sandboxEditSup end end tStylesLinks[i] = out end local tStylesList = mList.makeList('bulleted', tStylesLinks) boxArgs.text = format( mw.title.getCurrentTitle():inNamespaces(828, 829) and 'header-module' or 'header-template' ) .. '\n' .. tStylesList end return mMessageBox.main('mbox', boxArgs) end local function renderTrackingCategories(args, tStyles, titleObj) if yesno(args.nocat) then return '' end local cfg = getConfig() local cats = {} -- Error category if #tStyles < 1 and cfg['error_category'] then cats[#cats + 1] = cfg['error_category'] end -- TemplateStyles category titleObj = titleObj or mw.title.getCurrentTitle() if (titleObj.namespace == 10 or titleObj.namespace == 828) and not cfg['subpage_blacklist'][titleObj.subpageText] then local category = args.category or cfg['default_category'] if category then cats[#cats + 1] = category end if not yesno(args.noprotcat) and (cfg['protection_conflict_category'] or cfg['padlock_pattern']) then local currentProt = titleObj.protectionLevels["edit"] and titleObj.protectionLevels["edit"][1] or nil local addedLevelCat = false local addedPadlockCat = false for _, ts in ipairs(tStyles) do local tsTitleObj = mw.title.new(ts) local tsProt = tsTitleObj.protectionLevels["edit"] and tsTitleObj.protectionLevels["edit"][1] or nil if cfg['padlock_pattern'] and tsProt and not addedPadlockCat then local content = tsTitleObj:getContent() if not content:find(cfg['padlock_pattern']) then cats[#cats + 1] = cfg['missing_padlock_category'] addedPadlockCat = true end end if cfg['protection_conflict_category'] and currentProt and tsProt ~= currentProt and not addedLevelCat then currentProt = cfg['protection_hierarchy'][currentProt] or 0 tsProt = cfg['protection_hierarchy'][tsProt] or 0 if tsProt < currentProt then addedLevelCat = true cats[#cats + 1] = cfg['protection_conflict_category'] end end end end end for i, cat in ipairs(cats) do cats[i] = string.format('[[Category:%s]]', cat) end return table.concat(cats) end function p._main(args) local cfg = getConfig() if #args == 0 then local prefixed = mw.title.getCurrentTitle().prefixedText prefixed = prefixed:gsub("/doc", "") args[1] = prefixed .. "/" .. cfg["default_subpage_name"] end local tStyles = mTableTools.compressSparseArray(args) local box = renderBox(tStyles) local trackingCategories = renderTrackingCategories(args, tStyles) return box .. trackingCategories end function p.main(frame) local origArgs = frame:getParent().args local args = {} for k, v in pairs(origArgs) do v = v:match('^%s*(.-)%s*$') if v ~= '' then args[k] = v end end return p._main(args) end return p fzxzfes95mnfacwuhc6k9zeaixdtemr Giorgio Almirante 0 119367 740007 552400 2026-05-01T14:29:51Z Cryptocurrency777 73698 740007 wikitext text/x-wiki A (not to be confused with Giorgio armani 💎) cg083j19lraecbcumzwmaisyib177ew Moon 0 119986 740119 737215 2026-05-01T21:28:20Z Cryptocurrency777 73698 740119 wikitext text/x-wiki It's something in the sky [[File:Moon.jpg|thumb]] 8gdz06hqszyf77pk2euqcxr0wgjgmn4 World Championships of Ski Mountaineering 0 122035 740123 690838 2026-05-02T00:06:27Z InternetArchiveBot 34092 Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5 740123 wikitext text/x-wiki {{update|date=August 2018}} The '''World Championships of Ski Mountaineering''' are biannually held [[ski mountaineering]] competitions. == History == The events were originally sanctioned by the [[International Council for Ski Mountaineering Competitions]] (ISMC). The first official world mastership of the ISMC was carried out in the "[[International observance#2000s|International Year of Mountains]]" (2002), declared by the [[United Nations]]. The championship was held in [[Serre Chevalier]], [[France]], from January 24 to January 27, 2002. Prior the Italian [[Trofeo Mezzalama]] was held as "World Championship of Ski Mountaineering" with the classes "Civilians", "[[Soldier]]s" and "[[Mountain guide]]s" in 1975.<ref name="Rolf Majcen">[[Rolf Majcen]]: [http://www.mountains2b.com/76-Weltmeisterschaften_im_Skibergsteigen-,e_14310,r_1100.htm ''Weltmeisterschaften im Skibergsteigen''] {{Webarchive|url=https://archive.is/20121209103029/http://www.mountains2b.com/76-Weltmeisterschaften_im_Skibergsteigen-,e_14310,r_1100.htm|date=2012-12-09}} (German), February, 2004.</ref> Because the ISMC merged into the [[International Ski Mountaineering Federation]] (ISMF) in 2008, the next championships were sanctioned by the ISMF.<ref>[[Karl Posch]]: [http://www.teamformultimedia.at/_iu_write/cms/editor/page.php?user=askimo&vorlage=vorlage.php&file=index.htm&newsid=718& Weltmeisterschaft Schweiz Tag 5 - Neuer Verband der Schibergsteiger: ISMF] {{Webarchive|url=https://web.archive.org/web/20110706095145/http://www.teamformultimedia.at/_iu_write/cms/editor/page.php?user=askimo&vorlage=vorlage.php&file=index.htm&newsid=718&|date=2011-07-06}} (German), February 27, 2008.</ref> In 2011, the originally planned 9th edition of the [[European Championships of Ski Mountaineering]] at last was held as 6th edition of the World Championships. {{Expand section|missing medalists|date=April 2009}} {| class="wikitable" ! colspan="5" | Medalist teams of the 1975 Trofeo Mezzalama |- ! rowspan="3" | "civilian teams" | [[File:Med 1.png]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Renzo Meynet]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Osvaldo Ronc]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Mirko Stangalino]]<ref name="Albo d'oro">{{Cite web |url=http://www.old.inalto.org/magazine/trofeo_mezzalama_2005_04.shtml |title=''Albo d'oro'' |access-date=2020-05-28 |archive-date=2017-08-09 |archive-url=https://web.archive.org/web/20170809172431/http://www.old.inalto.org/magazine/trofeo_mezzalama_2005_04.shtml |url-status=dead }}</ref> |- ! bgcolor="silver" | [[File:Med 2.png]] | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; |- ! bgcolor="bronze" | [[File:Med 3.png]] | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; |- ! rowspan="3" | "military teams" | [[File:Med 1.png]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Angelo Genuin]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Bruno Bonaldi]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Luigi Weiss|Luigi "Gigi" Weiss]]<ref name="Albo d'oro" /> |- ! bgcolor="silver" | [[File:Med 2.png]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Gianfranco Stella]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Aldo Stella (skier)|Aldo Stella]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Leo Vidi]]<ref name="Genuin Angelo">{{Cite web |url=http://www.fondoitalia.it/personaggi/Genuin_Angelo.html |title=''Genuin Angelo'' |access-date=2020-05-28 |archive-date=2011-07-22 |archive-url=https://web.archive.org/web/20110722033827/http://www.fondoitalia.it/personaggi/Genuin_Angelo.html |url-status=dead }}</ref> |- ! bgcolor="bronze" | [[File:Med 3.png]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Willy Bertin]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Felice Darioli]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Fabrizio Pedranzini]]<ref name="Genuin Angelo" /> |- ! rowspan="3" | "mountain guides" | [[File:Med 1.png]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Oreste Squinobal]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Arturo Squinobal]] | bgcolor="#F7F6A8" |{{flagicon|Italy}}&nbsp;[[Lorenzo Squinobal]]<ref name="Albo d'oro" /> |- ! bgcolor="silver" | [[File:Med 2.png]] | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; |- ! bgcolor="bronze" | [[File:Med 3.png]] | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; | bgcolor="#F7F6A8" |{{flagicon|}}&nbsp; |} Further venues of the ISMC World Championships were the [[Aran Valley]] (Spain) in 2004, the Italian [[Province of Cuneo]] in 2006, and [[Portes du Soleil]] (Switzerland) in 2008.<ref name="History">[http://theuiaa.org/interna.php?page=Ski_history&change_language=ENG ''History of ski mountaineering''] {{Webarchive|url=https://web.archive.org/web/20131213195504/http://theuiaa.org/interna.php?page=Ski_history&change_language=ENG |date=2013-12-13 }}, UIAA.</ref> The World Championships are supported by the national organizations of the carrying out countries. == Ratings == The disciplines are rated by gender and age groups. In 2002, only individual and team (2 racers) races were held and rated, added with a combined ranking. At the 2004 championship a relay event and a [[vertical race]] competition were added. The men's relay teams were of four racers and the women's teams of three. In the following years all relay teams were of four ski mountaineers. In 2006 the relay race was canceled because of bad snow conditions, and consequently there was no combined ranking. At the 2008 World Masterships a long distance race was added. The national squads are often mixed with up an coming athletes of the "Espoirs"-level. Some nations do not have squads with enough racers for all disciplines. == Medalist nations and disciplines == (by point-awarding system)<ref name="sports123">[http://sports123.com/mou/index.html ''Mountaineering-World Ski Mountaineering Championships''] {{Webarchive|url=https://web.archive.org/web/20050207011458/http://sports123.com/mou/index.html |date=2005-02-07 }}, ''sports123.com''.</ref> {| class="wikitable" ! rowspan="2" | year ! rowspan="2" | venue ! rowspan="2" bgcolor="gold" | 1. ! rowspan="2" bgcolor="silver" | 2. ! rowspan="2" bgcolor="#cc9966" | 3. ! colspan="6" | disciplines |- | individual | team | combination | relay | vertical race | long distance |- | [[2002 World Championship of Ski Mountaineering|1st 2002]] | Serre Chevalier, [[France|FRA]] | bgcolor="gold" | {{flag|FRA}} | bgcolor="silver" | {{flag|ITA}} | bgcolor="#cc9966" | {{flag|SUI}} ! X ! X ! X ! - ! - ! - |- | [[2004 World Championship of Ski Mountaineering|2nd 2004]] | Aran Valley, [[Spain|ESP]] | bgcolor="gold" | {{flag|SUI}} | bgcolor="silver" | {{flag|ITA|2003}} | bgcolor="#cc9966" | {{flag|FRA}} ! X ! X ! X ! X ! X ! - |- | [[2006 World Championship of Ski Mountaineering|3rd 2006]] | Province of Cuneo, [[Italy|ITA]] | bgcolor="gold" | {{flag|ITA}} | bgcolor="silver" | {{flag|SUI}} | bgcolor="#cc9966" | {{flag|FRA}} ! - ! X ! - ! X ! X ! - |- | [[2008 World Championship of Ski Mountaineering|4th 2008]] | Champery, Portes du Soleil, [[Switzerland|SUI]] | bgcolor="gold" | {{flag|ITA}} | bgcolor="silver" | {{flag|FRA}} | bgcolor="#cc9966" | {{flag|SUI}} ! X ! X ! X ! X ! X ! X |- | [[2010 World Championship of Ski Mountaineering|5th 2010]] | Gran Valira, [[Andorra|AND]] | bgcolor="gold" | {{flag|ITA}} | bgcolor="silver" | {{flag|FRA}} | bgcolor="#cc9966" | {{flag|SUI}} ! X ! X ! X ! X ! X ! - |- | [[2011 World Championship of Ski Mountaineering|6th 2011]] | Claut, [[Italia|ITA]] | bgcolor="gold" | {{flag|FRA}} | bgcolor="silver" | {{flag|SUI}} | bgcolor="#cc9966" | {{flag|ITA}} ! X ! X ! X ! X ! X ! - |- | [[2013 World Championship of Ski Mountaineering|7th 2013]] | [[Puy-Saint-Vincent]], [[Pelvoux]], [[France|FRA]] | bgcolor="gold" | {{flag|ITA}} | bgcolor="silver" | {{flag|FRA}} | bgcolor="#cc9966" | {{flag|SUI}} ! X ! X ! X ! X ! X ! - |- | [[2015 World Championship of Ski Mountaineering|8th 2015]] | [[Verbier]], [[Switzerland|SUI]] | bgcolor="gold" | {{flag|ITA}} | bgcolor="silver" | {{flag|FRA}} | bgcolor="#cc9966" | {{flag|SUI}} ! X ! X ! X ! X ! X ! - |- |- | [[2017 World Championship of Ski Mountaineering|9th 2017]] | [[Tambre]] - [[Piancavallo]], [[Italy|ITA]] |{{flag|ITA}} |{{flag|SUI}} |{{flag|FRA}} ! X ! X ! X ! X ! X ! - |- |10th 2019 |Villars-sur-Ollon, SUI |{{flag|SUI}} |{{flag|ITA}} |{{flag|FRA}} !X !X !X !X !X !- |- |} ==See also== *[[ISMF World Cup Ski Mountaineering]] == References == {{reflist|2}} == External links == * [http://www.skieverest.com/skimount2002.htm Ski Mountaineering World Championship 2002] {{Webarchive|url=https://web.archive.org/web/20090329071306/http://www.skieverest.com/skimount2002.htm |date=2009-03-29 }}, president of the UIAA [[Ian McNaught-Davis]] {{World Championships of Ski Mountaineering}} {{Main world championships}} [[Category:World Championships of Ski Mountaineering| ]] [[Category:Ski mountaineering competitions]] [[Category:World championships in skiing|Mountaineering]] 5pj9edhplbs7h2n44u71y311odp90gb Template:AfC submission/doc 10 122523 740086 584342 2026-04-18T14:08:26Z en>W.andrea 0 /* TemplateData */ +header 740086 wikitext text/x-wiki {{Documentation subpage}} {{#ifeq:{{#invoke:High-use|num|x|demo={{ROOTPAGENAME}}}}|many||{{High-use|demo={{ROOTPAGENAME}}}}}} {{Wikipedia:Article alerts/Bot use warning}} {{transclusionless}} {{lua|Module:AfC submission catcheck}} {{Uses TemplateStyles|Template:AfC submission/styles.css}} == Usage == This template is meant as a single template for marking the status of an [[WP:Articles for creation|Articles for creation]] submission. To use this template to submit an article for review, use {{Tlsp|submit| ''username'' }}, where ''username'' is the username of the draft's creator (left blank it will assume it is you). The template uses one of the following parameter options: * {{Tlp|AfC submission|T}} produces the unsubmitted draft banner {{tl|AfC submission/draft}} ** Shortcut: {{Tlps|AfC draft| ''username'' }}, where ''username'' is the username of the draft's creator (left blank it will assume it is you). * {{Tl|AfC submission}} (no first parameter) produces the pending submission banner {{tl|AfC submission/pending}} ** This may also be produced using {{Tlps|submit|''username''}}, as discussed above. * {{Tlp|AfC submission|D}} produces the declined banner {{tl|AfC submission/declined}}, or the rejected banner {{tl|AfC submission/rejected}}, depending on whether the <code>reject</code> parameter is passed with some value * {{Tlp|AfC submission|R}} produces the reviewing banner {{tl|AfC submission/reviewing}} * When a submission is moved into mainspace, it produces the "created" message {{tl|AfC submission/created}} == Decline options == When declining an AfC submission, this template can take additional parameters to include a decline reason. All decline templates used under the old system (listed {{oldid|Wikipedia:WikiProject Articles for creation/Templates#Decision templates that can be used for declined articles|242565191|here}}) are included in this template. The idea is to streamline the decline process; instead of including three templates, now a reviewer need only add a single parameter to the template to include a decline reason. To include the decline reason, use {{tlx|AfC submission|D|reason}}, replacing ''reason'' with one of the abbreviations listed at [[Template:AfC submission/comments]]. That page also shows the resulting messages. Some ''reason''s may take an additional parameter ({{tlx|AfC submission|D|reason|Additional parameter}}). These parameters are optional, except the additional parameter for ''reason'' itself. If no parameter is included after '''D''', or an invalid parameter is entered, the invalid parameter will appear as the decline reason. == Subpages== See [[Special:PrefixIndex/Template:AfC submission/]] * [[Template:AfC submission/tools|/tools]] – the links to help reviewers at the bottom of the various templates * [[Template:AfC submission/helptools|/helptools]] – the links to policies and ways to get sources seen on several of the templates * [[Template:AfC submission/old|/old]] – template to mimic the old-style templates * [[Template:AfC submission/comments|/comments]] – stores the comments used by the various parameters of the decline template ** [[Template:AfC submission/table|/table]] – simplifies the code on the comments page * [[Template:AfC submission/reject reasons|/reject reasons]] – stores the comments used by the various parameters of the reject template * The six banners described above: ** [[Template:AfC submission/pending|/pending]] ** [[Template:AfC submission/declined|/declined]] ** [[Template:AfC submission/rejected|/rejected]] ** [[Template:AfC submission/reviewing|/reviewing]] ** [[Template:AfC submission/draft|/draft]] ** [[Template:AfC submission/created|/created]] * [[Template:AfC submission/misplaced|/misplaced]] – the code used for submissions created in any namespace other than ''Draft'' * [[Template:AfC submission/doc|/doc]] – the documentation you are reading now * [[Template:AfC submission/submit|/submit]] – code for new submissions * [[Template:AfC submission/draftnew|/draftnew]] – code for new draft submissions * Preload templates: ** [[Template:AfC submission/banner preload|/banner preload]] ** [[Template:AfC submission/banner preload (bio)|/banner preload (bio)]] ** [[Template:AfC submission/user talk preload|/user talk preload]] ** [[Template:AfC submission/user talk preload (hold)|/user talk preload (hold)]] ** [[Template:AfC submission/declined/HD_preload|/declined/HD_preload]] * Editnotice templates: ** [[Template:AfC submission/banner editintro|/banner editintro]] ** [[Template:AfC submission/user talk editintro|/user talk editintro]] ** [[Template:AfC submission/user talk editintro (hold)|/user talk editintro (hold)]] == TemplateData == {{TemplateData header}} <templatedata> { "params": { "1": {}, "2": { "label": "comment", "description": "reason the submission was declined", "example": "nn", "suggestedvalues": [ "nn", "reason", "neo", "web", "prof", "academic", "athlete", "sport", "music", "band", "film", "book", "event", "corp", "org", "bio", "list", "med", "geo", "species", "astro", "number", "creative", "school", "cat", "blank", "notenglish", "test", "redirect", "van", "ilc", "blp", "cv", "source", "rs", "not", "news", "oneevent", "dict", "plot", "joke", "ai", "llm", "resume", "ecr", "nosource", "essay", "npov", "advert", "spam", "exists", "duplicate", "context", "mergeto", "afd" ], "suggested": true }, "3": {}, "reject": {}, "ns": {}, "ts": {}, "a": {}, "u": {}, "details": {}, "reason2": {}, "details2": {}, "small": {}, "decliner": {}, "declinets": {}, "type": {}, "demo": {} } } </templatedata> == See also == * Templates for user talk pages: ** {{tl|AfC talk}}, for accepted submissions ** {{tl|Afc decline}} and {{tl|Afc reject}}, for declined/rejected submissions * {{tl|AfC date category}} <includeonly>{{Sandbox other|| [[Category:Draft namespace templates]] [[Category:WikiProject Articles for creation templates]] [[Category:Lua-based templates]] }}</includeonly> 70cfsg8lampqhtjkdcltmfjts8s9vg5 740087 740086 2026-05-01T19:28:20Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/doc]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740086 wikitext text/x-wiki {{Documentation subpage}} {{#ifeq:{{#invoke:High-use|num|x|demo={{ROOTPAGENAME}}}}|many||{{High-use|demo={{ROOTPAGENAME}}}}}} {{Wikipedia:Article alerts/Bot use warning}} {{transclusionless}} {{lua|Module:AfC submission catcheck}} {{Uses TemplateStyles|Template:AfC submission/styles.css}} == Usage == This template is meant as a single template for marking the status of an [[WP:Articles for creation|Articles for creation]] submission. To use this template to submit an article for review, use {{Tlsp|submit| ''username'' }}, where ''username'' is the username of the draft's creator (left blank it will assume it is you). The template uses one of the following parameter options: * {{Tlp|AfC submission|T}} produces the unsubmitted draft banner {{tl|AfC submission/draft}} ** Shortcut: {{Tlps|AfC draft| ''username'' }}, where ''username'' is the username of the draft's creator (left blank it will assume it is you). * {{Tl|AfC submission}} (no first parameter) produces the pending submission banner {{tl|AfC submission/pending}} ** This may also be produced using {{Tlps|submit|''username''}}, as discussed above. * {{Tlp|AfC submission|D}} produces the declined banner {{tl|AfC submission/declined}}, or the rejected banner {{tl|AfC submission/rejected}}, depending on whether the <code>reject</code> parameter is passed with some value * {{Tlp|AfC submission|R}} produces the reviewing banner {{tl|AfC submission/reviewing}} * When a submission is moved into mainspace, it produces the "created" message {{tl|AfC submission/created}} == Decline options == When declining an AfC submission, this template can take additional parameters to include a decline reason. All decline templates used under the old system (listed {{oldid|Wikipedia:WikiProject Articles for creation/Templates#Decision templates that can be used for declined articles|242565191|here}}) are included in this template. The idea is to streamline the decline process; instead of including three templates, now a reviewer need only add a single parameter to the template to include a decline reason. To include the decline reason, use {{tlx|AfC submission|D|reason}}, replacing ''reason'' with one of the abbreviations listed at [[Template:AfC submission/comments]]. That page also shows the resulting messages. Some ''reason''s may take an additional parameter ({{tlx|AfC submission|D|reason|Additional parameter}}). These parameters are optional, except the additional parameter for ''reason'' itself. If no parameter is included after '''D''', or an invalid parameter is entered, the invalid parameter will appear as the decline reason. == Subpages== See [[Special:PrefixIndex/Template:AfC submission/]] * [[Template:AfC submission/tools|/tools]] – the links to help reviewers at the bottom of the various templates * [[Template:AfC submission/helptools|/helptools]] – the links to policies and ways to get sources seen on several of the templates * [[Template:AfC submission/old|/old]] – template to mimic the old-style templates * [[Template:AfC submission/comments|/comments]] – stores the comments used by the various parameters of the decline template ** [[Template:AfC submission/table|/table]] – simplifies the code on the comments page * [[Template:AfC submission/reject reasons|/reject reasons]] – stores the comments used by the various parameters of the reject template * The six banners described above: ** [[Template:AfC submission/pending|/pending]] ** [[Template:AfC submission/declined|/declined]] ** [[Template:AfC submission/rejected|/rejected]] ** [[Template:AfC submission/reviewing|/reviewing]] ** [[Template:AfC submission/draft|/draft]] ** [[Template:AfC submission/created|/created]] * [[Template:AfC submission/misplaced|/misplaced]] – the code used for submissions created in any namespace other than ''Draft'' * [[Template:AfC submission/doc|/doc]] – the documentation you are reading now * [[Template:AfC submission/submit|/submit]] – code for new submissions * [[Template:AfC submission/draftnew|/draftnew]] – code for new draft submissions * Preload templates: ** [[Template:AfC submission/banner preload|/banner preload]] ** [[Template:AfC submission/banner preload (bio)|/banner preload (bio)]] ** [[Template:AfC submission/user talk preload|/user talk preload]] ** [[Template:AfC submission/user talk preload (hold)|/user talk preload (hold)]] ** [[Template:AfC submission/declined/HD_preload|/declined/HD_preload]] * Editnotice templates: ** [[Template:AfC submission/banner editintro|/banner editintro]] ** [[Template:AfC submission/user talk editintro|/user talk editintro]] ** [[Template:AfC submission/user talk editintro (hold)|/user talk editintro (hold)]] == TemplateData == {{TemplateData header}} <templatedata> { "params": { "1": {}, "2": { "label": "comment", "description": "reason the submission was declined", "example": "nn", "suggestedvalues": [ "nn", "reason", "neo", "web", "prof", "academic", "athlete", "sport", "music", "band", "film", "book", "event", "corp", "org", "bio", "list", "med", "geo", "species", "astro", "number", "creative", "school", "cat", "blank", "notenglish", "test", "redirect", "van", "ilc", "blp", "cv", "source", "rs", "not", "news", "oneevent", "dict", "plot", "joke", "ai", "llm", "resume", "ecr", "nosource", "essay", "npov", "advert", "spam", "exists", "duplicate", "context", "mergeto", "afd" ], "suggested": true }, "3": {}, "reject": {}, "ns": {}, "ts": {}, "a": {}, "u": {}, "details": {}, "reason2": {}, "details2": {}, "small": {}, "decliner": {}, "declinets": {}, "type": {}, "demo": {} } } </templatedata> == See also == * Templates for user talk pages: ** {{tl|AfC talk}}, for accepted submissions ** {{tl|Afc decline}} and {{tl|Afc reject}}, for declined/rejected submissions * {{tl|AfC date category}} <includeonly>{{Sandbox other|| [[Category:Draft namespace templates]] [[Category:WikiProject Articles for creation templates]] [[Category:Lua-based templates]] }}</includeonly> 70cfsg8lampqhtjkdcltmfjts8s9vg5 Template:AfC submission 10 122525 740068 584334 2025-11-27T04:04:55Z en>Sohom Datta 0 per talk page 740068 wikitext text/x-wiki {{SAFESUBST:<noinclude />#invoke:Unsubst||$B=<includeonly>__NONEWSECTIONLINK__</includeonly>{{AfC submission/{{#if:{{NAMESPACE}} |{{#switch:{{ucfirst:{{{1|}}}}} |R = reviewing |D = {{#if:{{{reject|}}}|rejected|declined}} |T = draft |#default = pending }} |{{#if:{{#ifeq:{{REVISIONID}}|{{REVISIONID:{{FULLPAGENAME}}}}|0|}}{{#ifeq:{{ucfirst:{{{1|}}}}}|D||0}} |{{#ifeq:{{{ns}}}|0 |misplaced |created }} |declined }} }} |submit={{{ts|}}} |author={{{a|}}} |user={{{u|}}} |reason={{{2|}}} |details={{{3|{{{details|}}}}}} |reason2={{{reason2|}}} |details2={{{details2|}}} |small={{{small|}}} |decliner={{{decliner|}}} |declinets={{{declinets|}}} |ts={{{ts|}}} |type={{{type}}} |1={{{2|}}} |2={{{3|}}} |demo={{{demo|}}} }}<includeonly>{{#if:{{{demo|}}}|| {{#ifeq:{{ucfirst:{{{1|}}}}}|T|| [[Category:{{AfC date category|ts={{{ts|}}}}}|{{If then show|{{SUBPAGENAME}}|{{PAGENAME}}}}]] {{#invoke:AfC submission catcheck|checkforcats}} }}}}<!--IP tracking-->{{#switch: {{IsIPAddress|{{{u}}}}}|4|6={{#switch:{{ucfirst:{{{1|}}}}} |R = |D = {{#if:{{{reject|}}}|[[Category:Rejected draft pages submitted for review by an IP]]|[[Category:Declined draft pages submitted for review by an IP]]}} |T = [[Category:Unsubmitted draft pages created by an IP]] |#default = [[Category:Draft pages submitted for review by an IP]] }} }}</includeonly>__NOINDEX__}}<noinclude> {{Documentation}} </noinclude> 2elue6szvkmdloc6lm1zz77pvvbwsse 740069 740068 2026-05-01T19:28:17Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740068 wikitext text/x-wiki {{SAFESUBST:<noinclude />#invoke:Unsubst||$B=<includeonly>__NONEWSECTIONLINK__</includeonly>{{AfC submission/{{#if:{{NAMESPACE}} |{{#switch:{{ucfirst:{{{1|}}}}} |R = reviewing |D = {{#if:{{{reject|}}}|rejected|declined}} |T = draft |#default = pending }} |{{#if:{{#ifeq:{{REVISIONID}}|{{REVISIONID:{{FULLPAGENAME}}}}|0|}}{{#ifeq:{{ucfirst:{{{1|}}}}}|D||0}} |{{#ifeq:{{{ns}}}|0 |misplaced |created }} |declined }} }} |submit={{{ts|}}} |author={{{a|}}} |user={{{u|}}} |reason={{{2|}}} |details={{{3|{{{details|}}}}}} |reason2={{{reason2|}}} |details2={{{details2|}}} |small={{{small|}}} |decliner={{{decliner|}}} |declinets={{{declinets|}}} |ts={{{ts|}}} |type={{{type}}} |1={{{2|}}} |2={{{3|}}} |demo={{{demo|}}} }}<includeonly>{{#if:{{{demo|}}}|| {{#ifeq:{{ucfirst:{{{1|}}}}}|T|| [[Category:{{AfC date category|ts={{{ts|}}}}}|{{If then show|{{SUBPAGENAME}}|{{PAGENAME}}}}]] {{#invoke:AfC submission catcheck|checkforcats}} }}}}<!--IP tracking-->{{#switch: {{IsIPAddress|{{{u}}}}}|4|6={{#switch:{{ucfirst:{{{1|}}}}} |R = |D = {{#if:{{{reject|}}}|[[Category:Rejected draft pages submitted for review by an IP]]|[[Category:Declined draft pages submitted for review by an IP]]}} |T = [[Category:Unsubmitted draft pages created by an IP]] |#default = [[Category:Draft pages submitted for review by an IP]] }} }}</includeonly>__NOINDEX__}}<noinclude> {{Documentation}} </noinclude> 2elue6szvkmdloc6lm1zz77pvvbwsse Template:AfC submission/helptools 10 122528 740080 584320 2025-08-21T19:57:59Z en>Novem Linguae 0 talk page edit request - removing double bold 740080 wikitext text/x-wiki {{#ifeq:{{{help|}}}|no|| ---- {{hidden|Where to get help| * If you need help '''editing or submitting your draft''', please {{Plain link|url={{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}}|name='''ask us a question'''}} at the AfC Help Desk or get '''[[Wikipedia:IRC help disclaimer|live help]]''' from experienced editors. These venues are only for help with editing and the submission process, not to get reviews. * If you need '''feedback on your draft''', or if the review is taking a lot of time, you can try asking for help on the [[Help:Talk pages|talk page]] of a [[Wikipedia:WikiProject#Finding a project|relevant WikiProject]]. Some WikiProjects are more active than others so a speedy reply is not guaranteed. }}}} {{#ifeq:{{{improve|}}}|no|| {{hidden|How to improve a draft| {{columns-list|colwidth=36em| *[[Wikipedia:Contributing to Wikipedia]] – a basic overview on how to edit Wikipedia. *[[Help:Wikitext]] – how to use the markup *[[Help:Referencing for beginners]] – how to include references *[[Wikipedia:Article development]] – how to develop your article *[[Wikipedia:Writing better articles]] – how to improve your article *[[Wikipedia:Verifiability]] – make sure your article includes [[Wikipedia:Reliable sources|reliable]] [[Wikipedia:Independent sources|third-party sources]] }} You can also browse [[Wikipedia:Featured articles]] and [[Wikipedia:Good articles]] to find examples of Wikipedia's best writing on topics similar to your proposed article. }}}} {{#ifeq:{{{speedyreviews|}}}|no|| {{hidden|Improving your odds of a speedy review|2= To improve your odds of a faster review, tag your draft with relevant [[WP:WikiProject|WikiProject]] tags using the button below. This will let reviewers know a new draft has been submitted in their area of interest. For instance, if you wrote about a female astronomer, you would want to add the ''Biography'', ''Astronomy'', and ''Women scientists'' tags. <div style="text-align: center;">{{Clickable button|url=https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Articles_for_creation/Add_WikiProject_tags?withJS=MediaWiki:AFC-add-project-tags.js&title={{urlencode:{{FULLPAGENAME}}|WIKI}} |Add tags to your draft|color=blue|class=mw-ui-progressive}}</div> }}}} {{#ifeq:{{{resources|}}}|no|| {{hidden|Editor resources| *{{automated tools}} }}}}<noinclude>{{documentation}}</noinclude> 8d9f8c56u4turlrymmthil9oehr2ci5 740081 740080 2026-05-01T19:28:19Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/helptools]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740080 wikitext text/x-wiki {{#ifeq:{{{help|}}}|no|| ---- {{hidden|Where to get help| * If you need help '''editing or submitting your draft''', please {{Plain link|url={{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}}|name='''ask us a question'''}} at the AfC Help Desk or get '''[[Wikipedia:IRC help disclaimer|live help]]''' from experienced editors. These venues are only for help with editing and the submission process, not to get reviews. * If you need '''feedback on your draft''', or if the review is taking a lot of time, you can try asking for help on the [[Help:Talk pages|talk page]] of a [[Wikipedia:WikiProject#Finding a project|relevant WikiProject]]. Some WikiProjects are more active than others so a speedy reply is not guaranteed. }}}} {{#ifeq:{{{improve|}}}|no|| {{hidden|How to improve a draft| {{columns-list|colwidth=36em| *[[Wikipedia:Contributing to Wikipedia]] – a basic overview on how to edit Wikipedia. *[[Help:Wikitext]] – how to use the markup *[[Help:Referencing for beginners]] – how to include references *[[Wikipedia:Article development]] – how to develop your article *[[Wikipedia:Writing better articles]] – how to improve your article *[[Wikipedia:Verifiability]] – make sure your article includes [[Wikipedia:Reliable sources|reliable]] [[Wikipedia:Independent sources|third-party sources]] }} You can also browse [[Wikipedia:Featured articles]] and [[Wikipedia:Good articles]] to find examples of Wikipedia's best writing on topics similar to your proposed article. }}}} {{#ifeq:{{{speedyreviews|}}}|no|| {{hidden|Improving your odds of a speedy review|2= To improve your odds of a faster review, tag your draft with relevant [[WP:WikiProject|WikiProject]] tags using the button below. This will let reviewers know a new draft has been submitted in their area of interest. For instance, if you wrote about a female astronomer, you would want to add the ''Biography'', ''Astronomy'', and ''Women scientists'' tags. <div style="text-align: center;">{{Clickable button|url=https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Articles_for_creation/Add_WikiProject_tags?withJS=MediaWiki:AFC-add-project-tags.js&title={{urlencode:{{FULLPAGENAME}}|WIKI}} |Add tags to your draft|color=blue|class=mw-ui-progressive}}</div> }}}} {{#ifeq:{{{resources|}}}|no|| {{hidden|Editor resources| *{{automated tools}} }}}}<noinclude>{{documentation}}</noinclude> 8d9f8c56u4turlrymmthil9oehr2ci5 Template:AfC submission/pending 10 122529 740070 487129 2026-03-31T07:56:16Z en>WOSlinker 0 fix lint issues 740070 wikitext text/x-wiki {{ombox | templatestyles = AfC submission/styles.css | class = afc-submission-pending | type = notice | image =[[Image:AFC-Logo.svg|center|75px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Review waiting, please be patient.</span></div> {{AfC status/age}} There are {{AfC status/backlog}} [[:Category:Pending AfC submissions|waiting for review]]. ---- * If the submission is '''accepted''', then this page will be moved into the article space.<br> * If the submission is '''declined''', then the reason will be posted here.<br/> * In the meantime, you can continue to improve this submission by editing normally. {{AfC submission/helptools}} {{AfC submission/tools |submit={{{submit|}}} |author={{{author|}}} |user={{{user|}}} }} }}{{#if:{{{demo|}}}||{{#ifeq:{{ROOTPAGENAME}}|AfC submission| |{{NOINDEX}}[[Category:Pending AfC submissions|P{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]] [[Category:{{AfC age category|ts={{{submit|}}}|status=pending}}|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]] {{#ifexist:{{SUBPAGENAME}}|{{#ifeq:{{lc:{{SUBPAGENAME}}}}|sandbox||[[Category:AfC submissions with the same name as existing articles|{{SUBPAGENAME}}]]}}}} }}<!-- See https://en.wikipedia.org/w/index.php?title=Wikipedia_talk%3AWikiProject_Articles_for_creation&diff=548201182&oldid=548180058 -->{{#ifeq:{{{type|}}}|template|[[Category:Pending template and disambiguation AfC submissions|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]]}} {{#ifeq:{{{type|}}}|dab|[[Category:Pending template and disambiguation AfC submissions|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]]}} {{#ifeq:{{NAMESPACE}}|{{ns:2}}|[[Category:Pending AfC submissions in userspace]]}}{{#ifexpr:{{PAGESIZE:{{FULLPAGENAME}}|R}}<450|[[Category:Pending AfC submissions less than 450 bytes long]]|}}{{#ifeq:{{NAMESPACE}}|{{ns:10}}||{{#ifeq:{{NUMBEROFSECTIONS}}|0|[[Category:Pending AfC submissions without a section]]}}}}}}<noinclude> {{documentation|Template:AfC_submission/doc}} </noinclude> cs5ci3b6dopfhasi6vldefsdzvakfd8 740071 740070 2026-05-01T19:28:18Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/pending]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740070 wikitext text/x-wiki {{ombox | templatestyles = AfC submission/styles.css | class = afc-submission-pending | type = notice | image =[[Image:AFC-Logo.svg|center|75px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Review waiting, please be patient.</span></div> {{AfC status/age}} There are {{AfC status/backlog}} [[:Category:Pending AfC submissions|waiting for review]]. ---- * If the submission is '''accepted''', then this page will be moved into the article space.<br> * If the submission is '''declined''', then the reason will be posted here.<br/> * In the meantime, you can continue to improve this submission by editing normally. {{AfC submission/helptools}} {{AfC submission/tools |submit={{{submit|}}} |author={{{author|}}} |user={{{user|}}} }} }}{{#if:{{{demo|}}}||{{#ifeq:{{ROOTPAGENAME}}|AfC submission| |{{NOINDEX}}[[Category:Pending AfC submissions|P{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]] [[Category:{{AfC age category|ts={{{submit|}}}|status=pending}}|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]] {{#ifexist:{{SUBPAGENAME}}|{{#ifeq:{{lc:{{SUBPAGENAME}}}}|sandbox||[[Category:AfC submissions with the same name as existing articles|{{SUBPAGENAME}}]]}}}} }}<!-- See https://en.wikipedia.org/w/index.php?title=Wikipedia_talk%3AWikiProject_Articles_for_creation&diff=548201182&oldid=548180058 -->{{#ifeq:{{{type|}}}|template|[[Category:Pending template and disambiguation AfC submissions|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]]}} {{#ifeq:{{{type|}}}|dab|[[Category:Pending template and disambiguation AfC submissions|{{#if:{{{submit|}}} |{{#expr:trunc({{{submit}}}/100)}}|3 }}]]}} {{#ifeq:{{NAMESPACE}}|{{ns:2}}|[[Category:Pending AfC submissions in userspace]]}}{{#ifexpr:{{PAGESIZE:{{FULLPAGENAME}}|R}}<450|[[Category:Pending AfC submissions less than 450 bytes long]]|}}{{#ifeq:{{NAMESPACE}}|{{ns:10}}||{{#ifeq:{{NUMBEROFSECTIONS}}|0|[[Category:Pending AfC submissions without a section]]}}}}}}<noinclude> {{documentation|Template:AfC_submission/doc}} </noinclude> cs5ci3b6dopfhasi6vldefsdzvakfd8 Template:AfC submission/tools 10 122530 740072 584336 2026-03-31T05:17:38Z en>Oshwah 0 Small grammar and punctuation on warning messages. 740072 wikitext text/x-wiki ---- {{hidden|Reviewer tools|class=afc-reviewer-tools-collapse|style={{#if:{{#switch:{{FULLPAGENAME}}|Wikipedia:Articles for creation/{{SUBPAGENAME}}|Draft:{{SUBPAGENAME}}|Wikipedia talk:Articles for creation/{{SUBPAGENAME}}=|#default=yes}}{{#if:{{{submit|}}}|yes|}}{{#ifexist:{{{SUBPAGENAME|}}}|yes|}}|padding-bottom: 0;|}}|2= *<span id="afc-reviewer-tools" class="plainlinks">[[Wikipedia:WikiProject Articles for creation/Reviewing instructions|Instructions]]<!-- -->{{·}}[[Special:WhatLinksHere/{{SUBPAGENAME}}|What links here]]<!-- -->{{·}}[[:{{SUBPAGENAME}}|{{SUBPAGENAME}}]]<!-- -->&nbsp;([[{{TALKPAGENAME:{{SUBPAGENAME}}}}|talk]]:<!-- -->&nbsp;[{{fullurl:{{TALKPAGENAME:{{SUBPAGENAME}}}}|action=edit&editintro=Template:AfC_submission/banner_editintro&preload=Template:AfC_submission/banner_preload}} +]<!-- -->{{·}}[{{fullurl:{{TALKPAGENAME:{{SUBPAGENAME}}}}|action=edit&editintro=Template:AfC_submission/banner_editintro&preload=Template:AfC_submission/banner_preload_(bio)}} bio])<!-- -->{{·}}([{{fullurl:index.php|title=Special:Log&page={{SUBPAGENAMEE}}}} log])<!-- -->{{·}}{{Copyvios||{{FULLPAGENAME}}}}<!-- -->{{·}}[https://refill.toolforge.org/result.php?page={{FULLPAGENAMEE}}&defaults=y reFill]<!-- -->{{·}}[https://citations.toolforge.org/process_page.php?edit=toolbar&slow=1&user=&page={{FULLPAGENAMEE}} Citation Bot]<!-- -->{{·}}(Search:&nbsp;[[google:{{urlencode:{{SUBPAGENAME}}}}|Google]],<!-- -->&nbsp;[{{fullurl:Special:Search|search={{urlencode:{{SUBPAGENAME}}}}&fulltext=Search}} Wikipedia])<!-- -->{{·}}Submitted {{#if:{{{submit|}}}|{{time ago|{{{submit|}}}}}|???}}<!-- -->{{#if:{{{user|}}}|&nbsp;by [[Special:Contributions/{{{user}}}|{{{user}}}]]&nbsp;([[User talk:{{{user}}}|talk]]:<!-- -->&nbsp;[{{fullurl:User talk:{{{user}}}|action=edit&editintro=Template:AfC_submission/user_talk_editintro_(declined)&preload=Template:AfC_submission/user_talk_preload_(declined)&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq:{{TALKSPACE}}|{{ns:Project talk}}|: {{SUBPAGENAME}}}}}}&section=new}} D]<!-- -->{{·}}[{{fullurl:User talk:{{{user}}}|action=edit&editintro=Template:AfC_submission/user_talk_editintro&preload=Template:AfC_submission/user_talk_preload&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq:{{TALKSPACE}}|{{ns:Project talk}}|: {{SUBPAGENAME}}}}}}&section=new}} +]<!-- -->) }}<!-- -->{{#if:{{{author|}}} |&nbsp;by {{{author}}} }}<!-- -->{{·}}Last edited {{time ago|{{REVISIONTIMESTAMP}}}}{{#if:{{REVISIONUSER}}|&nbsp;by [[User:{{REVISIONUSER}}|{{REVISIONUSER}}]]}}</span>}}<!-- -->{{#switch:{{FULLPAGENAME}} |Draft:{{SUBPAGENAME}} |Draft talk:{{SUBPAGENAME}} |Wikipedia:Articles for creation/{{SUBPAGENAME}} |Wikipedia talk:Articles for creation/{{SUBPAGENAME}}= |#default={{#ifexist:Draft:{{ucfirst:{{SUBPAGENAME}}}}| *<small>'''Warning:''' This page should probably be moved{{#ifeq:{{ucfirst:{{SUBPAGENAME}}}}|Sandbox|&nbsp;to the Draft namespace|, but a page already exists at [[Draft:{{ucfirst:{{SUBPAGENAME}}}}]]}}.</small><!-- --><inputbox> type=move page={{FULLPAGENAME}} prefix=Draft: {{#ifeq:{{ucfirst:{{SUBPAGENAME}}}}|Sandbox||default={{ucfirst:{{SUBPAGENAME}}}}}} placeholder=Name of the article (without the prefix) summary=Preferred location for [[WP:AFC|AfC]] submissions buttonlabel=Move to Draft space break=no </inputbox>| *<small>'''Warning:''' This page should probably be located at [[Draft:{{ucfirst:{{SUBPAGENAME}}}}]] (<span class="plainlinks">[{{fullurl:index.php|title=Special:MovePage/{{FULLPAGENAMEE}}&wpNewTitle=Draft:{{SUBPAGENAMEE}}&wpReason={{urlencode:Preferred location for [[WP:AFC|AfC]] submissions}}}} move]</span>).</small>}}}}<!-- -->{{#if:{{{submit|}}}| | *<small>'''Warning:''' This submission is not timestamped. Please replace this template with <span style="white-space:nowrap; font-family: monospace"><nowiki>{{subst:submit}}</nowiki></span>.</small> |<!-- Not Submitted-->}}<!-- -->{{#ifexist:{{SUBPAGENAME}}|{{#ifeq:{{lc:{{SUBPAGENAME}}}}|sandbox|| *<small>'''Warning:''' The page {{No redirect|{{SUBPAGENAME}}}} {{#ifeq:{{#invoke:redirect|isRedirect|{{SUBPAGENAME}}}}|yes|redirects to {{#invoke:redirect|main|{{SUBPAGENAME}}|bracket=yes}}|already exists}}. Please [[Wikipedia:Administrators' guide/Fixing cut-and-paste moves|ensure it is not a copy]] or that this page is located at the correct title.</small>}}|}}<!-- -->{{#if:{{#invoke:redirect|isRedirect|User talk:{{{user|Example}}}}}| *<small>'''Warning:''' [[User:{{{user|Example}}}|The user]] who submitted this draft may have been renamed. Please [https://en.wikipedia.org/wiki/Special:Log?type=renameuser&page=User:{{#invoke:String|replace|{{{user|Example}}}| |_}} verify this] and adjust the submission template if necessary before reviewing.</small>[[Category:AfC submissions with a potentially renamed submitter]] }}<noinclude> {{documentation}} </noinclude> 2glyu60x42ah89xlw42x5m0il3021y3 740073 740072 2026-05-01T19:28:18Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/tools]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740072 wikitext text/x-wiki ---- {{hidden|Reviewer tools|class=afc-reviewer-tools-collapse|style={{#if:{{#switch:{{FULLPAGENAME}}|Wikipedia:Articles for creation/{{SUBPAGENAME}}|Draft:{{SUBPAGENAME}}|Wikipedia talk:Articles for creation/{{SUBPAGENAME}}=|#default=yes}}{{#if:{{{submit|}}}|yes|}}{{#ifexist:{{{SUBPAGENAME|}}}|yes|}}|padding-bottom: 0;|}}|2= *<span id="afc-reviewer-tools" class="plainlinks">[[Wikipedia:WikiProject Articles for creation/Reviewing instructions|Instructions]]<!-- -->{{·}}[[Special:WhatLinksHere/{{SUBPAGENAME}}|What links here]]<!-- -->{{·}}[[:{{SUBPAGENAME}}|{{SUBPAGENAME}}]]<!-- -->&nbsp;([[{{TALKPAGENAME:{{SUBPAGENAME}}}}|talk]]:<!-- -->&nbsp;[{{fullurl:{{TALKPAGENAME:{{SUBPAGENAME}}}}|action=edit&editintro=Template:AfC_submission/banner_editintro&preload=Template:AfC_submission/banner_preload}} +]<!-- -->{{·}}[{{fullurl:{{TALKPAGENAME:{{SUBPAGENAME}}}}|action=edit&editintro=Template:AfC_submission/banner_editintro&preload=Template:AfC_submission/banner_preload_(bio)}} bio])<!-- -->{{·}}([{{fullurl:index.php|title=Special:Log&page={{SUBPAGENAMEE}}}} log])<!-- -->{{·}}{{Copyvios||{{FULLPAGENAME}}}}<!-- -->{{·}}[https://refill.toolforge.org/result.php?page={{FULLPAGENAMEE}}&defaults=y reFill]<!-- -->{{·}}[https://citations.toolforge.org/process_page.php?edit=toolbar&slow=1&user=&page={{FULLPAGENAMEE}} Citation Bot]<!-- -->{{·}}(Search:&nbsp;[[google:{{urlencode:{{SUBPAGENAME}}}}|Google]],<!-- -->&nbsp;[{{fullurl:Special:Search|search={{urlencode:{{SUBPAGENAME}}}}&fulltext=Search}} Wikipedia])<!-- -->{{·}}Submitted {{#if:{{{submit|}}}|{{time ago|{{{submit|}}}}}|???}}<!-- -->{{#if:{{{user|}}}|&nbsp;by [[Special:Contributions/{{{user}}}|{{{user}}}]]&nbsp;([[User talk:{{{user}}}|talk]]:<!-- -->&nbsp;[{{fullurl:User talk:{{{user}}}|action=edit&editintro=Template:AfC_submission/user_talk_editintro_(declined)&preload=Template:AfC_submission/user_talk_preload_(declined)&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq:{{TALKSPACE}}|{{ns:Project talk}}|: {{SUBPAGENAME}}}}}}&section=new}} D]<!-- -->{{·}}[{{fullurl:User talk:{{{user}}}|action=edit&editintro=Template:AfC_submission/user_talk_editintro&preload=Template:AfC_submission/user_talk_preload&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq:{{TALKSPACE}}|{{ns:Project talk}}|: {{SUBPAGENAME}}}}}}&section=new}} +]<!-- -->) }}<!-- -->{{#if:{{{author|}}} |&nbsp;by {{{author}}} }}<!-- -->{{·}}Last edited {{time ago|{{REVISIONTIMESTAMP}}}}{{#if:{{REVISIONUSER}}|&nbsp;by [[User:{{REVISIONUSER}}|{{REVISIONUSER}}]]}}</span>}}<!-- -->{{#switch:{{FULLPAGENAME}} |Draft:{{SUBPAGENAME}} |Draft talk:{{SUBPAGENAME}} |Wikipedia:Articles for creation/{{SUBPAGENAME}} |Wikipedia talk:Articles for creation/{{SUBPAGENAME}}= |#default={{#ifexist:Draft:{{ucfirst:{{SUBPAGENAME}}}}| *<small>'''Warning:''' This page should probably be moved{{#ifeq:{{ucfirst:{{SUBPAGENAME}}}}|Sandbox|&nbsp;to the Draft namespace|, but a page already exists at [[Draft:{{ucfirst:{{SUBPAGENAME}}}}]]}}.</small><!-- --><inputbox> type=move page={{FULLPAGENAME}} prefix=Draft: {{#ifeq:{{ucfirst:{{SUBPAGENAME}}}}|Sandbox||default={{ucfirst:{{SUBPAGENAME}}}}}} placeholder=Name of the article (without the prefix) summary=Preferred location for [[WP:AFC|AfC]] submissions buttonlabel=Move to Draft space break=no </inputbox>| *<small>'''Warning:''' This page should probably be located at [[Draft:{{ucfirst:{{SUBPAGENAME}}}}]] (<span class="plainlinks">[{{fullurl:index.php|title=Special:MovePage/{{FULLPAGENAMEE}}&wpNewTitle=Draft:{{SUBPAGENAMEE}}&wpReason={{urlencode:Preferred location for [[WP:AFC|AfC]] submissions}}}} move]</span>).</small>}}}}<!-- -->{{#if:{{{submit|}}}| | *<small>'''Warning:''' This submission is not timestamped. Please replace this template with <span style="white-space:nowrap; font-family: monospace"><nowiki>{{subst:submit}}</nowiki></span>.</small> |<!-- Not Submitted-->}}<!-- -->{{#ifexist:{{SUBPAGENAME}}|{{#ifeq:{{lc:{{SUBPAGENAME}}}}|sandbox|| *<small>'''Warning:''' The page {{No redirect|{{SUBPAGENAME}}}} {{#ifeq:{{#invoke:redirect|isRedirect|{{SUBPAGENAME}}}}|yes|redirects to {{#invoke:redirect|main|{{SUBPAGENAME}}|bracket=yes}}|already exists}}. Please [[Wikipedia:Administrators' guide/Fixing cut-and-paste moves|ensure it is not a copy]] or that this page is located at the correct title.</small>}}|}}<!-- -->{{#if:{{#invoke:redirect|isRedirect|User talk:{{{user|Example}}}}}| *<small>'''Warning:''' [[User:{{{user|Example}}}|The user]] who submitted this draft may have been renamed. Please [https://en.wikipedia.org/wiki/Special:Log?type=renameuser&page=User:{{#invoke:String|replace|{{{user|Example}}}| |_}} verify this] and adjust the submission template if necessary before reviewing.</small>[[Category:AfC submissions with a potentially renamed submitter]] }}<noinclude> {{documentation}} </noinclude> 2glyu60x42ah89xlw42x5m0il3021y3 Module:Transclusion count/data/A 828 122533 740063 732755 2026-04-26T05:09:46Z en>Ahechtbot 0 [[Wikipedia:BOT|Bot]]: Updated page. 740063 Scribunto text/plain return { ["A-Class"] = 5400, ["ACArt"] = 3800, ["AFB_game_box_end"] = 2500, ["AFB_game_box_start"] = 2500, ["AFB_game_box_start/styles.css"] = 2500, ["AFC_comment"] = 23000, ["AFC_submission"] = 32000, ["AFC_submission_category_header"] = 4600, ["AFD_help"] = 165000, ["AFD_help/styles.css"] = 165000, ["AFI/Picture_box/show_picture"] = 4100, ["AFI_film"] = 9900, ["AFL"] = 2100, ["AFL_Car"] = 2600, ["AFL_Col"] = 2500, ["AFL_Ess"] = 2600, ["AFL_Gee"] = 2600, ["AFL_Haw"] = 2400, ["AFL_Mel"] = 2700, ["AFL_NM"] = 2200, ["AFL_Ric"] = 2500, ["AFL_StK"] = 2700, ["AFL_Tables"] = 12000, ["AFL_Year"] = 2800, ["AFL_player"] = 2100, ["AI-generated"] = 7200, ["ALG"] = 2600, ["AMARB"] = 4400, ["AM_station_data"] = 4400, ["ARBPIA"] = 2900, ["ARE"] = 2200, ["ARG"] = 6900, ["ARM"] = 2100, ["ASIN"] = 5000, ["ASN"] = 3400, ["ATP"] = 5200, ["AUS"] = 15000, ["AUT"] = 10000, ["AZE"] = 2800, ["A_note"] = 5700, ["A_or_an"] = 12000, ["Aan"] = 61000, ["Abbr"] = 963000, ["Abbreviation"] = 2900, ["Abbrlink"] = 16000, ["Abot"] = 29000, ["About"] = 169000, ["Absolute_page_title"] = 4500, ["Acad"] = 6700, ["Access_icon"] = 3300, ["According_to_whom"] = 4700, ["AchievementTable"] = 11000, ["AdSenseSummary"] = 12000, ["Added"] = 2800, ["Adjacent_communities"] = 29000, ["Adjacent_stations"] = 42000, ["Adjacent_stations/styles.css"] = 42000, ["Adjacent_stations_doc"] = 2400, ["Adjstn"] = 2700, ["Admin"] = 15000, ["Admin_help/helped"] = 2200, ["Administrator_note"] = 6800, ["Administrators'_noticeboard_archives_all"] = 2100, ["Administrators'_noticeboard_navbox_all"] = 2100, ["Adminnote"] = 3600, ["Advert"] = 9500, ["Aet"] = 6900, ["AfC_accept/C_percentage"] = 6200, ["AfC_age_category"] = 4400, ["AfC_comment"] = 23000, ["AfC_date_category"] = 263000, ["AfC_status/age"] = 4400, ["AfC_status/backlog"] = 5000, ["AfC_submission"] = 44000, ["AfC_submission/comments"] = 30000, ["AfC_submission/declined"] = 30000, ["AfC_submission/draft"] = 13000, ["AfC_submission/helptools"] = 43000, ["AfC_submission/pending"] = 4400, ["AfC_submission/styles.css"] = 45000, ["AfC_submission/tools"] = 4400, ["AfC_submission_category_header"] = 6700, ["AfC_submission_category_header/day"] = 6400, ["AfC_submission_category_header/td"] = 6400, ["AfC_talk/C_percentage"] = 6200, ["AfC_topic"] = 30000, ["AfD_categories_horizontal_shortnames"] = 5300, ["AfD_count_link"] = 4100, ["Afd-merged-from"] = 10000, ["Afd_bottom/old"] = 412000, ["Afd_top/old"] = 412000, ["Afd_top/old/styles.css"] = 412000, ["Africa_topic"] = 6600, ["After_extra_time"] = 6900, ["Age"] = 29000, ["Age_in_days"] = 5600, ["Age_in_years"] = 4100, ["Age_in_years,_months,_weeks_and_days"] = 5300, ["Age_in_years,_months_and_days"] = 20000, ["Age_in_years_and_days"] = 5000, ["Age_in_years_and_days_nts"] = 4600, ["Agree"] = 2500, ["Ahnentafel"] = 7800, ["Ahnentafel/styles.css"] = 7800, ["Air_Force_Historical_Research_Agency"] = 4400, ["Air_force"] = 7400, ["Air_force/core"] = 7400, ["Aircontent"] = 9500, ["Aircraft_specs"] = 12000, ["Aircraft_specs/convert"] = 12000, ["Aircraft_specs/eng"] = 12000, ["Aircraft_specs/length"] = 12000, ["Aircraft_specs/range"] = 12000, ["Aircraft_specs/speed"] = 12000, ["Airport-dest-list"] = 3400, ["Airport_codes"] = 13000, ["Airport_destination_list"] = 4800, ["Al"] = 80000, ["Album"] = 2900, ["Album_chart"] = 36000, ["Album_cover_fur"] = 53000, ["Album_label_category"] = 2300, ["Album_label_category/core"] = 2300, ["Album_ratings"] = 30000, ["Album_reviews"] = 2400, ["Albums_category"] = 25000, ["Albums_category/core"] = 25000, ["Albums_category/type/default"] = 25000, ["Align"] = 204000, ["Aligned_table"] = 14000, ["AllIrelandByCountyCatNav"] = 3200, ["AllMusic"] = 77000, ["All_Ireland_by_county_category_navigation"] = 3200, ["All_plot"] = 2100, ["Allcaps"] = 11000, ["Allcaps/styles.css"] = 11000, ["Allmusic"] = 11000, ["Allow_wrap"] = 6500, ["Already_done"] = 2800, ["Also"] = 2400, ["Also_known_as"] = 2700, ["Alternating_rows_table"] = 2400, ["Alternating_rows_table/styles.css"] = 2400, ["Alumni"] = 3000, ["Always_substitute"] = 9100, ["Ambox"] = 1480000, ["Ambox_globe"] = 43000, ["Ambox_globe_current_red"] = 41000, ["American_English"] = 23000, ["American_football_roster/Footer"] = 3800, ["American_football_roster/Header"] = 3800, ["American_football_roster/Player"] = 3800, ["Americanfootballbox"] = 4500, ["Anarchism_announcements"] = 4000, ["Anarchism_announcements/shell"] = 4000, ["Anchor"] = 94000, ["Angbr"] = 2700, ["Angbr_IPA"] = 2400, ["Angle_bracket"] = 3900, ["Anglican_navbox_titlestyle"] = 14000, ["Anglicise_rank"] = 544000, ["Animal_tasks"] = 34000, ["Anime_News_Network"] = 12000, ["Anl"] = 2200, ["Annotated_link"] = 16000, ["Anonblock"] = 35000, ["Antonym_of_(dis)establish"] = 16000, ["Apostrophe"] = 97000, ["Arbitration_Committee_candidate/data"] = 108000, ["Archive"] = 340000, ["Archive_bottom"] = 67000, ["Archive_box"] = 18000, ["Archive_list"] = 95000, ["Archive_top"] = 47000, ["Archive_top/styles.css"] = 47000, ["Archive_top_green"] = 14000, ["Archive_top_green/styles.css"] = 14000, ["Archive_top_red"] = 6500, ["Archive_top_red/styles.css"] = 6500, ["Archivebottom"] = 3700, ["Archivebox"] = 2200, ["Archives"] = 55000, ["Archivetop"] = 3700, ["Army"] = 18000, ["Army/core"] = 18000, ["Art_UK_bio"] = 2500, ["Art_UK_bio/plural"] = 2500, ["Article"] = 3000, ["ArticleHistory"] = 27000, ["Article_alerts_box"] = 4100, ["Article_alerts_box/styles.css"] = 4100, ["Article_for_improvement_banner/Picture_box"] = 4500, ["Article_for_improvement_banner/Picture_box/show_picture"] = 4100, ["Article_history"] = 52000, ["Article_links"] = 512000, ["Article_links_2"] = 80000, ["Article_or_page"] = 6600, ["Article_stub_box"] = 2370000, ["Articles_by_Importance"] = 12000, ["Articles_by_Quality"] = 38000, ["Articles_for_creation_links"] = 6800, ["As_of"] = 91000, ["As_written"] = 2500, ["Asbox"] = 2350000, ["Asia_topic"] = 11000, ["Asof"] = 6200, ["Assessed-Class"] = 18000, ["Assignment"] = 6000, ["Assignment_milestones"] = 5300, ["Association_of_Tennis_Professionals"] = 5200, ["AstDys"] = 2800, ["Asterisk"] = 3400, ["Astro_list_redirect_comment"] = 2000, ["AthAbbr"] = 7300, ["Atnhead"] = 6000, ["Atop"] = 7200, ["Atopg"] = 13000, ["Atopr"] = 6100, ["Attached_KML"] = 14000, ["Au"] = 5900, ["AuEduNewbie"] = 2600, ["Audio"] = 41000, ["Audio_sample"] = 3100, ["AustralianFootball"] = 8900, ["Australian_Dictionary_of_Biography"] = 2300, ["Australian_English"] = 3700, ["Australian_Football_League_team"] = 2100, ["Australian_dollar"] = 2200, ["Australian_party_style"] = 6700, ["Australian_politics/name"] = 4700, ["Australian_politics/party_colours"] = 6800, ["Austria_metadata_Wikidata"] = 2100, ["Austria_population_Wikidata"] = 2100, ["Aut"] = 7800, ["Authority_control"] = 2280000, ["Authority_control_(arts)"] = 16000, ["Auto_link"] = 73000, ["Autobiography"] = 2100, ["Autolink"] = 41000, ["Automated_tools"] = 89000, ["Automated_tools/core"] = 89000, ["Automatic_archive_navigator"] = 42000, ["Automatic_archives_blurb"] = 19000, ["Automatic_category_TOC"] = 856000, ["Automatic_category_TOC/core"] = 855000, ["Automatic_taxobox"] = 104000, ["Automatically_generated"] = 2000, ["Aviation_Safety_Network_accident_history"] = 3400, ["Aviation_accidents_and_incidents"] = 2700, ["Avoid_wrap"] = 6000, ["Awaiting_admin"] = 2800, ["Awaitingadmin"] = 2700, ["Award2"] = 2600, ["Awards"] = 2600, ["Awards_table"] = 6200, ["Awards_table/styles.css"] = 6200, ["Ayd"] = 4400, ["Aye"] = 14000, ["Module:A_or_an"] = 12000, ["Module:A_or_an/words"] = 12000, ["Module:About"] = 169000, ["Module:Adjacent_stations"] = 80000, ["Module:Adjacent_stations/Amtrak"] = 2900, ["Module:Adjacent_stations/Deutsche_Bahn"] = 2100, ["Module:Adjacent_stations/Indian_Railways"] = 3500, ["Module:Adjacent_stations/JR_East"] = 2600, ["Module:Adjacent_stations/i18n"] = 80000, ["Module:Administrators'_noticeboard_archives"] = 2100, ["Module:Administrators'_noticeboard_archives/styles.css"] = 2100, ["Module:AfC_submission_catcheck"] = 352000, ["Module:AfC_topic"] = 30000, ["Module:Age"] = 1400000, ["Module:Ahnentafel"] = 7800, ["Module:Airport_destination_list"] = 4800, ["Module:Aligned_dates_list"] = 2700, ["Module:Aligned_table"] = 14000, ["Module:All_Ireland_by_county_category_navigation"] = 3400, ["Module:Anchor"] = 94000, ["Module:Ancient_Egypt_era"] = 2700, ["Module:Ancient_Egypt_era/data"] = 2700, ["Module:Ancient_Egypt_kings"] = 2700, ["Module:Ancient_Egypt_kings/data"] = 2700, ["Module:Ancient_Olympiads"] = 2700, ["Module:Ancient_Olympiads/data"] = 2700, ["Module:Annotated_link"] = 16000, ["Module:Archive"] = 340000, ["Module:Archive/config"] = 340000, ["Module:Archive_list"] = 98000, ["Module:Archives/bots"] = 34000, ["Module:Arguments"] = 37100000, ["Module:Armenian"] = 2700, ["Module:Arrowverse_redirect_category_handler"] = 2100, ["Module:Article_history"] = 52000, ["Module:Article_history/Category"] = 52000, ["Module:Article_history/config"] = 52000, ["Module:Article_history/styles.css"] = 52000, ["Module:Article_stub_box"] = 2380000, ["Module:Article_stub_box/styles.css"] = 2380000, ["Module:Articles_by_class"] = 50000, ["Module:Asbox_stubtree"] = 42000, ["Module:Attached_KML"] = 14000, ["Module:Attached_KML/styles.css"] = 14000, ["Module:Australian_place_map"] = 16000, ["Module:Authority_control"] = 2300000, ["Module:Authority_control/auxiliary"] = 711000, ["Module:Authority_control/config"] = 2300000, ["Module:Auto_date_formatter"] = 15000, ["Module:Automated_taxobox"] = 494000, ["Module:Autotaxobox"] = 645000, } 6jvk6g9qarl0ua4a808kj3t50mitceb 740064 740063 2026-05-01T19:16:17Z Novem Linguae 49714 1 revision imported from [[:en:Module:Transclusion_count/data/A]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740063 Scribunto text/plain return { ["A-Class"] = 5400, ["ACArt"] = 3800, ["AFB_game_box_end"] = 2500, ["AFB_game_box_start"] = 2500, ["AFB_game_box_start/styles.css"] = 2500, ["AFC_comment"] = 23000, ["AFC_submission"] = 32000, ["AFC_submission_category_header"] = 4600, ["AFD_help"] = 165000, ["AFD_help/styles.css"] = 165000, ["AFI/Picture_box/show_picture"] = 4100, ["AFI_film"] = 9900, ["AFL"] = 2100, ["AFL_Car"] = 2600, ["AFL_Col"] = 2500, ["AFL_Ess"] = 2600, ["AFL_Gee"] = 2600, ["AFL_Haw"] = 2400, ["AFL_Mel"] = 2700, ["AFL_NM"] = 2200, ["AFL_Ric"] = 2500, ["AFL_StK"] = 2700, ["AFL_Tables"] = 12000, ["AFL_Year"] = 2800, ["AFL_player"] = 2100, ["AI-generated"] = 7200, ["ALG"] = 2600, ["AMARB"] = 4400, ["AM_station_data"] = 4400, ["ARBPIA"] = 2900, ["ARE"] = 2200, ["ARG"] = 6900, ["ARM"] = 2100, ["ASIN"] = 5000, ["ASN"] = 3400, ["ATP"] = 5200, ["AUS"] = 15000, ["AUT"] = 10000, ["AZE"] = 2800, ["A_note"] = 5700, ["A_or_an"] = 12000, ["Aan"] = 61000, ["Abbr"] = 963000, ["Abbreviation"] = 2900, ["Abbrlink"] = 16000, ["Abot"] = 29000, ["About"] = 169000, ["Absolute_page_title"] = 4500, ["Acad"] = 6700, ["Access_icon"] = 3300, ["According_to_whom"] = 4700, ["AchievementTable"] = 11000, ["AdSenseSummary"] = 12000, ["Added"] = 2800, ["Adjacent_communities"] = 29000, ["Adjacent_stations"] = 42000, ["Adjacent_stations/styles.css"] = 42000, ["Adjacent_stations_doc"] = 2400, ["Adjstn"] = 2700, ["Admin"] = 15000, ["Admin_help/helped"] = 2200, ["Administrator_note"] = 6800, ["Administrators'_noticeboard_archives_all"] = 2100, ["Administrators'_noticeboard_navbox_all"] = 2100, ["Adminnote"] = 3600, ["Advert"] = 9500, ["Aet"] = 6900, ["AfC_accept/C_percentage"] = 6200, ["AfC_age_category"] = 4400, ["AfC_comment"] = 23000, ["AfC_date_category"] = 263000, ["AfC_status/age"] = 4400, ["AfC_status/backlog"] = 5000, ["AfC_submission"] = 44000, ["AfC_submission/comments"] = 30000, ["AfC_submission/declined"] = 30000, ["AfC_submission/draft"] = 13000, ["AfC_submission/helptools"] = 43000, ["AfC_submission/pending"] = 4400, ["AfC_submission/styles.css"] = 45000, ["AfC_submission/tools"] = 4400, ["AfC_submission_category_header"] = 6700, ["AfC_submission_category_header/day"] = 6400, ["AfC_submission_category_header/td"] = 6400, ["AfC_talk/C_percentage"] = 6200, ["AfC_topic"] = 30000, ["AfD_categories_horizontal_shortnames"] = 5300, ["AfD_count_link"] = 4100, ["Afd-merged-from"] = 10000, ["Afd_bottom/old"] = 412000, ["Afd_top/old"] = 412000, ["Afd_top/old/styles.css"] = 412000, ["Africa_topic"] = 6600, ["After_extra_time"] = 6900, ["Age"] = 29000, ["Age_in_days"] = 5600, ["Age_in_years"] = 4100, ["Age_in_years,_months,_weeks_and_days"] = 5300, ["Age_in_years,_months_and_days"] = 20000, ["Age_in_years_and_days"] = 5000, ["Age_in_years_and_days_nts"] = 4600, ["Agree"] = 2500, ["Ahnentafel"] = 7800, ["Ahnentafel/styles.css"] = 7800, ["Air_Force_Historical_Research_Agency"] = 4400, ["Air_force"] = 7400, ["Air_force/core"] = 7400, ["Aircontent"] = 9500, ["Aircraft_specs"] = 12000, ["Aircraft_specs/convert"] = 12000, ["Aircraft_specs/eng"] = 12000, ["Aircraft_specs/length"] = 12000, ["Aircraft_specs/range"] = 12000, ["Aircraft_specs/speed"] = 12000, ["Airport-dest-list"] = 3400, ["Airport_codes"] = 13000, ["Airport_destination_list"] = 4800, ["Al"] = 80000, ["Album"] = 2900, ["Album_chart"] = 36000, ["Album_cover_fur"] = 53000, ["Album_label_category"] = 2300, ["Album_label_category/core"] = 2300, ["Album_ratings"] = 30000, ["Album_reviews"] = 2400, ["Albums_category"] = 25000, ["Albums_category/core"] = 25000, ["Albums_category/type/default"] = 25000, ["Align"] = 204000, ["Aligned_table"] = 14000, ["AllIrelandByCountyCatNav"] = 3200, ["AllMusic"] = 77000, ["All_Ireland_by_county_category_navigation"] = 3200, ["All_plot"] = 2100, ["Allcaps"] = 11000, ["Allcaps/styles.css"] = 11000, ["Allmusic"] = 11000, ["Allow_wrap"] = 6500, ["Already_done"] = 2800, ["Also"] = 2400, ["Also_known_as"] = 2700, ["Alternating_rows_table"] = 2400, ["Alternating_rows_table/styles.css"] = 2400, ["Alumni"] = 3000, ["Always_substitute"] = 9100, ["Ambox"] = 1480000, ["Ambox_globe"] = 43000, ["Ambox_globe_current_red"] = 41000, ["American_English"] = 23000, ["American_football_roster/Footer"] = 3800, ["American_football_roster/Header"] = 3800, ["American_football_roster/Player"] = 3800, ["Americanfootballbox"] = 4500, ["Anarchism_announcements"] = 4000, ["Anarchism_announcements/shell"] = 4000, ["Anchor"] = 94000, ["Angbr"] = 2700, ["Angbr_IPA"] = 2400, ["Angle_bracket"] = 3900, ["Anglican_navbox_titlestyle"] = 14000, ["Anglicise_rank"] = 544000, ["Animal_tasks"] = 34000, ["Anime_News_Network"] = 12000, ["Anl"] = 2200, ["Annotated_link"] = 16000, ["Anonblock"] = 35000, ["Antonym_of_(dis)establish"] = 16000, ["Apostrophe"] = 97000, ["Arbitration_Committee_candidate/data"] = 108000, ["Archive"] = 340000, ["Archive_bottom"] = 67000, ["Archive_box"] = 18000, ["Archive_list"] = 95000, ["Archive_top"] = 47000, ["Archive_top/styles.css"] = 47000, ["Archive_top_green"] = 14000, ["Archive_top_green/styles.css"] = 14000, ["Archive_top_red"] = 6500, ["Archive_top_red/styles.css"] = 6500, ["Archivebottom"] = 3700, ["Archivebox"] = 2200, ["Archives"] = 55000, ["Archivetop"] = 3700, ["Army"] = 18000, ["Army/core"] = 18000, ["Art_UK_bio"] = 2500, ["Art_UK_bio/plural"] = 2500, ["Article"] = 3000, ["ArticleHistory"] = 27000, ["Article_alerts_box"] = 4100, ["Article_alerts_box/styles.css"] = 4100, ["Article_for_improvement_banner/Picture_box"] = 4500, ["Article_for_improvement_banner/Picture_box/show_picture"] = 4100, ["Article_history"] = 52000, ["Article_links"] = 512000, ["Article_links_2"] = 80000, ["Article_or_page"] = 6600, ["Article_stub_box"] = 2370000, ["Articles_by_Importance"] = 12000, ["Articles_by_Quality"] = 38000, ["Articles_for_creation_links"] = 6800, ["As_of"] = 91000, ["As_written"] = 2500, ["Asbox"] = 2350000, ["Asia_topic"] = 11000, ["Asof"] = 6200, ["Assessed-Class"] = 18000, ["Assignment"] = 6000, ["Assignment_milestones"] = 5300, ["Association_of_Tennis_Professionals"] = 5200, ["AstDys"] = 2800, ["Asterisk"] = 3400, ["Astro_list_redirect_comment"] = 2000, ["AthAbbr"] = 7300, ["Atnhead"] = 6000, ["Atop"] = 7200, ["Atopg"] = 13000, ["Atopr"] = 6100, ["Attached_KML"] = 14000, ["Au"] = 5900, ["AuEduNewbie"] = 2600, ["Audio"] = 41000, ["Audio_sample"] = 3100, ["AustralianFootball"] = 8900, ["Australian_Dictionary_of_Biography"] = 2300, ["Australian_English"] = 3700, ["Australian_Football_League_team"] = 2100, ["Australian_dollar"] = 2200, ["Australian_party_style"] = 6700, ["Australian_politics/name"] = 4700, ["Australian_politics/party_colours"] = 6800, ["Austria_metadata_Wikidata"] = 2100, ["Austria_population_Wikidata"] = 2100, ["Aut"] = 7800, ["Authority_control"] = 2280000, ["Authority_control_(arts)"] = 16000, ["Auto_link"] = 73000, ["Autobiography"] = 2100, ["Autolink"] = 41000, ["Automated_tools"] = 89000, ["Automated_tools/core"] = 89000, ["Automatic_archive_navigator"] = 42000, ["Automatic_archives_blurb"] = 19000, ["Automatic_category_TOC"] = 856000, ["Automatic_category_TOC/core"] = 855000, ["Automatic_taxobox"] = 104000, ["Automatically_generated"] = 2000, ["Aviation_Safety_Network_accident_history"] = 3400, ["Aviation_accidents_and_incidents"] = 2700, ["Avoid_wrap"] = 6000, ["Awaiting_admin"] = 2800, ["Awaitingadmin"] = 2700, ["Award2"] = 2600, ["Awards"] = 2600, ["Awards_table"] = 6200, ["Awards_table/styles.css"] = 6200, ["Ayd"] = 4400, ["Aye"] = 14000, ["Module:A_or_an"] = 12000, ["Module:A_or_an/words"] = 12000, ["Module:About"] = 169000, ["Module:Adjacent_stations"] = 80000, ["Module:Adjacent_stations/Amtrak"] = 2900, ["Module:Adjacent_stations/Deutsche_Bahn"] = 2100, ["Module:Adjacent_stations/Indian_Railways"] = 3500, ["Module:Adjacent_stations/JR_East"] = 2600, ["Module:Adjacent_stations/i18n"] = 80000, ["Module:Administrators'_noticeboard_archives"] = 2100, ["Module:Administrators'_noticeboard_archives/styles.css"] = 2100, ["Module:AfC_submission_catcheck"] = 352000, ["Module:AfC_topic"] = 30000, ["Module:Age"] = 1400000, ["Module:Ahnentafel"] = 7800, ["Module:Airport_destination_list"] = 4800, ["Module:Aligned_dates_list"] = 2700, ["Module:Aligned_table"] = 14000, ["Module:All_Ireland_by_county_category_navigation"] = 3400, ["Module:Anchor"] = 94000, ["Module:Ancient_Egypt_era"] = 2700, ["Module:Ancient_Egypt_era/data"] = 2700, ["Module:Ancient_Egypt_kings"] = 2700, ["Module:Ancient_Egypt_kings/data"] = 2700, ["Module:Ancient_Olympiads"] = 2700, ["Module:Ancient_Olympiads/data"] = 2700, ["Module:Annotated_link"] = 16000, ["Module:Archive"] = 340000, ["Module:Archive/config"] = 340000, ["Module:Archive_list"] = 98000, ["Module:Archives/bots"] = 34000, ["Module:Arguments"] = 37100000, ["Module:Armenian"] = 2700, ["Module:Arrowverse_redirect_category_handler"] = 2100, ["Module:Article_history"] = 52000, ["Module:Article_history/Category"] = 52000, ["Module:Article_history/config"] = 52000, ["Module:Article_history/styles.css"] = 52000, ["Module:Article_stub_box"] = 2380000, ["Module:Article_stub_box/styles.css"] = 2380000, ["Module:Articles_by_class"] = 50000, ["Module:Asbox_stubtree"] = 42000, ["Module:Attached_KML"] = 14000, ["Module:Attached_KML/styles.css"] = 14000, ["Module:Australian_place_map"] = 16000, ["Module:Authority_control"] = 2300000, ["Module:Authority_control/auxiliary"] = 711000, ["Module:Authority_control/config"] = 2300000, ["Module:Auto_date_formatter"] = 15000, ["Module:Automated_taxobox"] = 494000, ["Module:Autotaxobox"] = 645000, } 6jvk6g9qarl0ua4a808kj3t50mitceb Template:AfC submission/declined 10 122547 740096 584296 2026-03-31T07:55:23Z en>WOSlinker 0 fix lint issues 740096 wikitext text/x-wiki {{Ombox | templatestyles = AfC submission/styles.css | class = afc-submission-declined | type = notice | image = none | imageright = [[File:AFC-Logo_Decline.svg|center|75px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Submission declined{{#if: {{{decliner|}}}{{{declinets|}}} | &#32;{{#if: {{{declinets|}}} | on {{#time: j F Y|{{{declinets}}}}}{{#if: {{{decliner|}}} | &#32; }} }}{{#if: {{{decliner|}}} |by {{Noping|{{{decliner}}}}} ([[User talk:{{{decliner}}}|talk]]) }} }}.</span></div>{{#if:{{NAMESPACE}}{{#ifeq:{{REVISIONID}}|{{REVISIONID:{{FULLPAGENAME}}}}||0}} |{{#if: {{{reason|}}} | <div class="afc-submission-declined-comment">{{AfC submission/comments|cat={{{cat|{{#if:{{{demo|}}}|no}}}}}|{{{reason}}}|{{{details|}}}}}</div>|<br /> }}{{#if: {{{reason2|}}} | <div class="afc-submission-declined-comment">{{AfC submission/comments|cat={{{cat|{{#if:{{{demo|}}}|no}}}}}|{{{reason2}}}|{{{details2|}}}}}</div>|<!--empty--> }}}}{{#ifeq: {{lc: {{{small|}}} }} | yes| | {{blist |1=If you would like to continue working on the submission, click on the "Edit" tab at the top of the window. |2=If you have not resolved the issues listed above, your draft will be declined again and potentially deleted. |3=If you need extra help, please {{Plain link|url={{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}}|name='''ask us a question'''}} at the AfC Help Desk or get '''[[Wikipedia:IRC help disclaimer|live help]]''' from experienced editors. |4=Please do not remove reviewer comments or this notice until the submission is accepted. }} {{#if:{{NAMESPACE}}|{{AfC submission/helptools}}|}}<includeonly>{{#ifexpr: {{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months }} < {{#time: U}}|<div class="afc-submission-g13">This draft has not been edited in over six months and qualifies to be deleted per [[WP:G13|CSD G13]].</div>{{#if:{{{demo|}}}||[[Category:G13 eligible AfC submissions|{{REVISIONTIMESTAMP}}]]}}}} {{#ifexpr: ({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +5 months}} < {{#time: U}})and({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} > {{#time: U}})|{{#if:{{{demo|}}}||[[Category:AfC G13 eligible soon submissions|{{REVISIONTIMESTAMP}}]]}}}} </includeonly> }}<small>{{#if: {{{decliner|}}} | &#32;Declined by {{Noping|{{{decliner}}}}}{{#if: {{{declinets|}}} | &#32;{{time ago|{{{declinets}}}}} }}. | &#32;The reviewer(s) who declined this submission will be listed in the [{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=history}} page history]. }}</small>{{#ifeq: {{lc: {{{small|}}} }} | yes|| <small> Last edited {{#if: {{REVISIONUSER}} | by [[User:{{encodefirst|{{REVISIONUSER}}}}|{{encodefirst|{{REVISIONUSER}}}}]] }} {{time ago|{{{revisionts|{{REVISIONTIMESTAMP}}}}}}}. {{#if: {{{user|}}} | Reviewer: [{{fullurl: User talk:{{{user}}} | action=edit&editintro=Template:AfC_submission/user_talk_editintro_declined&preload=Template:AfC_submission/user_talk_preload_declined&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq: {{TALKSPACE}} | {{ns:Project talk}} | : {{SUBPAGENAME}} }} }}&section=new}} Inform author]. }}</small>{{main other||2=<div style="border-top: 1px #AAA solid; margin-top:5px;">{{#invoke:AfC submission catcheck|submitted|This draft has been resubmitted and is currently awaiting re-review.|{{#if:{{REVISIONID}}<noinclude>0</noinclude>|<table><tr><td>{{Clickable button | Resubmit | url = {{fullurl:Wikipedia:Articles_for_creation/Submitting|withJS=MediaWiki:AFC-submit-wizard.js&page={{FULLPAGENAMEE}}}} | class = mw-ui-progressive }}</td><td>Please note that if the issues are not fixed, the draft will be declined again.</td></tr></table>|Once you save your changes using the "Publish changes" button below, you will be able to resubmit your draft for review by pressing the "Resubmit" button that will appear here.}}}}</div>}}}} }}<includeonly>{{NOINDEX}}{{user other|{{#if:{{{demo|}}}||[[Category:Declined AfC submissions in userspace]]}}}}{{#if:{{{demo|}}}||[[Category:Declined AfC submissions]]}} </includeonly><noinclude> {{documentation}} </noinclude> 12qkxdn8okhh88my61xvh4nrp39dyod 740097 740096 2026-05-01T19:32:06Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/declined]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740096 wikitext text/x-wiki {{Ombox | templatestyles = AfC submission/styles.css | class = afc-submission-declined | type = notice | image = none | imageright = [[File:AFC-Logo_Decline.svg|center|75px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Submission declined{{#if: {{{decliner|}}}{{{declinets|}}} | &#32;{{#if: {{{declinets|}}} | on {{#time: j F Y|{{{declinets}}}}}{{#if: {{{decliner|}}} | &#32; }} }}{{#if: {{{decliner|}}} |by {{Noping|{{{decliner}}}}} ([[User talk:{{{decliner}}}|talk]]) }} }}.</span></div>{{#if:{{NAMESPACE}}{{#ifeq:{{REVISIONID}}|{{REVISIONID:{{FULLPAGENAME}}}}||0}} |{{#if: {{{reason|}}} | <div class="afc-submission-declined-comment">{{AfC submission/comments|cat={{{cat|{{#if:{{{demo|}}}|no}}}}}|{{{reason}}}|{{{details|}}}}}</div>|<br /> }}{{#if: {{{reason2|}}} | <div class="afc-submission-declined-comment">{{AfC submission/comments|cat={{{cat|{{#if:{{{demo|}}}|no}}}}}|{{{reason2}}}|{{{details2|}}}}}</div>|<!--empty--> }}}}{{#ifeq: {{lc: {{{small|}}} }} | yes| | {{blist |1=If you would like to continue working on the submission, click on the "Edit" tab at the top of the window. |2=If you have not resolved the issues listed above, your draft will be declined again and potentially deleted. |3=If you need extra help, please {{Plain link|url={{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}}|name='''ask us a question'''}} at the AfC Help Desk or get '''[[Wikipedia:IRC help disclaimer|live help]]''' from experienced editors. |4=Please do not remove reviewer comments or this notice until the submission is accepted. }} {{#if:{{NAMESPACE}}|{{AfC submission/helptools}}|}}<includeonly>{{#ifexpr: {{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months }} < {{#time: U}}|<div class="afc-submission-g13">This draft has not been edited in over six months and qualifies to be deleted per [[WP:G13|CSD G13]].</div>{{#if:{{{demo|}}}||[[Category:G13 eligible AfC submissions|{{REVISIONTIMESTAMP}}]]}}}} {{#ifexpr: ({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +5 months}} < {{#time: U}})and({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} > {{#time: U}})|{{#if:{{{demo|}}}||[[Category:AfC G13 eligible soon submissions|{{REVISIONTIMESTAMP}}]]}}}} </includeonly> }}<small>{{#if: {{{decliner|}}} | &#32;Declined by {{Noping|{{{decliner}}}}}{{#if: {{{declinets|}}} | &#32;{{time ago|{{{declinets}}}}} }}. | &#32;The reviewer(s) who declined this submission will be listed in the [{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=history}} page history]. }}</small>{{#ifeq: {{lc: {{{small|}}} }} | yes|| <small> Last edited {{#if: {{REVISIONUSER}} | by [[User:{{encodefirst|{{REVISIONUSER}}}}|{{encodefirst|{{REVISIONUSER}}}}]] }} {{time ago|{{{revisionts|{{REVISIONTIMESTAMP}}}}}}}. {{#if: {{{user|}}} | Reviewer: [{{fullurl: User talk:{{{user}}} | action=edit&editintro=Template:AfC_submission/user_talk_editintro_declined&preload=Template:AfC_submission/user_talk_preload_declined&preloadtitle={{urlencode:Your submission at [[WP:AfC|Articles for creation]]{{#ifeq: {{TALKSPACE}} | {{ns:Project talk}} | : {{SUBPAGENAME}} }} }}&section=new}} Inform author]. }}</small>{{main other||2=<div style="border-top: 1px #AAA solid; margin-top:5px;">{{#invoke:AfC submission catcheck|submitted|This draft has been resubmitted and is currently awaiting re-review.|{{#if:{{REVISIONID}}<noinclude>0</noinclude>|<table><tr><td>{{Clickable button | Resubmit | url = {{fullurl:Wikipedia:Articles_for_creation/Submitting|withJS=MediaWiki:AFC-submit-wizard.js&page={{FULLPAGENAMEE}}}} | class = mw-ui-progressive }}</td><td>Please note that if the issues are not fixed, the draft will be declined again.</td></tr></table>|Once you save your changes using the "Publish changes" button below, you will be able to resubmit your draft for review by pressing the "Resubmit" button that will appear here.}}}}</div>}}}} }}<includeonly>{{NOINDEX}}{{user other|{{#if:{{{demo|}}}||[[Category:Declined AfC submissions in userspace]]}}}}{{#if:{{{demo|}}}||[[Category:Declined AfC submissions]]}} </includeonly><noinclude> {{documentation}} </noinclude> 12qkxdn8okhh88my61xvh4nrp39dyod Template:AfC submission/rejected 10 122550 740094 584346 2026-03-31T07:56:34Z en>WOSlinker 0 fix lint issues 740094 wikitext text/x-wiki {{Ombox | type = notice | class = afc-submission-rejected | templatestyles = AfC submission/styles.css | image = none | imageright = [[File:Dialog-STOP.svg|center|60px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Submission rejected{{#if: {{{decliner|}}}{{{declinets|}}} | &#32;{{#if: {{{declinets|}}} | on {{#time: j F Y|{{{declinets}}}}}{{#if: {{{decliner|}}} | &#32; }} }}{{#if: {{{decliner|}}} |by {{Noping|{{{decliner}}}}} ([[User talk:{{{decliner}}}|talk]]) }} }}.</span></div>{{#if: {{{reason|}}} |<p>{{AfC submission/reject reasons|cat={{{cat|}}}|{{{reason}}}|{{{details|}}}}}</p><!-- -->{{#if: {{{reason2|}}}|<p>{{AfC submission/reject reasons|cat={{{cat|}}}|{{{reason2}}}|{{{details2|}}}}}</p>|<!--empty-->}}|<br />| <includeonly>{{#ifexpr: {{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} < {{#time: U}}|<div class="afc-submission-g13">This draft has not been edited in over six months and qualifies to be deleted per [[WP:G13|CSD G13]].</div>{{#if:{{{demo|}}}||[[Category:G13 eligible AfC submissions|{{REVISIONTIMESTAMP}}]][[Category:Candidates for speedy deletion as abandoned drafts or AfC submissions|{{REVISIONTIMESTAMP}}]]}}}} {{#ifexpr: ({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +5 months}} < {{#time: U}})and({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} > {{#time: U}})|{{#if:{{{demo|}}}||[[Category:AfC G13 eligible soon submissions|{{REVISIONTIMESTAMP}}]]}}}} </includeonly> }}<small>{{#if: {{{decliner|}}} | &#32;Rejected by {{Noping|{{{decliner}}}}}{{#if: {{{declinets|}}} | &#32;{{time ago|{{{declinets}}}}} }}. | &#32;The reviewer(s) who rejected this submission will be listed in the [{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=history}} page history]. }}</small>{{#ifeq: {{lc: {{{small|}}} }} | yes||&nbsp;<small>Last edited {{#if: {{REVISIONUSER}} | by [[User:{{encodefirst|{{REVISIONUSER}}}}|{{encodefirst|{{REVISIONUSER}}}}]] }} {{time ago|{{{revisionts|{{REVISIONTIMESTAMP}}}}}}}. </small>{{main other||2=<div class="afc-submission-advice">{{Clickable button | Ask for advice | url = {{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}} | class = mw-ui-progressive }}</div>}}}} }}<includeonly>{{NOINDEX}}{{user other|{{#if:{{{demo|}}}||[[Category:Rejected AfC submissions in userspace]]}}}}{{#if:{{{demo|}}}||[[Category:Rejected AfC submissions]]}} </includeonly><noinclude> {{documentation}} </noinclude> cata9f97fibpgv1dpxsc5y8xq44mvwc 740095 740094 2026-05-01T19:31:54Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/rejected]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740094 wikitext text/x-wiki {{Ombox | type = notice | class = afc-submission-rejected | templatestyles = AfC submission/styles.css | image = none | imageright = [[File:Dialog-STOP.svg|center|60px]] | text = <div style="text-align: center;"><span class="afc-submission-header">Submission rejected{{#if: {{{decliner|}}}{{{declinets|}}} | &#32;{{#if: {{{declinets|}}} | on {{#time: j F Y|{{{declinets}}}}}{{#if: {{{decliner|}}} | &#32; }} }}{{#if: {{{decliner|}}} |by {{Noping|{{{decliner}}}}} ([[User talk:{{{decliner}}}|talk]]) }} }}.</span></div>{{#if: {{{reason|}}} |<p>{{AfC submission/reject reasons|cat={{{cat|}}}|{{{reason}}}|{{{details|}}}}}</p><!-- -->{{#if: {{{reason2|}}}|<p>{{AfC submission/reject reasons|cat={{{cat|}}}|{{{reason2}}}|{{{details2|}}}}}</p>|<!--empty-->}}|<br />| <includeonly>{{#ifexpr: {{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} < {{#time: U}}|<div class="afc-submission-g13">This draft has not been edited in over six months and qualifies to be deleted per [[WP:G13|CSD G13]].</div>{{#if:{{{demo|}}}||[[Category:G13 eligible AfC submissions|{{REVISIONTIMESTAMP}}]][[Category:Candidates for speedy deletion as abandoned drafts or AfC submissions|{{REVISIONTIMESTAMP}}]]}}}} {{#ifexpr: ({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +5 months}} < {{#time: U}})and({{#time: U | {{{revisionts|{{REVISIONTIMESTAMP}}}}} +6 months}} > {{#time: U}})|{{#if:{{{demo|}}}||[[Category:AfC G13 eligible soon submissions|{{REVISIONTIMESTAMP}}]]}}}} </includeonly> }}<small>{{#if: {{{decliner|}}} | &#32;Rejected by {{Noping|{{{decliner}}}}}{{#if: {{{declinets|}}} | &#32;{{time ago|{{{declinets}}}}} }}. | &#32;The reviewer(s) who rejected this submission will be listed in the [{{SERVER}}{{localurl:{{NAMESPACE}}:{{PAGENAME}}|action=history}} page history]. }}</small>{{#ifeq: {{lc: {{{small|}}} }} | yes||&nbsp;<small>Last edited {{#if: {{REVISIONUSER}} | by [[User:{{encodefirst|{{REVISIONUSER}}}}|{{encodefirst|{{REVISIONUSER}}}}]] }} {{time ago|{{{revisionts|{{REVISIONTIMESTAMP}}}}}}}. </small>{{main other||2=<div class="afc-submission-advice">{{Clickable button | Ask for advice | url = {{fullurl:Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question|withJS=MediaWiki:AFCHD-wizard.js&page={{FULLPAGENAMEE}}}} | class = mw-ui-progressive }}</div>}}}} }}<includeonly>{{NOINDEX}}{{user other|{{#if:{{{demo|}}}||[[Category:Rejected AfC submissions in userspace]]}}}}{{#if:{{{demo|}}}||[[Category:Rejected AfC submissions]]}} </includeonly><noinclude> {{documentation}} </noinclude> cata9f97fibpgv1dpxsc5y8xq44mvwc Template:AfC submission/comments 10 122555 740059 732749 2026-04-27T04:38:02Z en>Paine Ellsworth 0 per edit request on talk page - fix URL for cv-c parameter (tested with /comments sandbox and testcases) 740059 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |resume = This draft reads like a resume or curriculum vitae. Wikipedia is an encyclopedia, not a professional networking website or a place to promote yourself or your services. We also [[Wikipedia:Autobiography|strongly discourage writing about yourself]]. Articles about people must meet Wikipedia's [[Wikipedia:Notability (people)|criteria for inclusion for people]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability_(people)#Basic_criteria|significant coverage]]'': discuss the person in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the person, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as resume-like]]}}}}</includeonly> |ai |llm = This draft appears to be generated by a [[large language model]] (such as ChatGPT). You [[Wikipedia:NEWLLM|cannot use LLMs]] to generate article content. LLM-generated pages with [[Wikipedia:G15|certain obvious signs of being machine generated]] may be deleted without notice. These tools are prone to specific issues that violate our policies: * ''[[Hallucination (artificial intelligence)|hallucinations]]'': they often invent false information and cite non-existent references. * ''[[Wikipedia:Neutral point of view|unencyclopedic tone]]'': they tend to be vague, promotional, or essay-like, rather than neutral and factual. * ''[[Wikipedia:Copyright violations|copyright issues]]'': they may closely paraphrase existing text, leading to copyright violations. Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. See [[Wikipedia:Large language models|the advice page on large language models]] for more information. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a large language model output]]}}}}</includeonly> |neo = This draft's references do not show that the neologism meets Wikipedia's [[Wikipedia:Notability|criteria for inclusion]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the term in detail, not just brief mentions or routine use; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as promoted or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. [[Wikipedia:Neologism|Wikipedia is not a dictionary]] and we do not accept articles that are simply definitions of neologisms.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable web content]]}}}}</includeonly> |web = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (web)|criteria for inclusion for web content]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable web content]]}}}}</includeonly> |prof |academic = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (academics)|criteria for inclusion for academics]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (academics)|specific criteria for academics]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable academic topic]]}}}}</includeonly> |sport |athlete = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (sports)|criteria for inclusion for sports]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (sports)|specific criteria for sports]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable athletic topic]]}}}}</includeonly> |music |m |band = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (music)|criteria for inclusion for music]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (music)|specific criteria for music]];'' '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. It is often easier to prove the notability of an ''album'' or ''artist'' than an ''individual song'' or ''band member''. If the subject is not yet notable, consider improving a relevant existing article instead. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable musical topic]]}}}}</includeonly> |film = This draft's references do not show that the film meets Wikipedia's [[Wikipedia:Notability (films)|criteria for inclusion for films]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (films)|specific criteria for films]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the film in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the film, such as press releases, the studio's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable film]]}}}}</includeonly> |book = This draft's references do not show that the book meets Wikipedia's [[Wikipedia:Notability (books)|criteria for inclusion for books]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (books)|specific criteria for books]]'' '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the book in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the book, such as press releases, the author or publisher's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable book]]}}}}</includeonly> |event = This draft's references do not show that the event meets Wikipedia's [[Wikipedia:Notability (events)|criteria for inclusion for events]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the event in detail, not just brief mentions or routine announcements; * show ''[[Wikipedia:Notability (events)#Lasting effects|lasting significance]]'': analysis published after the initial breaking news has passed, showing historical impact; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the event, such as press releases, the event or organizers own website, or sponsored content. Please add references that meet ''all four'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable event]]}}}}</includeonly> |org |inc |corp = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (organizations and companies)|criteria for inclusion for organizations and companies]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability (organizations and companies)#Significant coverage|significant coverage]]'': discuss the subject in detail, excluding routine coverage like product launches, staff appointments, or financial reports and listings in databases or [[listicle]]s; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable organization]]}}}}</includeonly> |bio = This draft's references do not show that the person meets Wikipedia's [[Wikipedia:Notability (people)|criteria for inclusion for people]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability_(people)#Basic_criteria|significant coverage]]'': discuss the person in detail, not brief mentions or interviews lacking independent analysis; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the person, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable biography]]}}}}</includeonly> |nn = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability|criteria for inclusion]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable]]}}}}</includeonly> |empty |blank = This draft appears to be blank. If you have not written your text yet, please add your content before resubmitting. If you did write the text, it may be hidden by a formatting error. Please check the "Edit" tab to ensure your text is not inside a comment tag.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as blank]]}}}}</includeonly> |notenglish |lang = {{safesubst:<noinclude />#if: {{{2|}}}|This draft is written in {{{2}}}.|}} This is the English Wikipedia and we can only accept articles written in the [[English language]]. * Please provide a high-quality English translation, avoiding [[Wikipedia:Translation|machine translation tools]] (like Google Translate). {{safesubst:<noinclude />#if: {{safesubst:<noinclude />Mw lang|fn=is_name|{{{2|}}}}}|If you prefer to write in your own language, you may write it in the [[:{{safesubst:<noinclude />mw lang|fn=code_from_name|{{{2}}}}}:|{{{2}}} Wikipedia]].|If you prefer to write in your own language, look for the Wikipedia project in your language on the [https://www.wikipedia.org/ Wikipedia home page].|}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not in English]]}}}}</includeonly> |test = This draft appears to be a test edit. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a test]]}}}}</includeonly> |redirect = This is not the correct place to request new redirects. Please follow the instructions at [[Wikipedia:Article wizard/Redirects]]. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a redirect]]}}}}</includeonly> |van = This draft has been declined because it meets one of these criteria: * ''[[Wikipedia:Attack page|attack page]]'': it is created primarily to disparage, threaten, or harass a subject; * ''[[Wikipedia:Biographies of living persons|negative biography]]'': it contains unsourced, negative material about a living person; * ''[[Wikipedia:Vandalism|vandalism]]'': it contains jokes, hoaxes, nonsense, or gibberish. Drafts that meet [[Wikipedia:Deletion policy|these criteria may be deleted without notice]]. Users who continue to create inappropriate pages may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as being vandalism or attack pages]]}}}}</includeonly> |cite |footnote |ilc = This draft lacks inline citations. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. We require inline citations (footnotes) to show which source supports which specific statement. You must place an inline citation directly after: * quotations; * any material whose verifiability is likely to be challenged or is contentious; * any material about living people. Please edit your draft to support your statements with inline citations. Learn how to create inline citations in the: * ''Source Editor'': [[Help:Introduction to referencing with Wiki Markup/1|Introduction to referencing with Wiki Markup]]. * ''VisualEditor'': [[Help:Introduction to referencing with VisualEditor/1|Introduction to referencing with VisualEditor]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as needing footnotes]]}}}}</includeonly> |blp = The text of this submission has been removed. Wikipedia has a strict [[Wikipedia:Biographies of living persons|policy regarding biographies of living people]]. To protect subjects from harm, we remove content that is: * ''[[Wikipedia:Unsourced|unverified]]'': lacking citations from reliable sources and * ''[[Wikipedia:Libel|potentially damaging]]'': containing negative, sensitive, or libelous information that cannot be verified. Your text is preserved in the [[Help:Page history|page history]] tab. You may retrieve it only if you immediately add inline citations to [[Wikipedia:Reliable sources|reliable sources]] that support the material.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as BLP violations]]}}}}</includeonly> |cv-n |cv = This draft appears to contain copyrighted material{{safesubst:<noinclude />#ifeq: {{{2|}}}|||{{spaces}}from {{{2}}},}} which has been removed. Wikipedia strictly prohibits [[Wikipedia:Copyright violations|copyright violations]]. Assume all text is copyrighted unless released under a compatible license. * We can only accept text if it is [[Wikipedia:Copying text from other sources|explicitly released under a compatible free license]] or is in the [[public domain]]. * If the text is not freely licensed, you must rewrite or summarize it entirely in your own words. [[Wikipedia:Close paraphrasing|Changing a few words]] is still considered a copyright violation. * If you own the text you cannot paste it here unless you formally release it via our [[Wikipedia:Donating copyrighted materials|release process]]. Users who continue to post copyrighted material may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]]. {{#ifeq:{{NAMESPACENUMBER}}|3||{{pb}}{{huge|{{error|<u>Note to reviewers</u>: do not leave copyright violations sitting in the page history. Please follow the [[Wikipedia:WikiProject Articles for creation/Reviewing instructions/Copyright cleanup instructions|cleanup instructions]].}}}}{{pb}}<span class="sysop-show">''Administrators: if the page has been cleaned and you are seeing this notice, please change the <code>cv</code> to <code>cv-cleaned</code> in the {{t|AfC submission}} call.''</span>}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as copyright violations]][[Category:Requested RD1 redactions]]}}}}</includeonly> |cv-c |cv-cleaned = This draft appears to contain copyrighted material {{safesubst:<noinclude />#ifeq:{{{2|}}}|||from {{{2}}},}} which has been removed. Wikipedia strictly prohibits [[Wikipedia:Copyright violations|copyright violations]]. Assume all text is copyrighted unless released under a compatible license. * We can only accept text if it is [[Wikipedia:Copying text from other sources|explicitly released under a compatible free license]] or is in the [[public domain]]. * If the text is not freely licensed, you must rewrite or summarize it entirely in your own words. [[Wikipedia:Close paraphrasing|Changing a few words]] is still considered a copyright violation. * If you own the text you cannot paste it here unless you formally release it via our [[Wikipedia:Donating copyrighted materials|release process]]. Users who continue to post copyrighted material may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]].<p>[[File:Symbol opinion vote.svg|20px]] This submission has now been cleaned of the above-noted copyright violation and its history redacted by an administrator to remove the infringement. If re-submitted (and subsequent additions do not reintroduce copyright problems), the content may be assessed on other grounds.</p><includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as copyright violations, but later cleaned]]}}}}</includeonly> |v |rs |source = This draft is not adequately supported by [[Wikipedia:Reliable sources|reliable sources]]. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. * Reliable sources include: reputable newspapers, magazines, academic journals, and books from respected publishers. * Unacceptable sources include: personal blogs, social media, [[Predatory publishing|predatory publishers]], most [[Tabloid journalism|tabloids]], and websites where anyone can contribute. Replace any unreliable sources with high-quality sources. If you cannot find a reliable source for the material, it should be removed.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as lacking reliable third-party sources]]}}}}</includeonly> |not = This draft is not suitable for Wikipedia. Wikipedia is not a general publishing platform. We do not accept: * ''essays or original research'': personal views, advocacy, unpublished theories, or pre-prints; * ''resumes or personal profiles'': CVs, social networking pages, or memorials; * ''directories or manuals'': travel guides, catalogs, or how-to instructions; * ''creative writing'': fanfiction, worldbuilding, myths, or things you have made up. Read [[Wikipedia:What Wikipedia is not|What Wikipedia is not]] to understand what content is excluded.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not suitable for Wikipedia]]}}}}</includeonly> |test = This draft appears to be a test edit. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a test]]}}}}</includeonly> |oneevent |news = This draft appears to be a news report or about a single event. Wikipedia is an encyclopedia, [[Wikipedia:NOT#IINFO|not an indiscriminate collection of information]] or a [[Wikipedia:NOT#NEWS|news service]]. We generally do not accept articles about: * ''events'' that do not meet our [[Wikipedia:Notability (events)|criteria for inclusion for events]]; * ''people'' known only for a [[Wikipedia:BLP1E|single incident]]. Please add references published after the initial breaking news has passed that demonstrate the event's ''[[Wikipedia:Notability (events)#Lasting effects|lasting significance]]''. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a news report on a single event]]}}}}</includeonly> |d |dict = This draft appears to be a dictionary definition. [[Wikipedia:Wikipedia is not a dictionary|Wikipedia is not a dictionary]] and we do not accept articles that are simply definitions of words, acronyms, or slang. * To improve the draft it must expand on the subject, such as discussing its history, cultural significance, or impact rather than just defining the word; * To create a dictionary entry you can contribute to our sister project [[wikt:Main_Page|Wiktionary]]. Please ensure the subject meets [[wikt:Wiktionary:Criteria_for_inclusion|Wiktionary's inclusion criteria]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a dictionary definition]]}}}}</includeonly> |plot = This draft appears to be a plot summary. [[Wikipedia:What Wikipedia is not#PLOT|Wikipedia does not host articles consisting of only plot summaries]]. Articles about fictional subjects must cover their real-world context supported by multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the work in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as all plot]]}}}}</includeonly> |joke |hoax = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia. Users who continue to create inappropriate pages may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]]. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as jokes]]}}}}</includeonly> |essay = This draft reads like an essay or opinion piece. [[Wikipedia:What Wikipedia is not|Wikipedia is not a place for original research or personal opinions]]. The draft should: * ''[[Wikipedia:No original research#Primary, secondary and tertiary sources|summarize secondary sources]]'': do not offer your own analysis or arguments; * ''[[Wikipedia:Neutral point of view|be written from a neutral point of view]]'': represent the subject without bias, avoiding praise, criticism, or persuasive or promotional language; * ''[[Wikipedia:No original research|not contain original research]]'': do not include new theories, unpublished ideas, or personal experiences. Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as an essay]]}}}}</includeonly> |npov = [[Wikipedia:Neutral point of view|This draft is not written from a neutral point of view]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not written in a neutral point of view]]}}}}</includeonly> |adv |spam |advert = This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as an advertisement]]}}}}</includeonly> |exists = This draft appears to be a duplicate of an existing article. Wikipedia does not permit multiple articles on the same topic. * {{safesubst:<noinclude />#ifeq:{{{2|}}}|||To improve the existing article, please edit it directly at [[:{{{2}}}]]. If the article is [[Wikipedia:Protection policy|protected]] and you cannot edit it, propose your changes on the article's [[Help:Talk pages|talk page]].}} * If you are writing about a different subject with the same name, ask the reviewer to [[Wikipedia:Disambiguation|rename your draft]] to distinguish it. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as already existing]]}}}}</includeonly> |dup |duplicate = This draft duplicates another submission{{safesubst:<noinclude />#ifeq: {{{2|}}}|||, [[Draft:{{{2}}}|{{{2}}}]],}} currently submitted for review. To save time, we will review the other submission only. Any future edits or improvements should be made on that submission, not here. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a duplicate]]}}}}</includeonly> |context = This draft provides insufficient context for those unfamiliar with the subject. Please see the [[Wikipedia:Writing better articles#Provide context for the reader|guide to writing better articles]] for how to improve your writing. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as lacking context]]}}}}</includeonly> |merge |mergeto = This draft does not have sufficient content to warrant a standalone article of its own, but it could be merged into the existing article {{safesubst:<noinclude />#if:{{{2|}}}|at [[:{{{2}}}]]|on the same subject}}. * Edit the existing article to include your text and citations into an appropriate section. * If the article is [[Wikipedia:Protection policy|protected]] and you cannot edit it, propose your changes on the article's [[Help:Talk pages|Talk page]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as needing to be merged]]}}}}</includeonly> |cat = This is not the correct place to request new categories. Please follow the instructions at [[Wikipedia:Articles for creation/Categories]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a category]]}}}}</includeonly> <!-- New templates added below --> |ecr= This draft covers a [[Wikipedia:Contentious topics|contentious topic]] with an editing restriction. To prevent disruption, Wikipedia's [[Wikipedia:Arbitration Committee|Arbitration Committee]] only permits editors with [[Wikipedia:User groups#Extended confirmed accounts|Extended confirmed]] accounts to write about this topic. * You are not Extended confirmed. Your account must be at least 30 days old and have at least 500 edits. * You must wait until your account becomes Extended confirmed before making further edits or resubmitting this draft. For a list of contentious topics and their restrictions see [[Wikipedia:Contentious topics#List of contentious topics|Contentious topics]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as contentious topics restricted]]}}}}</includeonly> |ns |nosource = This draft does not include any sources or inline citations. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. You should also use inline citations (footnotes) to show which source supports which specific statement. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. You must place an inline citation directly after: * quotations; * any material whose verifiability is likely to be challenged or is contentious; * any material about living people. Learn how to create inline citations in the: * ''Source Editor'': [[Help:Introduction to referencing with Wiki Markup/1|Introduction to referencing with Wiki Markup]]. * ''VisualEditor'': [[Help:Introduction to referencing with VisualEditor/1|Introduction to referencing with VisualEditor]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as unsourced]]}}}}</includeonly> |list= This draft's references do not show the list meets Wikipedia's [[Wikipedia:Stand-alone lists#Notability|criteria for inclusion for stand-alone lists]]. The draft requires evidence of: * ''group notability'': sources must discuss the items in the list as a whole, rather than just verifying that the individual items exist; * ''encyclopedic purpose'': avoid cross-categorisations, such as "List of X of Y", unless sources explicitly discuss this intersection as a distinct subject; * ''Selection criteria'': an unambiguous, objective definition of what is included to distinguish the list from a [[Wikipedia:What Wikipedia is not#DIR|directory]]. Please add references that meet ''all three'' of these criteria. If no such sources exist, the list is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable list]]}}}}</includeonly> |med= This draft contains [[Wikipedia:Biomedical information|medical, health, or psychological claims]]. Wikipedia requires a [[Wikipedia:Identifying reliable sources (medicine)|higher standard of sourcing for biomedical content]]. To ensure accuracy, content must be based on reliable secondary sources that reflect current knowledge. The draft should: * ''have high-quality secondary sources'': such as systematic reviews, meta-analyses, or medical textbooks from respected publishers; * ''avoid primary sources'': such as direct research papers, clinical trials, or news reports on early studies. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable medical content]]}}}}</includeonly> |geo= This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (geographic features)|criteria for inclusion for geographic features]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (geographic features)|specific criteria for geographic features]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine directory listings; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, tourism boards, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable geographic feature]]}}}}</includeonly> |species= This draft's references do not show that the species meets Wikipedia's [[Wikipedia:Notability (species)|criteria for inclusion for species]]. The draft requires either: * evidence that the species meets any of the ''[[Wikipedia:Notability (species)|specific criteria for species]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the species in detail, not just brief mentions; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable scientific outlets with editorial oversight. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable species]]}}}}</includeonly> |astro= This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (astronomical objects)|criteria for inclusion for astronomical objects]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (astronomical objects)|specific criteria for astronomical objects]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the object in detail, not just brief mentions or database entries; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable scientific outlets with editorial oversight. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable astronomical object]]}}}}</includeonly> |number= This draft's references do not show that the number meets Wikipedia's [[Wikipedia:Notability (numbers)|criteria for inclusion for numbers]]. The draft requires either: * evidence that the number meets any of the ''[[Wikipedia:Notability (numbers)|specific criteria for numbers]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the number in detail; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable mathematical outlets with editorial oversight. Please add references that meet these criteria. If no such sources exist, the number is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable number]]}}}}</includeonly> |creative = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (people)#Creative professionals|criteria for inclusion for creative professionals]]. The draft requires: * evidence that the subject meets the ''[[Wikipedia:Notability|general criteria for inclusion]]''; * '''or''' evidence that the subject meets any of the ''[[Wikipedia:Notability (people)#Creative professionals|specific criteria for creative professionals]]''; * '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that cover the subject or their work and: ** provide ''[[Wikipedia:Notability#significant_coverage|critical attention]]'': professional reviews, excluding user reviews from the general public; ** are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; ** are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. It is often easier to prove the notability of a ''creative work'' than of a ''person''. If the person is not yet notable, but their work has received multiple in-depth professional reviews, consider writing about the work instead.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable creative professional]]}}}}</includeonly> |school = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (organizations and companies)#Schools|criteria for inclusion for schools]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability (organizations and companies)#Significant coverage|significant coverage]]'': discuss the subject in detail, excluding routine coverage like sports results, league tables, government inspection reports, and listings in databases or [[listicle]]s; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. Individual faculties, departments, and student clubs are rarely notable on their own. You could add this content to the main educational institution article if one exists instead.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable school]]}}}}</includeonly> |reason = {{{2}}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined with a custom reason]]}}}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> 7r2vq9ht2p1k2r96oragafn3ewsoyxs 740060 740059 2026-05-01T19:16:16Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/comments]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740059 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |resume = This draft reads like a resume or curriculum vitae. Wikipedia is an encyclopedia, not a professional networking website or a place to promote yourself or your services. We also [[Wikipedia:Autobiography|strongly discourage writing about yourself]]. Articles about people must meet Wikipedia's [[Wikipedia:Notability (people)|criteria for inclusion for people]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability_(people)#Basic_criteria|significant coverage]]'': discuss the person in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the person, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as resume-like]]}}}}</includeonly> |ai |llm = This draft appears to be generated by a [[large language model]] (such as ChatGPT). You [[Wikipedia:NEWLLM|cannot use LLMs]] to generate article content. LLM-generated pages with [[Wikipedia:G15|certain obvious signs of being machine generated]] may be deleted without notice. These tools are prone to specific issues that violate our policies: * ''[[Hallucination (artificial intelligence)|hallucinations]]'': they often invent false information and cite non-existent references. * ''[[Wikipedia:Neutral point of view|unencyclopedic tone]]'': they tend to be vague, promotional, or essay-like, rather than neutral and factual. * ''[[Wikipedia:Copyright violations|copyright issues]]'': they may closely paraphrase existing text, leading to copyright violations. Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. See [[Wikipedia:Large language models|the advice page on large language models]] for more information. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a large language model output]]}}}}</includeonly> |neo = This draft's references do not show that the neologism meets Wikipedia's [[Wikipedia:Notability|criteria for inclusion]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the term in detail, not just brief mentions or routine use; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as promoted or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. [[Wikipedia:Neologism|Wikipedia is not a dictionary]] and we do not accept articles that are simply definitions of neologisms.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable web content]]}}}}</includeonly> |web = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (web)|criteria for inclusion for web content]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable web content]]}}}}</includeonly> |prof |academic = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (academics)|criteria for inclusion for academics]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (academics)|specific criteria for academics]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable academic topic]]}}}}</includeonly> |sport |athlete = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (sports)|criteria for inclusion for sports]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (sports)|specific criteria for sports]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable athletic topic]]}}}}</includeonly> |music |m |band = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (music)|criteria for inclusion for music]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (music)|specific criteria for music]];'' '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. It is often easier to prove the notability of an ''album'' or ''artist'' than an ''individual song'' or ''band member''. If the subject is not yet notable, consider improving a relevant existing article instead. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable musical topic]]}}}}</includeonly> |film = This draft's references do not show that the film meets Wikipedia's [[Wikipedia:Notability (films)|criteria for inclusion for films]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (films)|specific criteria for films]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the film in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the film, such as press releases, the studio's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable film]]}}}}</includeonly> |book = This draft's references do not show that the book meets Wikipedia's [[Wikipedia:Notability (books)|criteria for inclusion for books]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (books)|specific criteria for books]]'' '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the book in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the book, such as press releases, the author or publisher's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable book]]}}}}</includeonly> |event = This draft's references do not show that the event meets Wikipedia's [[Wikipedia:Notability (events)|criteria for inclusion for events]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the event in detail, not just brief mentions or routine announcements; * show ''[[Wikipedia:Notability (events)#Lasting effects|lasting significance]]'': analysis published after the initial breaking news has passed, showing historical impact; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the event, such as press releases, the event or organizers own website, or sponsored content. Please add references that meet ''all four'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable event]]}}}}</includeonly> |org |inc |corp = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (organizations and companies)|criteria for inclusion for organizations and companies]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability (organizations and companies)#Significant coverage|significant coverage]]'': discuss the subject in detail, excluding routine coverage like product launches, staff appointments, or financial reports and listings in databases or [[listicle]]s; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable organization]]}}}}</includeonly> |bio = This draft's references do not show that the person meets Wikipedia's [[Wikipedia:Notability (people)|criteria for inclusion for people]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability_(people)#Basic_criteria|significant coverage]]'': discuss the person in detail, not brief mentions or interviews lacking independent analysis; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the person, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable biography]]}}}}</includeonly> |nn = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability|criteria for inclusion]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as non-notable]]}}}}</includeonly> |empty |blank = This draft appears to be blank. If you have not written your text yet, please add your content before resubmitting. If you did write the text, it may be hidden by a formatting error. Please check the "Edit" tab to ensure your text is not inside a comment tag.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as blank]]}}}}</includeonly> |notenglish |lang = {{safesubst:<noinclude />#if: {{{2|}}}|This draft is written in {{{2}}}.|}} This is the English Wikipedia and we can only accept articles written in the [[English language]]. * Please provide a high-quality English translation, avoiding [[Wikipedia:Translation|machine translation tools]] (like Google Translate). {{safesubst:<noinclude />#if: {{safesubst:<noinclude />Mw lang|fn=is_name|{{{2|}}}}}|If you prefer to write in your own language, you may write it in the [[:{{safesubst:<noinclude />mw lang|fn=code_from_name|{{{2}}}}}:|{{{2}}} Wikipedia]].|If you prefer to write in your own language, look for the Wikipedia project in your language on the [https://www.wikipedia.org/ Wikipedia home page].|}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not in English]]}}}}</includeonly> |test = This draft appears to be a test edit. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a test]]}}}}</includeonly> |redirect = This is not the correct place to request new redirects. Please follow the instructions at [[Wikipedia:Article wizard/Redirects]]. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a redirect]]}}}}</includeonly> |van = This draft has been declined because it meets one of these criteria: * ''[[Wikipedia:Attack page|attack page]]'': it is created primarily to disparage, threaten, or harass a subject; * ''[[Wikipedia:Biographies of living persons|negative biography]]'': it contains unsourced, negative material about a living person; * ''[[Wikipedia:Vandalism|vandalism]]'': it contains jokes, hoaxes, nonsense, or gibberish. Drafts that meet [[Wikipedia:Deletion policy|these criteria may be deleted without notice]]. Users who continue to create inappropriate pages may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as being vandalism or attack pages]]}}}}</includeonly> |cite |footnote |ilc = This draft lacks inline citations. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. We require inline citations (footnotes) to show which source supports which specific statement. You must place an inline citation directly after: * quotations; * any material whose verifiability is likely to be challenged or is contentious; * any material about living people. Please edit your draft to support your statements with inline citations. Learn how to create inline citations in the: * ''Source Editor'': [[Help:Introduction to referencing with Wiki Markup/1|Introduction to referencing with Wiki Markup]]. * ''VisualEditor'': [[Help:Introduction to referencing with VisualEditor/1|Introduction to referencing with VisualEditor]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as needing footnotes]]}}}}</includeonly> |blp = The text of this submission has been removed. Wikipedia has a strict [[Wikipedia:Biographies of living persons|policy regarding biographies of living people]]. To protect subjects from harm, we remove content that is: * ''[[Wikipedia:Unsourced|unverified]]'': lacking citations from reliable sources and * ''[[Wikipedia:Libel|potentially damaging]]'': containing negative, sensitive, or libelous information that cannot be verified. Your text is preserved in the [[Help:Page history|page history]] tab. You may retrieve it only if you immediately add inline citations to [[Wikipedia:Reliable sources|reliable sources]] that support the material.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as BLP violations]]}}}}</includeonly> |cv-n |cv = This draft appears to contain copyrighted material{{safesubst:<noinclude />#ifeq: {{{2|}}}|||{{spaces}}from {{{2}}},}} which has been removed. Wikipedia strictly prohibits [[Wikipedia:Copyright violations|copyright violations]]. Assume all text is copyrighted unless released under a compatible license. * We can only accept text if it is [[Wikipedia:Copying text from other sources|explicitly released under a compatible free license]] or is in the [[public domain]]. * If the text is not freely licensed, you must rewrite or summarize it entirely in your own words. [[Wikipedia:Close paraphrasing|Changing a few words]] is still considered a copyright violation. * If you own the text you cannot paste it here unless you formally release it via our [[Wikipedia:Donating copyrighted materials|release process]]. Users who continue to post copyrighted material may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]]. {{#ifeq:{{NAMESPACENUMBER}}|3||{{pb}}{{huge|{{error|<u>Note to reviewers</u>: do not leave copyright violations sitting in the page history. Please follow the [[Wikipedia:WikiProject Articles for creation/Reviewing instructions/Copyright cleanup instructions|cleanup instructions]].}}}}{{pb}}<span class="sysop-show">''Administrators: if the page has been cleaned and you are seeing this notice, please change the <code>cv</code> to <code>cv-cleaned</code> in the {{t|AfC submission}} call.''</span>}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as copyright violations]][[Category:Requested RD1 redactions]]}}}}</includeonly> |cv-c |cv-cleaned = This draft appears to contain copyrighted material {{safesubst:<noinclude />#ifeq:{{{2|}}}|||from {{{2}}},}} which has been removed. Wikipedia strictly prohibits [[Wikipedia:Copyright violations|copyright violations]]. Assume all text is copyrighted unless released under a compatible license. * We can only accept text if it is [[Wikipedia:Copying text from other sources|explicitly released under a compatible free license]] or is in the [[public domain]]. * If the text is not freely licensed, you must rewrite or summarize it entirely in your own words. [[Wikipedia:Close paraphrasing|Changing a few words]] is still considered a copyright violation. * If you own the text you cannot paste it here unless you formally release it via our [[Wikipedia:Donating copyrighted materials|release process]]. Users who continue to post copyrighted material may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]].<p>[[File:Symbol opinion vote.svg|20px]] This submission has now been cleaned of the above-noted copyright violation and its history redacted by an administrator to remove the infringement. If re-submitted (and subsequent additions do not reintroduce copyright problems), the content may be assessed on other grounds.</p><includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as copyright violations, but later cleaned]]}}}}</includeonly> |v |rs |source = This draft is not adequately supported by [[Wikipedia:Reliable sources|reliable sources]]. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. * Reliable sources include: reputable newspapers, magazines, academic journals, and books from respected publishers. * Unacceptable sources include: personal blogs, social media, [[Predatory publishing|predatory publishers]], most [[Tabloid journalism|tabloids]], and websites where anyone can contribute. Replace any unreliable sources with high-quality sources. If you cannot find a reliable source for the material, it should be removed.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as lacking reliable third-party sources]]}}}}</includeonly> |not = This draft is not suitable for Wikipedia. Wikipedia is not a general publishing platform. We do not accept: * ''essays or original research'': personal views, advocacy, unpublished theories, or pre-prints; * ''resumes or personal profiles'': CVs, social networking pages, or memorials; * ''directories or manuals'': travel guides, catalogs, or how-to instructions; * ''creative writing'': fanfiction, worldbuilding, myths, or things you have made up. Read [[Wikipedia:What Wikipedia is not|What Wikipedia is not]] to understand what content is excluded.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not suitable for Wikipedia]]}}}}</includeonly> |test = This draft appears to be a test edit. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a test]]}}}}</includeonly> |oneevent |news = This draft appears to be a news report or about a single event. Wikipedia is an encyclopedia, [[Wikipedia:NOT#IINFO|not an indiscriminate collection of information]] or a [[Wikipedia:NOT#NEWS|news service]]. We generally do not accept articles about: * ''events'' that do not meet our [[Wikipedia:Notability (events)|criteria for inclusion for events]]; * ''people'' known only for a [[Wikipedia:BLP1E|single incident]]. Please add references published after the initial breaking news has passed that demonstrate the event's ''[[Wikipedia:Notability (events)#Lasting effects|lasting significance]]''. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a news report on a single event]]}}}}</includeonly> |d |dict = This draft appears to be a dictionary definition. [[Wikipedia:Wikipedia is not a dictionary|Wikipedia is not a dictionary]] and we do not accept articles that are simply definitions of words, acronyms, or slang. * To improve the draft it must expand on the subject, such as discussing its history, cultural significance, or impact rather than just defining the word; * To create a dictionary entry you can contribute to our sister project [[wikt:Main_Page|Wiktionary]]. Please ensure the subject meets [[wikt:Wiktionary:Criteria_for_inclusion|Wiktionary's inclusion criteria]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a dictionary definition]]}}}}</includeonly> |plot = This draft appears to be a plot summary. [[Wikipedia:What Wikipedia is not#PLOT|Wikipedia does not host articles consisting of only plot summaries]]. Articles about fictional subjects must cover their real-world context supported by multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the work in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as all plot]]}}}}</includeonly> |joke |hoax = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative. Please use the [[Wikipedia:Sandbox|sandbox]] to practice editing. Do not submit this draft for review until you have written an article intended for the encyclopedia. Users who continue to create inappropriate pages may be [[Wikipedia:Blocking policy|blocked from editing Wikipedia]]. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as jokes]]}}}}</includeonly> |essay = This draft reads like an essay or opinion piece. [[Wikipedia:What Wikipedia is not|Wikipedia is not a place for original research or personal opinions]]. The draft should: * ''[[Wikipedia:No original research#Primary, secondary and tertiary sources|summarize secondary sources]]'': do not offer your own analysis or arguments; * ''[[Wikipedia:Neutral point of view|be written from a neutral point of view]]'': represent the subject without bias, avoiding praise, criticism, or persuasive or promotional language; * ''[[Wikipedia:No original research|not contain original research]]'': do not include new theories, unpublished ideas, or personal experiences. Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as an essay]]}}}}</includeonly> |npov = [[Wikipedia:Neutral point of view|This draft is not written from a neutral point of view]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as not written in a neutral point of view]]}}}}</includeonly> |adv |spam |advert = This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as an advertisement]]}}}}</includeonly> |exists = This draft appears to be a duplicate of an existing article. Wikipedia does not permit multiple articles on the same topic. * {{safesubst:<noinclude />#ifeq:{{{2|}}}|||To improve the existing article, please edit it directly at [[:{{{2}}}]]. If the article is [[Wikipedia:Protection policy|protected]] and you cannot edit it, propose your changes on the article's [[Help:Talk pages|talk page]].}} * If you are writing about a different subject with the same name, ask the reviewer to [[Wikipedia:Disambiguation|rename your draft]] to distinguish it. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as already existing]]}}}}</includeonly> |dup |duplicate = This draft duplicates another submission{{safesubst:<noinclude />#ifeq: {{{2|}}}|||, [[Draft:{{{2}}}|{{{2}}}]],}} currently submitted for review. To save time, we will review the other submission only. Any future edits or improvements should be made on that submission, not here. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a duplicate]]}}}}</includeonly> |context = This draft provides insufficient context for those unfamiliar with the subject. Please see the [[Wikipedia:Writing better articles#Provide context for the reader|guide to writing better articles]] for how to improve your writing. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as lacking context]]}}}}</includeonly> |merge |mergeto = This draft does not have sufficient content to warrant a standalone article of its own, but it could be merged into the existing article {{safesubst:<noinclude />#if:{{{2|}}}|at [[:{{{2}}}]]|on the same subject}}. * Edit the existing article to include your text and citations into an appropriate section. * If the article is [[Wikipedia:Protection policy|protected]] and you cannot edit it, propose your changes on the article's [[Help:Talk pages|Talk page]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as needing to be merged]]}}}}</includeonly> |cat = This is not the correct place to request new categories. Please follow the instructions at [[Wikipedia:Articles for creation/Categories]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a category]]}}}}</includeonly> <!-- New templates added below --> |ecr= This draft covers a [[Wikipedia:Contentious topics|contentious topic]] with an editing restriction. To prevent disruption, Wikipedia's [[Wikipedia:Arbitration Committee|Arbitration Committee]] only permits editors with [[Wikipedia:User groups#Extended confirmed accounts|Extended confirmed]] accounts to write about this topic. * You are not Extended confirmed. Your account must be at least 30 days old and have at least 500 edits. * You must wait until your account becomes Extended confirmed before making further edits or resubmitting this draft. For a list of contentious topics and their restrictions see [[Wikipedia:Contentious topics#List of contentious topics|Contentious topics]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as contentious topics restricted]]}}}}</includeonly> |ns |nosource = This draft does not include any sources or inline citations. Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] requires that all content be supported by [[Wikipedia:Reliable sources|reliable sources]]. You should also use inline citations (footnotes) to show which source supports which specific statement. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine announcements; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. You must place an inline citation directly after: * quotations; * any material whose verifiability is likely to be challenged or is contentious; * any material about living people. Learn how to create inline citations in the: * ''Source Editor'': [[Help:Introduction to referencing with Wiki Markup/1|Introduction to referencing with Wiki Markup]]. * ''VisualEditor'': [[Help:Introduction to referencing with VisualEditor/1|Introduction to referencing with VisualEditor]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as unsourced]]}}}}</includeonly> |list= This draft's references do not show the list meets Wikipedia's [[Wikipedia:Stand-alone lists#Notability|criteria for inclusion for stand-alone lists]]. The draft requires evidence of: * ''group notability'': sources must discuss the items in the list as a whole, rather than just verifying that the individual items exist; * ''encyclopedic purpose'': avoid cross-categorisations, such as "List of X of Y", unless sources explicitly discuss this intersection as a distinct subject; * ''Selection criteria'': an unambiguous, objective definition of what is included to distinguish the list from a [[Wikipedia:What Wikipedia is not#DIR|directory]]. Please add references that meet ''all three'' of these criteria. If no such sources exist, the list is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable list]]}}}}</includeonly> |med= This draft contains [[Wikipedia:Biomedical information|medical, health, or psychological claims]]. Wikipedia requires a [[Wikipedia:Identifying reliable sources (medicine)|higher standard of sourcing for biomedical content]]. To ensure accuracy, content must be based on reliable secondary sources that reflect current knowledge. The draft should: * ''have high-quality secondary sources'': such as systematic reviews, meta-analyses, or medical textbooks from respected publishers; * ''avoid primary sources'': such as direct research papers, clinical trials, or news reports on early studies. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable medical content]]}}}}</includeonly> |geo= This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (geographic features)|criteria for inclusion for geographic features]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (geographic features)|specific criteria for geographic features]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the subject in detail, not just brief mentions or routine directory listings; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, tourism boards, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable geographic feature]]}}}}</includeonly> |species= This draft's references do not show that the species meets Wikipedia's [[Wikipedia:Notability (species)|criteria for inclusion for species]]. The draft requires either: * evidence that the species meets any of the ''[[Wikipedia:Notability (species)|specific criteria for species]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the species in detail, not just brief mentions; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable scientific outlets with editorial oversight. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable species]]}}}}</includeonly> |astro= This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (astronomical objects)|criteria for inclusion for astronomical objects]]. The draft requires either: * evidence that the subject meets any of the ''[[Wikipedia:Notability (astronomical objects)|specific criteria for astronomical objects]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the object in detail, not just brief mentions or database entries; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable scientific outlets with editorial oversight. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable astronomical object]]}}}}</includeonly> |number= This draft's references do not show that the number meets Wikipedia's [[Wikipedia:Notability (numbers)|criteria for inclusion for numbers]]. The draft requires either: * evidence that the number meets any of the ''[[Wikipedia:Notability (numbers)|specific criteria for numbers]]''; '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability#significant_coverage|significant coverage]]'': discuss the number in detail; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable mathematical outlets with editorial oversight. Please add references that meet these criteria. If no such sources exist, the number is not yet suitable for Wikipedia.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable number]]}}}}</includeonly> |creative = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (people)#Creative professionals|criteria for inclusion for creative professionals]]. The draft requires: * evidence that the subject meets the ''[[Wikipedia:Notability|general criteria for inclusion]]''; * '''or''' evidence that the subject meets any of the ''[[Wikipedia:Notability (people)#Creative professionals|specific criteria for creative professionals]]''; * '''or''' multiple published [[Wikipedia:Secondary|secondary]] sources that cover the subject or their work and: ** provide ''[[Wikipedia:Notability#significant_coverage|critical attention]]'': professional reviews, excluding user reviews from the general public; ** are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; ** are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as interviews, press releases, the subject's own website, or sponsored content. Please add references that meet these criteria. If none exist, the subject is not yet suitable for Wikipedia. It is often easier to prove the notability of a ''creative work'' than of a ''person''. If the person is not yet notable, but their work has received multiple in-depth professional reviews, consider writing about the work instead.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable creative professional]]}}}}</includeonly> |school = This draft's references do not show that the subject meets Wikipedia's [[Wikipedia:Notability (organizations and companies)#Schools|criteria for inclusion for schools]]. The draft requires multiple published [[Wikipedia:Secondary|secondary]] sources that: * provide ''[[Wikipedia:Notability (organizations and companies)#Significant coverage|significant coverage]]'': discuss the subject in detail, excluding routine coverage like sports results, league tables, government inspection reports, and listings in databases or [[listicle]]s; * are ''[[Wikipedia:Reliable sources|reliable]]'': from reputable outlets with editorial oversight; * are ''[[Wikipedia:Independent_sources|independent]]'': not connected to the subject, such as press releases, the subject's own website, or sponsored content. Please add references that meet ''all three'' of these criteria. If none exist, the subject is not yet suitable for Wikipedia. Individual faculties, departments, and student clubs are rarely notable on their own. You could add this content to the main educational institution article if one exists instead.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined as a non-notable school]]}}}}</includeonly> |reason = {{{2}}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||{{safesubst:<noinclude />#switch:{{NAMESPACENUMBER}}|2|118=[[Category:AfC submissions declined with a custom reason]]}}}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> 7r2vq9ht2p1k2r96oragafn3ewsoyxs Template:AfC submission/comments/doc 10 122556 740065 732757 2026-03-14T22:32:04Z en>HyperAnd 0 Display new templates in documentation 740065 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go at the bottom of this page and interwikis go in Wikidata. --> {{High-use}} These are the possible automated comments when using the {{tlx|AfC submission|D}} template: {| class="wikitable" ! code || Comment |- ! colspan=2 | Notability {{anchor|neo}} {{AFC submission/table|neo}} {{anchor|web}} {{AFC submission/table|web}} {{anchor|prof|academic}} {{AFC submission/table|prof|academic}} {{anchors|athlete|sport}} {{AFC submission/table|athlete|sport}} {{anchors|music|m|band}} {{AFC submission/table|music|m|band}} {{anchor|film}} {{AFC submission/table|film}} {{anchors|corp|inc|org}} {{AFC submission/table|book}} {{AFC submission/table|event}} {{AFC submission/table|corp|inc|org}} {{anchor|bio}} {{AFC submission/table|bio}} {{anchor|nn}} {{AFC submission/table|nn}} {{AFC submission/table|list}} {{AFC submission/table|med}} {{AFC submission/table|geo}} {{AFC submission/table|species}} {{AFC submission/table|astro}} {{AFC submission/table|number}} {{AFC submission/table|creative}} {{AFC submission/table|school}} |- ! colspan=2 | Invalid submissions {{anchors|cat}} {{AFC submission/table|cat}} {{anchor|empty|blank}} {{AFC submission/table|empty|blank}} {{anchor|lang}} {{AFC submission/table|lang|notenglish|input=language}} {{anchor|test}} {{AFC submission/table|test}} {{anchor|redirect}} {{AFC submission/table|redirect}} |- ! colspan=2 | BLP and vandalism {{anchor|van}} {{AFC submission/table|van}} {{anchor|ilc}} {{AFC submission/table|ilc}} {{anchor|blp}} {{AFC submission/table|blp}} |- ! colspan=2 | Submission content {{anchors|cv|cv-n}} {{AFC submission/table|cv|cv-n}} {{anchors|cv_2|cv-n_2}} {{AFC submission/table|cv|cv-n|input=Website}} {{anchor|cv-cleaned|cv-c}} {{AFC submission/table|cv-cleaned|cv-c}} {{anchor|cv-bot}} {{AFC submission/table|cv-bot}} {{anchors|source|v|rs}} {{AFC submission/table|source|v|rs}} {{anchor|not}} {{AFC submission/table|not}} {{anchors|news|oneevent}} {{AFC submission/table|news|oneevent}} {{anchors|dict|d}} {{AFC submission/table|dict|d}} {{anchor|plot}} {{AFC submission/table|plot}} {{anchor|plot_2}} {{AFC submission/table|plot|input=Pagename}} {{anchor|joke}} {{AFC submission/table|joke}} {{AFC submission/table|ai|llm}} {{anchors|ai|llm}} {{AFC submission/table|resume}} {{AFC submission/table|ecr}} {{AFC submission/table|nosource}} |- ! colspan=2 | Prose issues {{anchor|essay}} {{AFC submission/table|essay}} {{anchor|npov}} {{AFC submission/table|npov}} {{anchors|advert|spam|adv}} {{AFC submission/table|advert|spam|adv}} |- ! colspan=2 | Duplicate articles {{anchor|exists}} {{AFC submission/table|exists}} {{anchor|exists_2}} {{AFC submission/table|exists|input=Pagename}} {{anchors|duplicate|dup}} {{AFC submission/table|duplicate|dup}} {{anchors|duplicate_2|dup_2}} {{AFC submission/table|duplicate|dup|input=Title}} |- ! colspan=2 | Other {{anchor|context}} {{AFC submission/table|context}} {{anchors|mergeto|merge}} {{AFC submission/table|mergeto|merge}} {{anchor|r}} {{AFC submission/table|mergeto|merge|input=Pagename}} {{anchor|afd}} {{AFC submission/table|afd}} {{anchor|reason}} {{AFC submission/table|reason|input=Reason}} |} <includeonly>{{Sandbox other|| <!-- Categories go here, and interwikis go in Wikidata --> {{DEFAULTSORT:*}} [[Category:WikiProject Articles for creation]] }}</includeonly> rmabeuudsfa9ze1f9xwa9zz65hpknzc 740066 740065 2026-05-01T19:16:18Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/comments/doc]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740065 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go at the bottom of this page and interwikis go in Wikidata. --> {{High-use}} These are the possible automated comments when using the {{tlx|AfC submission|D}} template: {| class="wikitable" ! code || Comment |- ! colspan=2 | Notability {{anchor|neo}} {{AFC submission/table|neo}} {{anchor|web}} {{AFC submission/table|web}} {{anchor|prof|academic}} {{AFC submission/table|prof|academic}} {{anchors|athlete|sport}} {{AFC submission/table|athlete|sport}} {{anchors|music|m|band}} {{AFC submission/table|music|m|band}} {{anchor|film}} {{AFC submission/table|film}} {{anchors|corp|inc|org}} {{AFC submission/table|book}} {{AFC submission/table|event}} {{AFC submission/table|corp|inc|org}} {{anchor|bio}} {{AFC submission/table|bio}} {{anchor|nn}} {{AFC submission/table|nn}} {{AFC submission/table|list}} {{AFC submission/table|med}} {{AFC submission/table|geo}} {{AFC submission/table|species}} {{AFC submission/table|astro}} {{AFC submission/table|number}} {{AFC submission/table|creative}} {{AFC submission/table|school}} |- ! colspan=2 | Invalid submissions {{anchors|cat}} {{AFC submission/table|cat}} {{anchor|empty|blank}} {{AFC submission/table|empty|blank}} {{anchor|lang}} {{AFC submission/table|lang|notenglish|input=language}} {{anchor|test}} {{AFC submission/table|test}} {{anchor|redirect}} {{AFC submission/table|redirect}} |- ! colspan=2 | BLP and vandalism {{anchor|van}} {{AFC submission/table|van}} {{anchor|ilc}} {{AFC submission/table|ilc}} {{anchor|blp}} {{AFC submission/table|blp}} |- ! colspan=2 | Submission content {{anchors|cv|cv-n}} {{AFC submission/table|cv|cv-n}} {{anchors|cv_2|cv-n_2}} {{AFC submission/table|cv|cv-n|input=Website}} {{anchor|cv-cleaned|cv-c}} {{AFC submission/table|cv-cleaned|cv-c}} {{anchor|cv-bot}} {{AFC submission/table|cv-bot}} {{anchors|source|v|rs}} {{AFC submission/table|source|v|rs}} {{anchor|not}} {{AFC submission/table|not}} {{anchors|news|oneevent}} {{AFC submission/table|news|oneevent}} {{anchors|dict|d}} {{AFC submission/table|dict|d}} {{anchor|plot}} {{AFC submission/table|plot}} {{anchor|plot_2}} {{AFC submission/table|plot|input=Pagename}} {{anchor|joke}} {{AFC submission/table|joke}} {{AFC submission/table|ai|llm}} {{anchors|ai|llm}} {{AFC submission/table|resume}} {{AFC submission/table|ecr}} {{AFC submission/table|nosource}} |- ! colspan=2 | Prose issues {{anchor|essay}} {{AFC submission/table|essay}} {{anchor|npov}} {{AFC submission/table|npov}} {{anchors|advert|spam|adv}} {{AFC submission/table|advert|spam|adv}} |- ! colspan=2 | Duplicate articles {{anchor|exists}} {{AFC submission/table|exists}} {{anchor|exists_2}} {{AFC submission/table|exists|input=Pagename}} {{anchors|duplicate|dup}} {{AFC submission/table|duplicate|dup}} {{anchors|duplicate_2|dup_2}} {{AFC submission/table|duplicate|dup|input=Title}} |- ! colspan=2 | Other {{anchor|context}} {{AFC submission/table|context}} {{anchors|mergeto|merge}} {{AFC submission/table|mergeto|merge}} {{anchor|r}} {{AFC submission/table|mergeto|merge|input=Pagename}} {{anchor|afd}} {{AFC submission/table|afd}} {{anchor|reason}} {{AFC submission/table|reason|input=Reason}} |} <includeonly>{{Sandbox other|| <!-- Categories go here, and interwikis go in Wikidata --> {{DEFAULTSORT:*}} [[Category:WikiProject Articles for creation]] }}</includeonly> rmabeuudsfa9ze1f9xwa9zz65hpknzc User talk:Novem Linguae 3 122929 740108 721322 2026-05-01T20:19:40Z Novem Linguae 49714 Notification: Your [[Draft:NovemTest|Articles for Creation submission]] has been rejected 740108 wikitext text/x-wiki {{Talk header}} == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (November 27) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This submission appears to [[WP:Spam#Advertisements masquerading as articles|read more like an advertisement]] than an entry in an encyclopedia. Encyclopedia articles need to be written from a [[Wikipedia:Neutral point of view|neutral point of view]], and should refer to a range of [[Wikipedia:Reliable sources|independent, reliable, published sources]], not just to materials produced by the creator of the subject being discussed. This is important so that the article can meet Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] and the [[Wikipedia:Notability|notability]] of the subject can be established. If you still feel that this subject is worthy of inclusion in Wikipedia, please rewrite your submission to comply with these policies.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 07:20, 27 November 2024 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Jon Snow (fictional character)|Jon Snow (fictional character)]] (March 18) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by JoBoGamer was: {{divbox|gray|3=This submission is not suitable for Wikipedia. Please read [[Wikipedia:What Wikipedia is not|"What Wikipedia is not"]] for more information.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Jon Snow (fictional character)]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Jon_Snow_(fictional_character) '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:JoBoGamer&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Jon_Snow_(fictional_character) '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div><!--Template:AfC decline--> {| style="margin: 0.4em 2em;" |- style="vertical-align: top;" | [[File:WP teahouse logo 2.png|alt=Teahouse logo]] | <div style="background-color:#e1e6db; color: #393D38; padding: 1em; font-size: 1.1em; border-radius:10px;box-shadow:-2px -2px 1px #8e8a78;">Hello, '''Novem Linguae'''! Having an article draft declined at Articles for Creation can be disappointing. If you are wondering why your article submission was declined, please post a question at the '''[[Wikipedia:WikiProject Articles for creation/Help desk|Articles for creation help desk]]'''. If you have any ''other'' questions about your editing experience, we'd love to help you at the '''[[Wikipedia:Teahouse|Teahouse]]''', a friendly space on Wikipedia where experienced editors lend a hand to help new editors like yourself! See you there! [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div> |}<!-- Wikipedia:Teahouse/AfC Invitation --> [[Category:Wikipedians who have received a Teahouse invitation through AfC]] == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Test|Test]] (May 21) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This is not the correct place to request new categories. Please follow the instructions at [[Wikipedia:Articles for creation/Categories]]. Thank you.|}}<!-- -- -->&nbsp;The comment the reviewer left was: {{divbox|blue|3=Test}} Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Test]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Test '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Test '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:30, 21 May 2025 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[NovemTest]] has been accepted == {{subst:Afc talk|NovemTest|class=|sig=–[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:42, 21 May 2025 (UTC)}} ==[[Wikipedia:Criteria for speedy deletion|Speedy deletion]] nomination of [[:Mainspace]]== [[File:Information icon4.svg|48px|left|alt=|link=]] Thank you for your interest in <strong class="error">No contentious topic code has been specified</strong>. Due to past disruption, a [[WP:GS|special set of rules]] apply to anyone editing in this topic area. Specifically, <strong class="error">No contentious topic code has been specified</strong>. Since you currently do not meet the requirements to contribute to this topic area, the page you created has been nominated for speedy deletion under [[WP:G5|G5]]. If you think this page does not fall under the general sanctions described, you may '''contest the nomination''' by [[:Mainspace|visiting the page]] and clicking the button labelled "Contest this speedy deletion". This will give you the opportunity to explain why you believe the page should not be deleted. However, be aware that once a page is tagged for speedy deletion, it may be deleted without delay. Please do not remove the speedy deletion tag from the page yourself, but do not hesitate to add information in line with [[Wikipedia:List of policies|Wikipedia's policies and guidelines]]. Additionally you may wish to read about the topic restrictions <strong class="error">No contentious topic code has been specified</strong>.<!-- Template:Db-gs-notice --><!-- Template:Db-csd-notice-custom --> [[User:Chaotic Enby|Chaotic Enby]] ([[User talk:Chaotic Enby|talk]]) 16:42, 3 November 2025 (UTC) == Deletion discussion about [[Mainspace]] == {{subst:AfD-notice-NPF|Mainspace|Mainspace (18th nomination)}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 13:49, 30 December 2025 (UTC) == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == {{subst:Afc reject|full=Draft:NovemTest|reason=n|details=|reason2=|details2=|comment=|sig=yes}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:19, 1 May 2026 (UTC) scash0trsky4aodb8vymwoskmhoun1s 740111 740108 2026-05-01T20:21:43Z Novem Linguae 49714 Notification: Your [[Draft:NovemTest|Articles for Creation submission]] has been declined 740111 wikitext text/x-wiki {{Talk header}} == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (November 27) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This submission appears to [[WP:Spam#Advertisements masquerading as articles|read more like an advertisement]] than an entry in an encyclopedia. Encyclopedia articles need to be written from a [[Wikipedia:Neutral point of view|neutral point of view]], and should refer to a range of [[Wikipedia:Reliable sources|independent, reliable, published sources]], not just to materials produced by the creator of the subject being discussed. This is important so that the article can meet Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] and the [[Wikipedia:Notability|notability]] of the subject can be established. If you still feel that this subject is worthy of inclusion in Wikipedia, please rewrite your submission to comply with these policies.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 07:20, 27 November 2024 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Jon Snow (fictional character)|Jon Snow (fictional character)]] (March 18) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by JoBoGamer was: {{divbox|gray|3=This submission is not suitable for Wikipedia. Please read [[Wikipedia:What Wikipedia is not|"What Wikipedia is not"]] for more information.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Jon Snow (fictional character)]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Jon_Snow_(fictional_character) '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:JoBoGamer&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Jon_Snow_(fictional_character) '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div><!--Template:AfC decline--> {| style="margin: 0.4em 2em;" |- style="vertical-align: top;" | [[File:WP teahouse logo 2.png|alt=Teahouse logo]] | <div style="background-color:#e1e6db; color: #393D38; padding: 1em; font-size: 1.1em; border-radius:10px;box-shadow:-2px -2px 1px #8e8a78;">Hello, '''Novem Linguae'''! Having an article draft declined at Articles for Creation can be disappointing. If you are wondering why your article submission was declined, please post a question at the '''[[Wikipedia:WikiProject Articles for creation/Help desk|Articles for creation help desk]]'''. If you have any ''other'' questions about your editing experience, we'd love to help you at the '''[[Wikipedia:Teahouse|Teahouse]]''', a friendly space on Wikipedia where experienced editors lend a hand to help new editors like yourself! See you there! [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div> |}<!-- Wikipedia:Teahouse/AfC Invitation --> [[Category:Wikipedians who have received a Teahouse invitation through AfC]] == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Test|Test]] (May 21) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This is not the correct place to request new categories. Please follow the instructions at [[Wikipedia:Articles for creation/Categories]]. Thank you.|}}<!-- -- -->&nbsp;The comment the reviewer left was: {{divbox|blue|3=Test}} Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Test]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Test '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Test '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:30, 21 May 2025 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[NovemTest]] has been accepted == {{subst:Afc talk|NovemTest|class=|sig=–[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:42, 21 May 2025 (UTC)}} ==[[Wikipedia:Criteria for speedy deletion|Speedy deletion]] nomination of [[:Mainspace]]== [[File:Information icon4.svg|48px|left|alt=|link=]] Thank you for your interest in <strong class="error">No contentious topic code has been specified</strong>. Due to past disruption, a [[WP:GS|special set of rules]] apply to anyone editing in this topic area. Specifically, <strong class="error">No contentious topic code has been specified</strong>. Since you currently do not meet the requirements to contribute to this topic area, the page you created has been nominated for speedy deletion under [[WP:G5|G5]]. If you think this page does not fall under the general sanctions described, you may '''contest the nomination''' by [[:Mainspace|visiting the page]] and clicking the button labelled "Contest this speedy deletion". This will give you the opportunity to explain why you believe the page should not be deleted. However, be aware that once a page is tagged for speedy deletion, it may be deleted without delay. Please do not remove the speedy deletion tag from the page yourself, but do not hesitate to add information in line with [[Wikipedia:List of policies|Wikipedia's policies and guidelines]]. Additionally you may wish to read about the topic restrictions <strong class="error">No contentious topic code has been specified</strong>.<!-- Template:Db-gs-notice --><!-- Template:Db-csd-notice-custom --> [[User:Chaotic Enby|Chaotic Enby]] ([[User talk:Chaotic Enby|talk]]) 16:42, 3 November 2025 (UTC) == Deletion discussion about [[Mainspace]] == {{subst:AfD-notice-NPF|Mainspace|Mainspace (18th nomination)}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 13:49, 30 December 2025 (UTC) == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == {{subst:Afc reject|full=Draft:NovemTest|reason=n|details=|reason2=|details2=|comment=|sig=yes}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:19, 1 May 2026 (UTC) == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:21, 1 May 2026 (UTC)</div><!--Template:AfC decline--> e73us5kmag77ahgbye57u5y1q6031da 740114 740111 2026-05-01T20:23:54Z Novem Linguae 49714 Notification: Your [[Draft:NovemTest|Articles for Creation submission]] has been declined 740114 wikitext text/x-wiki {{Talk header}} == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (November 27) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This submission appears to [[WP:Spam#Advertisements masquerading as articles|read more like an advertisement]] than an entry in an encyclopedia. Encyclopedia articles need to be written from a [[Wikipedia:Neutral point of view|neutral point of view]], and should refer to a range of [[Wikipedia:Reliable sources|independent, reliable, published sources]], not just to materials produced by the creator of the subject being discussed. This is important so that the article can meet Wikipedia's [[Wikipedia:Verifiability|verifiability policy]] and the [[Wikipedia:Notability|notability]] of the subject can be established. If you still feel that this subject is worthy of inclusion in Wikipedia, please rewrite your submission to comply with these policies.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 07:20, 27 November 2024 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Jon Snow (fictional character)|Jon Snow (fictional character)]] (March 18) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by JoBoGamer was: {{divbox|gray|3=This submission is not suitable for Wikipedia. Please read [[Wikipedia:What Wikipedia is not|"What Wikipedia is not"]] for more information.|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Jon Snow (fictional character)]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Jon_Snow_(fictional_character) '''Articles for creation help desk''']</span>, on the <span class="plainlinks">[//test.wikipedia.org/w/index.php?title=User_talk:JoBoGamer&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Jon_Snow_(fictional_character) '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div><!--Template:AfC decline--> {| style="margin: 0.4em 2em;" |- style="vertical-align: top;" | [[File:WP teahouse logo 2.png|alt=Teahouse logo]] | <div style="background-color:#e1e6db; color: #393D38; padding: 1em; font-size: 1.1em; border-radius:10px;box-shadow:-2px -2px 1px #8e8a78;">Hello, '''Novem Linguae'''! Having an article draft declined at Articles for Creation can be disappointing. If you are wondering why your article submission was declined, please post a question at the '''[[Wikipedia:WikiProject Articles for creation/Help desk|Articles for creation help desk]]'''. If you have any ''other'' questions about your editing experience, we'd love to help you at the '''[[Wikipedia:Teahouse|Teahouse]]''', a friendly space on Wikipedia where experienced editors lend a hand to help new editors like yourself! See you there! [[User:JoBoGamer|JoBoGamer]] ([[User talk:JoBoGamer|talk]]) 10:57, 18 March 2025 (UTC)</div> |}<!-- Wikipedia:Teahouse/AfC Invitation --> [[Category:Wikipedians who have received a Teahouse invitation through AfC]] == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:Test|Test]] (May 21) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This is not the correct place to request new categories. Please follow the instructions at [[Wikipedia:Articles for creation/Categories]]. Thank you.|}}<!-- -- -->&nbsp;The comment the reviewer left was: {{divbox|blue|3=Test}} Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:Test]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:Test '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:Test '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:30, 21 May 2025 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[NovemTest]] has been accepted == {{subst:Afc talk|NovemTest|class=|sig=–[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 02:42, 21 May 2025 (UTC)}} ==[[Wikipedia:Criteria for speedy deletion|Speedy deletion]] nomination of [[:Mainspace]]== [[File:Information icon4.svg|48px|left|alt=|link=]] Thank you for your interest in <strong class="error">No contentious topic code has been specified</strong>. Due to past disruption, a [[WP:GS|special set of rules]] apply to anyone editing in this topic area. Specifically, <strong class="error">No contentious topic code has been specified</strong>. Since you currently do not meet the requirements to contribute to this topic area, the page you created has been nominated for speedy deletion under [[WP:G5|G5]]. If you think this page does not fall under the general sanctions described, you may '''contest the nomination''' by [[:Mainspace|visiting the page]] and clicking the button labelled "Contest this speedy deletion". This will give you the opportunity to explain why you believe the page should not be deleted. However, be aware that once a page is tagged for speedy deletion, it may be deleted without delay. Please do not remove the speedy deletion tag from the page yourself, but do not hesitate to add information in line with [[Wikipedia:List of policies|Wikipedia's policies and guidelines]]. Additionally you may wish to read about the topic restrictions <strong class="error">No contentious topic code has been specified</strong>.<!-- Template:Db-gs-notice --><!-- Template:Db-csd-notice-custom --> [[User:Chaotic Enby|Chaotic Enby]] ([[User talk:Chaotic Enby|talk]]) 16:42, 3 November 2025 (UTC) == Deletion discussion about [[Mainspace]] == {{subst:AfD-notice-NPF|Mainspace|Mainspace (18th nomination)}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 13:49, 30 December 2025 (UTC) == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == {{subst:Afc reject|full=Draft:NovemTest|reason=n|details=|reason2=|details2=|comment=|sig=yes}} –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:19, 1 May 2026 (UTC) == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:21, 1 May 2026 (UTC)</div><!--Template:AfC decline--> == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:23, 1 May 2026 (UTC)</div><!--Template:AfC decline--> nkyidxoszwe9cet4qrzhnk8npfxcazb 740115 740114 2026-05-01T20:24:08Z Novem Linguae 49714 Blanked the page 740115 wikitext text/x-wiki phoiac9h4m842xq45sp7s6u21eteeq1 740118 740115 2026-05-01T20:24:49Z Novem Linguae 49714 Notification: Your [[Draft:NovemTest|Articles for Creation submission]] has been declined 740118 wikitext text/x-wiki == Your submission at [[Wikipedia:Articles for creation|Articles for creation]]: [[Draft:NovemTest|NovemTest]] (May 1) == <div style="border: solid 1px #FCC; background-color: #F8EEBC; padding: 0.5em 1em; color: #000; margin: 1.5em; width: 90%;"> [[File:AFC-Logo_Decline.svg|50px|left]]Your recent article submission to [[Wikipedia:Articles for creation|Articles for Creation]] has been reviewed. Unfortunately, it has not been accepted at this time.<nowiki> </nowiki>The reason left by Novem Linguae was: {{divbox|gray|3=This draft reads like an advertisement. Wikipedia is an encyclopedia, [[Wikipedia:Spam#Advertisements masquerading as articles|not a platform for promotion or marketing]]. Drafts that are [[Wikipedia:G11|exclusively promotional may be deleted without notice]]. Wikipedia articles must be written neutrally in a formal, impersonal, and dispassionate way. They should not read like a blog post, advertisement, or fan page. Rewrite the draft to remove: * ''promotional language'': see [[Wikipedia:Manual of Style/Words to watch|Words to watch]]; * ''personal commentary'': opinions or direct addresses to the reader; * ''[[Wikipedia:Tone|informal language]].'' Instead, only [[Wikipedia:Writing better articles#Information style and tone|summarize in your own words a range of independent, reliable, published sources]] that discuss the subject. If you have a [[Wikipedia:Conflict of interest|conflict of interest]] (e.g. you are the subject, an employee, or a relative) or are [[Wikipedia:Paid-contribution disclosure|being paid to edit]], you must disclose this to comply with Wikipedia's [[Wikipedia:Terms of Use|Terms of Use]].|}}<!-- -- --> Please check the submission for any additional comments left by the reviewer. You are encouraged to edit the submission to address the issues raised and resubmit ''after they have been resolved''. {{clear}} * If you would like to continue working on the submission, go to [[Draft:NovemTest]] and click on the "Edit" tab at the top of the window. * If you do not edit your draft in the next 6 months, it will be considered abandoned and [[Wikipedia:Criteria for speedy deletion#G13. Abandoned drafts and Articles for creation submissions|may be deleted]]. * If you need any assistance, or have experienced any [[Wikipedia:Articles for creation/Scam warning|untoward behavior]] associated with this submission, you can ask for help at the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=Wikipedia:WikiProject_Articles_for_creation/Help_desk/New_question&withJS=MediaWiki:AFCHD-wizard.js&page=Draft:NovemTest '''Articles for creation help desk''']</span>, on the <span class="plainlinks" >[//test.wikipedia.org/w/index.php?title=User_talk:Novem_Linguae&action=edit&section=new&nosummary=1&preload=Template:AfC_decline/HD_preload&preloadparams%5B%5D=Draft:NovemTest '''reviewer's talk page''']</span> or use [[Wikipedia:IRC help disclaimer|Wikipedia's real-time chat help from experienced editors]]. –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:24, 1 May 2026 (UTC)</div><!--Template:AfC decline--> {| style="margin: 0.4em 2em;" |- style="vertical-align: top;" | [[File:WP teahouse logo 2.png|alt=Teahouse logo]] | <div style="background-color:#e1e6db; color: #393D38; padding: 1em; font-size: 1.1em; border-radius:10px;box-shadow:-2px -2px 1px #8e8a78;">Hello, '''Novem Linguae'''! Having an article draft declined at Articles for Creation can be disappointing. If you are wondering why your article submission was declined, please post a question at the '''[[Wikipedia:WikiProject Articles for creation/Help desk|Articles for creation help desk]]'''. If you have any ''other'' questions about your editing experience, we'd love to help you at the '''[[Wikipedia:Teahouse|Teahouse]]''', a friendly space on Wikipedia where experienced editors lend a hand to help new editors like yourself! See you there! –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:24, 1 May 2026 (UTC)</div> |}<!-- Wikipedia:Teahouse/AfC Invitation --> [[Category:Wikipedians who have received a Teahouse invitation through AfC]] 99t3h5sld9bsu5h4psw0a35v4ox7dfz User:SongVĩ.Bot II 2 124239 740038 739887 2026-05-01T17:00:15Z SongVĩ.Bot II 52414 [[User:SongVĩ.Bot II|Task 0]]: Đã 1586 ngày... 740038 wikitext text/x-wiki Cập nhật lần cuối: 02-05-2026 Đã 1586 ngày... qra80wmeq4yrskj1nkcuok9r8tc0nr5 Template:Transclusionless 10 125588 740088 665105 2026-04-09T03:27:09Z en>Andrybak 0 remove pages like [[Template:Parameter typo documentation/testcases]] from [[Category:Wikipedia transclusionless templates]] 740088 wikitext text/x-wiki {{Mbox|text=This template {{Yesno|{{{should|}}}|yes=should|no=may}} have no [[Wikipedia:Transclusion|transclusions]]. This is because {{{reason|it is substituted by a tool or script, it is used as part of a short-term or less active Wikipedia process, or for some other reason}}}.}}<!-- --><includeonly>__EXPECTUNUSEDTEMPLATE__{{#switch: {{SUBPAGENAME}} | testcases |doc |sandbox= |{{module other | [[Category:Wikipedia transclusionless modules]] | [[Category:Wikipedia transclusionless templates]] }}}}</includeonly><noinclude> {{Documentation}} </noinclude> lf3v59v95zajhzsjgic1v3ohe6skaaf 740089 740088 2026-05-01T19:28:21Z Novem Linguae 49714 1 revision imported from [[:en:Template:Transclusionless]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740088 wikitext text/x-wiki {{Mbox|text=This template {{Yesno|{{{should|}}}|yes=should|no=may}} have no [[Wikipedia:Transclusion|transclusions]]. This is because {{{reason|it is substituted by a tool or script, it is used as part of a short-term or less active Wikipedia process, or for some other reason}}}.}}<!-- --><includeonly>__EXPECTUNUSEDTEMPLATE__{{#switch: {{SUBPAGENAME}} | testcases |doc |sandbox= |{{module other | [[Category:Wikipedia transclusionless modules]] | [[Category:Wikipedia transclusionless templates]] }}}}</includeonly><noinclude> {{Documentation}} </noinclude> lf3v59v95zajhzsjgic1v3ohe6skaaf Template:AfC submission/reject reasons 10 153905 740098 584352 2026-04-04T14:43:56Z en>Novem Linguae 0 re-add accidentally deleted code 740098 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |n = The subject [[Wikipedia:Notability|does not meet Wikipedia's criteria for inclusion]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as non-notable]]}}</includeonly> |e = The subject is [[Wikipedia:What Wikipedia is not|contrary to the purpose of Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as unencyclopedic]]}}</includeonly> |j = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as jokes or hoaxes]]}}</includeonly> |blp = This draft appears to be an [[Wikipedia:Attack page|attack page]], created primarily to disparage, threaten, or harass a subject. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as attack pages]]}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> jhr0t6jfwoa6lzr5l7hfcouvemymdqf 740099 740098 2026-05-01T19:43:36Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/reject_reasons]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740098 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |n = The subject [[Wikipedia:Notability|does not meet Wikipedia's criteria for inclusion]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as non-notable]]}}</includeonly> |e = The subject is [[Wikipedia:What Wikipedia is not|contrary to the purpose of Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as unencyclopedic]]}}</includeonly> |j = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative.<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as jokes or hoaxes]]}}</includeonly> |blp = This draft appears to be an [[Wikipedia:Attack page|attack page]], created primarily to disparage, threaten, or harass a subject. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as attack pages]]}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> jhr0t6jfwoa6lzr5l7hfcouvemymdqf 740102 740099 2026-05-01T19:46:34Z en>Novem Linguae 0 debug 740102 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |n = The subject [[Wikipedia:Notability|does not meet Wikipedia's criteria for inclusion]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as non-notable]]}}</includeonly> |e = The subject is [[Wikipedia:What Wikipedia is not|contrary to the purpose of Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as unencyclopedic]]}}</includeonly> |j = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative. {{pb}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as jokes or hoaxes]]}}</includeonly> |blp = This draft appears to be an [[Wikipedia:Attack page|attack page]], created primarily to disparage, threaten, or harass a subject. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as attack pages]]}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> h19clfnjelo0sbf08g0wdls5ek80fav 740103 740102 2026-05-01T19:59:23Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/reject_reasons]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740102 wikitext text/x-wiki {{safesubst:<noinclude />#switch:{{safesubst:<noinclude />lc:{{{1}}}}} |n = The subject [[Wikipedia:Notability|does not meet Wikipedia's criteria for inclusion]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as non-notable]]}}</includeonly> |e = The subject is [[Wikipedia:What Wikipedia is not|contrary to the purpose of Wikipedia]].<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as unencyclopedic]]}}</includeonly> |j = This draft appears to be a joke or a hoax. Wikipedia is an encyclopedia. We do not accept: * ''[[Wikipedia:Do not create hoaxes|hoaxes]]'': made-up stories presented as fact; * ''humor'': content intended to be funny rather than informative. {{pb}}<includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as jokes or hoaxes]]}}</includeonly> |blp = This draft appears to be an [[Wikipedia:Attack page|attack page]], created primarily to disparage, threaten, or harass a subject. <includeonly>{{safesubst:<noinclude />#ifeq:{{{cat|}}}|no||[[Category:AfC submissions rejected as attack pages]]}}</includeonly> |#default = {{{1}}} }}<noinclude> {{Documentation}} <!-- Categories go on the /doc subpage, and interwikis go on Wikidata. --> </noinclude> h19clfnjelo0sbf08g0wdls5ek80fav Template:AfC submission/reject reasons/doc 10 153906 740100 584354 2026-04-04T11:32:30Z en>Primefac 0 /* Usage */ upd. 740100 wikitext text/x-wiki {{Documentation subpage}} <!-- Please place categories where indicated at the bottom of this page and interwikis at Wikidata (see [[Wikipedia:Wikidata]]) --> == Usage == Helper template for {{tl|AFC submission/rejected}}, to be used ''only'' by that template. This template is similar to {{tl|AFC submission/comments}}, but with rejection comments instead of decline comments. These are the possible comments when using {{tlx|AFC submission|D|reject{{=}}yes}}: {| class="wikitable" ! code || Comment |- | ''n'' || {{AFC submission/reject reasons|n|cat=no}} |- | ''e'' || {{AFC submission/reject reasons|e|cat=no}} |- | ''j'' || {{AFC submission/reject reasons|j|cat=no}} |- | ''blp'' || {{AFC submission/reject reasons|blp|cat=no}} |} == See also == * {{tl|AFC submission}} <includeonly>{{sandbox other|| <!-- Categories below this line, please; interwikis at Wikidata --> [[Category:WikiProject Articles for creation]] }}</includeonly> cl9tjj04onpcqif6dykwb6ujtsox30t 740101 740100 2026-05-01T19:43:36Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/reject_reasons/doc]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740100 wikitext text/x-wiki {{Documentation subpage}} <!-- Please place categories where indicated at the bottom of this page and interwikis at Wikidata (see [[Wikipedia:Wikidata]]) --> == Usage == Helper template for {{tl|AFC submission/rejected}}, to be used ''only'' by that template. This template is similar to {{tl|AFC submission/comments}}, but with rejection comments instead of decline comments. These are the possible comments when using {{tlx|AFC submission|D|reject{{=}}yes}}: {| class="wikitable" ! code || Comment |- | ''n'' || {{AFC submission/reject reasons|n|cat=no}} |- | ''e'' || {{AFC submission/reject reasons|e|cat=no}} |- | ''j'' || {{AFC submission/reject reasons|j|cat=no}} |- | ''blp'' || {{AFC submission/reject reasons|blp|cat=no}} |} == See also == * {{tl|AFC submission}} <includeonly>{{sandbox other|| <!-- Categories below this line, please; interwikis at Wikidata --> [[Category:WikiProject Articles for creation]] }}</includeonly> cl9tjj04onpcqif6dykwb6ujtsox30t Draft:NovemTest 118 155404 740067 735309 2026-05-01T19:25:59Z Novem Linguae 49714 Rejecting submission: j - Topic is a joke or hoax 740067 wikitext text/x-wiki {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> idkdkwpkqbugy4hnhzk2lt5pmhgyjrn 740104 740067 2026-05-01T19:59:42Z Novem Linguae 49714 Submitting 740104 wikitext text/x-wiki {{AFC submission||ns=118|u=Novem Linguae|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> kizgjzpbk936twvve7ju3b74y4wmr4f 740105 740104 2026-05-01T20:00:13Z Novem Linguae 49714 Rejecting submission: blp - Topic is an attack page 740105 wikitext text/x-wiki {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> n9ujol71qda0dbqre7elwtkeohbbfl7 740106 740105 2026-05-01T20:01:37Z Novem Linguae 49714 Submitting 740106 wikitext text/x-wiki {{AFC submission||ns=118|u=Novem Linguae|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> 9rjyzaowicpgcmwte2v0qg5yfkjl3in 740107 740106 2026-05-01T20:19:39Z Novem Linguae 49714 Rejecting submission: n - Topic is not notable 740107 wikitext text/x-wiki {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> 7p4rpc1zd1cdb6oy5t1lkanuab2yw3f 740109 740107 2026-05-01T20:21:06Z Novem Linguae 49714 Submitting 740109 wikitext text/x-wiki {{AFC submission||ns=118|u=Novem Linguae|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> sue369xi7y2vu1amg4jr4q8eqlrg1lo 740110 740109 2026-05-01T20:21:42Z Novem Linguae 49714 Declining submission: adv - Submission reads like an advertisement 740110 wikitext text/x-wiki {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202142|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|small=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> c3xkytpqf5fii9iln4c5sg3gxb4acxs 740112 740110 2026-05-01T20:23:41Z Novem Linguae 49714 Submitting 740112 wikitext text/x-wiki {{AFC submission||ns=118|u=Novem Linguae|ts=20260501202341}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202142|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|small=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> tqikhcduazgai0wwm5yrx2h2uo3c3n6 740113 740112 2026-05-01T20:23:53Z Novem Linguae 49714 Declining submission: adv - Submission reads like an advertisement 740113 wikitext text/x-wiki {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202353|ts=20260501202341}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202142|small=yes|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|small=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> 8urfjwvxve8y6yqbhwmnunhtzxjc644 740116 740113 2026-05-01T20:24:36Z Novem Linguae 49714 Submitting 740116 wikitext text/x-wiki {{AFC submission||ns=118|u=Novem Linguae|ts=20260501202436}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202353|ts=20260501202341}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202142|small=yes|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|small=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> 1gefpp5srdtd1r2mj28q9a6kf4k3fhr 740117 740116 2026-05-01T20:24:48Z Novem Linguae 49714 Declining submission: adv - Submission reads like an advertisement 740117 wikitext text/x-wiki {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202448|ts=20260501202436}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202353|small=yes|ts=20260501202341}} <!-- Do not remove this line! --> {{AFC submission|d|adv|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501202142|small=yes|ts=20260501202106}} <!-- Do not remove this line! --> {{AFC submission|d|n|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501201939|reject=yes|small=yes|ts=20260501200137}} <!-- Do not remove this line! --> {{AFC submission|d|blp|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501200013|reject=yes|small=yes|ts=20260501195942}} <!-- Do not remove this line! --> {{AFC submission|d|j|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260501192559|reject=yes|small=yes|ts=20260314173031}} <!-- Do not remove this line! --> {{AFC submission|d|creative|ns=118|u=Novem Linguae|decliner=Novem Linguae|declinets=20260314172810|small=yes|ts=20260314172453}} <!-- Do not remove this line! --> {{AFC submission|d|ns|ns=118|decliner=Novem Linguae|declinets=20260314172427|small=yes|ts=}} <!-- Do not remove this line! --> {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 20:00, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 19:25, 1 May 2026 (UTC)}} {{AFC comment|1=Test –[[User:Novem Linguae|<span style="color:blue">'''Novem Linguae'''</span>]] <small>([[User talk:Novem Linguae|talk]])</small> 17:24, 14 March 2026 (UTC)}} ---- Test <!-- Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment Long hidden HTML comment --> 8eqq0qbecn3nh077dy565zl7eddwidu User:ToprakM/common.css 2 162878 739984 635788 2026-05-01T13:13:05Z ToprakM 35316 739984 css text/css @import url("/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); hcs4gkr48gfyxlxhd7tzkkbd72xl4w9 739986 739984 2026-05-01T13:13:56Z ToprakM 35316 739986 css text/css @import url("/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); background-color: red; ncvej4djqksahqm0v31gshaguolb3ek 739988 739986 2026-05-01T13:15:34Z ToprakM 35316 739988 css text/css @import url("/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); body, #mw-page-base, #content { background-color: purple !important; } ikwq82wjiniofujilmk3212mxauphxu 739989 739988 2026-05-01T13:15:49Z ToprakM 35316 739989 css text/css @import url("/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); hcs4gkr48gfyxlxhd7tzkkbd72xl4w9 739990 739989 2026-05-01T13:18:14Z ToprakM 35316 739990 css text/css @import url("/index.php?action=raw&ctype=text/css&title=User:ToprakM/test.css"); pyjgosbsb4eve1t5ubs0hzjvgn5lnut 739991 739990 2026-05-01T13:19:41Z ToprakM 35316 739991 css text/css @import url("User:ToprakM/test.css"); 9h6vxwhol955rt0yiejhfkqo949np94 739992 739991 2026-05-01T13:21:44Z ToprakM 35316 739992 css text/css @import url("/index.php?action=raw&ctype=text/css&title=User:ToprakM/test.css"); pyjgosbsb4eve1t5ubs0hzjvgn5lnut 739993 739992 2026-05-01T13:23:50Z ToprakM 35316 739993 css text/css @import url("https://test.wikipedia.org/w/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); 3vizexvnyyuvsrhzuvpawdjqmjfgar5 739994 739993 2026-05-01T13:24:02Z ToprakM 35316 739994 css text/css @import url("/w/index.php?title=User:ToprakM/test.css&action=raw&ctype=text/css"); di2oflz8nyxe74621d8jafp2k8p9qbh Template:AfC submission/styles.css 10 166304 740092 658468 2026-03-31T05:10:41Z en>Oshwah 0 Increase title size to make easier for new users to see and read. 740092 sanitized-css text/css /* {{pp-template|small=yes}} */ .afc-submission-header { font-weight: bold; font-size: 135%; text-align: center; } .afc-submission-g13 { text-align: center; background-color: #fcc; border: 1px solid #faa; margin: 0.5em; padding: 0.5em; font-weight: bold; color: #202122; } .afc-submission-advice { border-top: 1px #AAA solid; margin-top: 5px; padding-top: 5px } .afc-submission-pending { background-color: #f3eba3; } .afc-submission-declined, .afc-submission-rejected { background-color: #fee; } .afc-submission-reviewing { background-color: #b1dae8; } .afc-submission-declined-comment { background-color: var(--background-color-neutral-subtle, #f8f9fa); border: 1px solid #999; margin: 0.5em; padding: 1em; } @media screen { html.skin-theme-clientpref-night .afc-submission-pending { background-color: #381C03; } html.skin-theme-clientpref-night .afc-submission-declined, html.skin-theme-clientpref-night .afc-submission-rejected { background-color: #300; } html.skin-theme-clientpref-night .afc-submission-reviewing { background-color: #0d1a27; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .afc-submission-pending { background-color: #381C03; } html.skin-theme-clientpref-os .afc-submission-declined, html.skin-theme-clientpref-os .afc-submission-rejected { background-color: #300; } html.skin-theme-clientpref-os .afc-submission-reviewing { background-color: #0d1a27; } } 4976kyexml6iq95bq9fow1d1x7ni819 740093 740092 2026-05-01T19:28:21Z Novem Linguae 49714 1 revision imported from [[:en:Template:AfC_submission/styles.css]]: Copied content from another wiki; see the linked page's history for attribution ([[w:User:Novem Linguae/Scripts/CWWEditSummary.js|CWWEditSummary]]) 740092 sanitized-css text/css /* {{pp-template|small=yes}} */ .afc-submission-header { font-weight: bold; font-size: 135%; text-align: center; } .afc-submission-g13 { text-align: center; background-color: #fcc; border: 1px solid #faa; margin: 0.5em; padding: 0.5em; font-weight: bold; color: #202122; } .afc-submission-advice { border-top: 1px #AAA solid; margin-top: 5px; padding-top: 5px } .afc-submission-pending { background-color: #f3eba3; } .afc-submission-declined, .afc-submission-rejected { background-color: #fee; } .afc-submission-reviewing { background-color: #b1dae8; } .afc-submission-declined-comment { background-color: var(--background-color-neutral-subtle, #f8f9fa); border: 1px solid #999; margin: 0.5em; padding: 1em; } @media screen { html.skin-theme-clientpref-night .afc-submission-pending { background-color: #381C03; } html.skin-theme-clientpref-night .afc-submission-declined, html.skin-theme-clientpref-night .afc-submission-rejected { background-color: #300; } html.skin-theme-clientpref-night .afc-submission-reviewing { background-color: #0d1a27; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .afc-submission-pending { background-color: #381C03; } html.skin-theme-clientpref-os .afc-submission-declined, html.skin-theme-clientpref-os .afc-submission-rejected { background-color: #300; } html.skin-theme-clientpref-os .afc-submission-reviewing { background-color: #0d1a27; } } 4976kyexml6iq95bq9fow1d1x7ni819 User:Supertian8 2 166966 740040 739397 2026-05-01T17:11:06Z Supertian8 67751 gooden linkboxes 740040 wikitext text/x-wiki = win = NaNNaN{{subst:<noinclude/>#ifeq:{{{nxtchap|none}}}|none | |{{subst:<noinclude/>!}}nxtchap={{{nxtchap}}}}} <div style="display: flex; justify-content: flex-end;"> <div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -326px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Powered_by_Miraheze_(no_box).svg|link=meta:Special:MyLanguage/Miraheze Meta]]</div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -215px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Cc-by-sa.png|link=https://creativecommons.org/licenses/by-sa/4.0/|87px]]</div><!--not the exact same image; this one's a bit more blurry, but I couldn't be bothered finding the exact one--> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -104px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Poweredby MediaWiki current.svg|link=mediawikiwiki:|87px]]</div></div></div> <div style="position: relative;position:absolute;bottom:-1036px"> </div><!--adds padding between link boxes and the bottom of the page. kind of a jank way to do it, but it works.--> <!--above is linked boxes above is linked boxes --> <div style=" position: relative; position:absolute; bottom: -900px; border-left-style: solid; border-left-color: #a7d6f7; border-left-width: 1px; border-top-style: solid; border-top-color: #a7d6f7; border-top-width: 1px; border-bottom-style: solid; border-bottom-color: #a7d6f7; border-bottom-width: 1px; margin-left: -1.76em; margin-right: -1.7em; background-color: rgba(255, 255, 255, 0.8); ><!-- --><div style="margin-left: 1.75em;margin-right: 1.7em;opacity: 1;"> =User:Supertian9= [[File:Example.jpg|thumb|right|Win!]] According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off of the ground, but the bee of course, flies anyway, because bees don't care what humans think is impossible.<!--- ---><div class="catlinks" style="margin-bottom: 1.5em;">[[Special:Categories|Categories]]: <ul style="margin: 0"><li>foo</li><li>bar</li></ul></div><!-- --></div></div><div style=" position: relative; position:absolute; bottom: -1012px; font-size: 12px; line-height: 2em; margin-left: -0.3em; <!--Interestingly, on Vector 2010, the notice below the page is not aligned with the text in the page--> "> This page was last edited on 03 January 2000, at 09:05. Content is available under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) unless otherwise noted. [[meta:Special:MyLanguage/Privacy Policy|Privacy Policy]] [[MediaWiki:Noarticletext|About You Shou Yan]] [[MediaWiki:Noarticletext|Disclaimers]] [[meta:Special:MyLanguage/Terms of Use|Terms of Use]] [[meta:Special:MyLanguage/Donate|Donate to Miraheze]] [[:File:ImageShare1.jpg|Mobile view]] </div> <div style="width:100px; height:15; background-color: white;border-radius: 2px; border:0.5px solid black;">[[File:Powered_by_Miraheze_(no_box).svg]]</div> <!--<div style="width:200px; height:50px; fill: white">[[File:Cc-by-sa.png]]</div> <div style="width:200px; height:50px; fill: white">[[File:Poweredby_MediaWiki_2025.svg]]</div>--> <div style="width:100px; height:15; background-color: white;">foo</div> 3tspsfovvpjajrqpr4iyblfx11rqh6y 740041 740040 2026-05-01T17:13:16Z Supertian8 67751 testing second article title 740041 wikitext text/x-wiki <h1><big>User:Supertian8</big></h1> NaNNaN{{subst:<noinclude/>#ifeq:{{{nxtchap|none}}}|none | |{{subst:<noinclude/>!}}nxtchap={{{nxtchap}}}}} <div style="display: flex; justify-content: flex-end;"> <div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -326px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Powered_by_Miraheze_(no_box).svg|link=meta:Special:MyLanguage/Miraheze Meta]]</div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -215px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Cc-by-sa.png|link=https://creativecommons.org/licenses/by-sa/4.0/|87px]]</div><!--not the exact same image; this one's a bit more blurry, but I couldn't be bothered finding the exact one--> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -104px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Poweredby MediaWiki current.svg|link=mediawikiwiki:|87px]]</div></div></div> <div style="position: relative;position:absolute;bottom:-1036px"> </div><!--adds padding between link boxes and the bottom of the page. kind of a jank way to do it, but it works.--> <!--above is linked boxes above is linked boxes --> <div style=" position: relative; position:absolute; bottom: -900px; border-left-style: solid; border-left-color: #a7d6f7; border-left-width: 1px; border-top-style: solid; border-top-color: #a7d6f7; border-top-width: 1px; border-bottom-style: solid; border-bottom-color: #a7d6f7; border-bottom-width: 1px; margin-left: -1.76em; margin-right: -1.7em; background-color: rgba(255, 255, 255, 0.8); ><!-- --><div style="margin-left: 1.75em;margin-right: 1.7em;opacity: 1;"> <h1><big>User:Supertian9</big></h1> [[File:Example.jpg|thumb|right|Win!]] According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off of the ground, but the bee of course, flies anyway, because bees don't care what humans think is impossible.<!--- ---><div class="catlinks" style="margin-bottom: 1.5em;">[[Special:Categories|Categories]]: <ul style="margin: 0"><li>foo</li><li>bar</li></ul></div><!-- --></div></div><div style=" position: relative; position:absolute; bottom: -1012px; font-size: 12px; line-height: 2em; margin-left: -0.3em; <!--Interestingly, on Vector 2010, the notice below the page is not aligned with the text in the page--> "> This page was last edited on 03 January 2000, at 09:05. Content is available under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) unless otherwise noted. [[meta:Special:MyLanguage/Privacy Policy|Privacy Policy]] [[MediaWiki:Noarticletext|About You Shou Yan]] [[MediaWiki:Noarticletext|Disclaimers]] [[meta:Special:MyLanguage/Terms of Use|Terms of Use]] [[meta:Special:MyLanguage/Donate|Donate to Miraheze]] [[:File:ImageShare1.jpg|Mobile view]] </div> <div style="width:100px; height:15; background-color: white;border-radius: 2px; border:0.5px solid black;">[[File:Powered_by_Miraheze_(no_box).svg]]</div> <!--<div style="width:200px; height:50px; fill: white">[[File:Cc-by-sa.png]]</div> <div style="width:200px; height:50px; fill: white">[[File:Poweredby_MediaWiki_2025.svg]]</div>--> <div style="width:100px; height:15; background-color: white;">foo</div> 123snybdm2v5oknyf4pve186ze1jk13 740044 740041 2026-05-01T17:17:07Z Supertian8 67751 more accurate maybe 740044 wikitext text/x-wiki <h1><span style="font-size: 30px;">User:Supertian8</span></h1> NaNNaN{{subst:<noinclude/>#ifeq:{{{nxtchap|none}}}|none | |{{subst:<noinclude/>!}}nxtchap={{{nxtchap}}}}} <div style="display: flex; justify-content: flex-end;"> <div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -326px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Powered_by_Miraheze_(no_box).svg|link=meta:Special:MyLanguage/Miraheze Meta]]</div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -215px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Cc-by-sa.png|link=https://creativecommons.org/licenses/by-sa/4.0/|87px]]</div><!--not the exact same image; this one's a bit more blurry, but I couldn't be bothered finding the exact one--> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -104px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Poweredby MediaWiki current.svg|link=mediawikiwiki:|87px]]</div></div></div> <div style="position: relative;position:absolute;bottom:-1036px"> </div><!--adds padding between link boxes and the bottom of the page. kind of a jank way to do it, but it works.--> <!--above is linked boxes above is linked boxes --> <div style=" position: relative; position:absolute; bottom: -900px; border-left-style: solid; border-left-color: #a7d6f7; border-left-width: 1px; border-top-style: solid; border-top-color: #a7d6f7; border-top-width: 1px; border-bottom-style: solid; border-bottom-color: #a7d6f7; border-bottom-width: 1px; margin-left: -1.76em; margin-right: -1.7em; background-color: rgba(255, 255, 255, 0.8); ><!-- --><div style="margin-left: 1.75em;margin-right: 1.7em;opacity: 1;"> <h1><span style="font-size: 30px;">User:Supertian8</span>/h1> [[File:Example.jpg|thumb|right|Win!]] According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off of the ground, but the bee of course, flies anyway, because bees don't care what humans think is impossible.<!--- ---><div class="catlinks" style="margin-bottom: 1.5em;">[[Special:Categories|Categories]]: <ul style="margin: 0"><li>foo</li><li>bar</li></ul></div><!-- --></div></div><div style=" position: relative; position:absolute; bottom: -1012px; font-size: 12px; line-height: 2em; margin-left: -0.3em; <!--Interestingly, on Vector 2010, the notice below the page is not aligned with the text in the page--> "> This page was last edited on 03 January 2000, at 09:05. Content is available under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) unless otherwise noted. [[meta:Special:MyLanguage/Privacy Policy|Privacy Policy]] [[MediaWiki:Noarticletext|About You Shou Yan]] [[MediaWiki:Noarticletext|Disclaimers]] [[meta:Special:MyLanguage/Terms of Use|Terms of Use]] [[meta:Special:MyLanguage/Donate|Donate to Miraheze]] [[:File:ImageShare1.jpg|Mobile view]] </div> <div style="width:100px; height:15; background-color: white;border-radius: 2px; border:0.5px solid black;">[[File:Powered_by_Miraheze_(no_box).svg]]</div> <!--<div style="width:200px; height:50px; fill: white">[[File:Cc-by-sa.png]]</div> <div style="width:200px; height:50px; fill: white">[[File:Poweredby_MediaWiki_2025.svg]]</div>--> <div style="width:100px; height:15; background-color: white;">foo</div> gy5j2kzlv3tyhx8o41f01ks7cm7hgra 740047 740044 2026-05-01T17:19:25Z Supertian8 67751 precise and fix h1 740047 wikitext text/x-wiki <h1><span style="font-size: 29px;">User:Supertian8</span></h1> NaNNaN{{subst:<noinclude/>#ifeq:{{{nxtchap|none}}}|none | |{{subst:<noinclude/>!}}nxtchap={{{nxtchap}}}}} <div style="display: flex; justify-content: flex-end;"> <div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -326px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Powered_by_Miraheze_(no_box).svg|link=meta:Special:MyLanguage/Miraheze Meta]]</div> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -215px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Cc-by-sa.png|link=https://creativecommons.org/licenses/by-sa/4.0/|87px]]</div><!--not the exact same image; this one's a bit more blurry, but I couldn't be bothered finding the exact one--> <div style=" position: relative; position:absolute; bottom: -1028px; margin-left: -104px; width:104px; height:42px; background-color: white; border-radius: 2px; border:0.5px solid #72777d; display: flex; justify-content: center; align-items: center; ">[[File:Poweredby MediaWiki current.svg|link=mediawikiwiki:|87px]]</div></div></div> <div style="position: relative;position:absolute;bottom:-1036px"> </div><!--adds padding between link boxes and the bottom of the page. kind of a jank way to do it, but it works.--> <!--above is linked boxes above is linked boxes --> <div style=" position: relative; position:absolute; bottom: -900px; border-left-style: solid; border-left-color: #a7d6f7; border-left-width: 1px; border-top-style: solid; border-top-color: #a7d6f7; border-top-width: 1px; border-bottom-style: solid; border-bottom-color: #a7d6f7; border-bottom-width: 1px; margin-left: -1.76em; margin-right: -1.7em; background-color: rgba(255, 255, 255, 0.8); ><!-- --><div style="margin-left: 1.75em;margin-right: 1.7em;opacity: 1;"> <h1><span style="font-size: 29px;">User:Supertian8</span></h1> [[File:Example.jpg|thumb|right|Win!]] According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off of the ground, but the bee of course, flies anyway, because bees don't care what humans think is impossible.<!--- ---><div class="catlinks" style="margin-bottom: 1.5em;">[[Special:Categories|Categories]]: <ul style="margin: 0"><li>foo</li><li>bar</li></ul></div><!-- --></div></div><div style=" position: relative; position:absolute; bottom: -1012px; font-size: 12px; line-height: 2em; margin-left: -0.3em; <!--Interestingly, on Vector 2010, the notice below the page is not aligned with the text in the page--> "> This page was last edited on 03 January 2000, at 09:05. Content is available under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) unless otherwise noted. [[meta:Special:MyLanguage/Privacy Policy|Privacy Policy]] [[MediaWiki:Noarticletext|About You Shou Yan]] [[MediaWiki:Noarticletext|Disclaimers]] [[meta:Special:MyLanguage/Terms of Use|Terms of Use]] [[meta:Special:MyLanguage/Donate|Donate to Miraheze]] [[:File:ImageShare1.jpg|Mobile view]] </div> <div style="width:100px; height:15; background-color: white;border-radius: 2px; border:0.5px solid black;">[[File:Powered_by_Miraheze_(no_box).svg]]</div> <!--<div style="width:200px; height:50px; fill: white">[[File:Cc-by-sa.png]]</div> <div style="width:200px; height:50px; fill: white">[[File:Poweredby_MediaWiki_2025.svg]]</div>--> <div style="width:100px; height:15; background-color: white;">foo</div> k0b1t2q6u0j8txh8gdyuo4lnbc42hiw User:Namoroka/common.js 2 166970 740128 665307 2026-05-02T02:37:19Z Namoroka 19627 740128 javascript text/javascript mw.loader.using(['mediawiki.util'], function () { mw.loader.load('User:Namoroka/keyboard-layouts.js'); mw.loader.load('User:Namoroka/additional-layouts.js'); mw.loader.load('User:Namoroka/ohi.js'); }); lj3jpwa82nmjctgfgstadt1zchgvvko 740130 740128 2026-05-02T03:18:26Z Namoroka 19627 Blanked the page 740130 javascript text/javascript phoiac9h4m842xq45sp7s6u21eteeq1 User:Namoroka/ohi.js 2 166972 740126 665299 2026-05-02T02:27:17Z Namoroka 19627 740126 javascript text/javascript /** Modified Version (http://ohi.pat.im) * Modifier : Pat-Al <pat@pat.im> (https://pat.im/910) * Last Update : 2024/08/07 * Added support for more keyboard layouts by custom keyboard layout tables. * Added support for Dvorak and Colemak and Workman keyboard layouts. * Added support for Firefox 12 and higher version. * Added the on-screen keyboard function. * Added support for old Hangeul combination by Syllable-Initial-Peak-Final Encoding Approach. * Added support for multi-key input (moachigi). **/ /** Original Version (copy - http://ohi.pat.im/org) * Author : Ho-Seok Ee <hsee@korea.ac.kr> * Release: 2006/07/18 * Update : 2011/01/22 Copyright (C) Ho-Seok Ee <hsee@korea.ac.kr>. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. The license can be found at http://www.gnu.org/licenses/gpl.txt. **/ var default_En_type = 'QWERTY'; var default_Ko_type = 'Sin3-P2'; var default_ohi_KBD_type = 'QWERTY'; var default_ohi_KE = 'Ko'; var En_type; // 영문 자판 종류 (ohiChange 함수로 바꿈) var Ko_type; // 한글 자판 종류 (ohiChange 함수로 바꿈) var ohi_KBD_type; // 기준 자판 종류 (QWERTY/QWERTZ/AZERTY, ohiChange_KBD_type 함수로 바꿈) var ohi_KE; // 한글·영문 상태 (Ko: 한글, En: 영문) (ohiChange_KE 함수로 바꿈) function option() { var enable_double_final_ext; // 겹받침 확장 배열 쓰기 --> ohiChange_enable_double_final_ext() 함수로 값을 바꿈 var enable_sign_ext; // 세벌식 자판의 기호 확장 배열 쓰기 --> ohiChange_enable_sign_ext() 함수로 값을 바꿈 var force_normal_typing; // 모아치기 자판을 이어치기(일반 타자법)로 치게 하기 var only_NFD_hangeul_encoding; // 한글을 첫가끝 조합형으로만 넣기 (완성형으로 넣지 않기) var enable_old_hangeul_input; // 옛한글 조합하기 var use_hangeul_compatibility_jamo_when_entering_old_hangeul; // 옛한글을 조합할 때 낱자를 호환 자모로 나타냄 var enable_Sin3_diphthong_key; // 0이면 신세벌식 자판에서 오른쪽 글쇠로 홀소리를 넣을 수 없음 var phonemic_writing; // 풀어쓰기 var phonemic_writing_in_single_phoneme; // 풀어쓰기: 겹낱자를 홑낱자로 풀기 var phonemic_writing_in_halfwidth_letter; // 풀어쓰기: 한글 낱자를 반각 문자로 넣기 var phonemic_writing_initial_ieung_ellipsis; // 풀어쓰기: 첫소리 ㅇ 넣지 않기 var phonemic_writing_adding_space_every_syllable_end; // 풀어쓰기: 낱내자(음절자)마다 빈칸 넣기 var phonemic_writing_directly; // 풀어쓰기: 조합하지 않고 낱자를 바로 넣기 var abbreviation; // 이어치기 자판에서 줄임말 기능 쓰기 var convenience_combination; // 입력 편의를 높이는 추가 낱자 조합 쓰기 var sunalae; // 두벌식 자판 순아래 조합 var show_layout; // 1: 자판 배열표 보이기 / 0: 자판 배열표 감추기 --> show_keyboard_layout() 함수로 값을 바꿈 var turn_off_OHI; // OHI 입력 기능 끄기 (끄더라도 화상 자판은 그대로 쓸 수 있음) var square_layout; // 화상 배열표를 네모지게 나타내기 } function converting_option() { var NCR_text; // HTML 문자 참조 보기 var convert_only_NFD_hangeul_encoding_in_NCR_text; // 첫가끝 조합형으로 들어간 한글만 바꾸기 var direct_typing_text; // 쿼티 배열 기준으로 글쇠값 바꾸기 var extended_hangeul_layout_reflection; // 한글 확장 배열 반영하기 var combination_table_reflection; // 낱자 조합 규칙 반영하기 var combination_table_reflection_priority; // 겹낱자까지 낱자 조합 규칙을 우선 반영하기 var combination_table_reflection_ggeut_ss_exception; // 낱자 조합 규칙을 적용하더라도 받침 ㅆ은 조합해서 넣지 않는 것으로 셈하기 } function initialize_options() { var default_enable_double_final_ext = 0; var default_enable_sign_ext = 1; var default_force_normal_typing = 0; var default_only_NFD_hangeul_encoding = 0; var default_enable_old_hangeul_input = 0; var default_use_hangeul_compatibility_jamo_when_entering_old_hangeul = 1; var default_enable_Sin3_diphthong_key = 1; var default_enable_Sin3_adding_cheos_with_shift_key = 0; var default_phonemic_writing = 0; var default_phonemic_writing_in_single_phoneme = 1; var default_phonemic_writing_in_halfwidth_letter = 0; var default_phonemic_writing_initial_ieung_ellipsis = 1; var default_phonemic_writing_adding_space_every_syllable_end = 0; var default_phonemic_writing_directly = 0; var default_phonemic_writing_NFD_ggeut_to_cheos = 1; var default_abbreviation = 0; var default_convenience_combination = 1; var default_sunalae = 0; var default_square_layout = 0; // En_type, Ko_type 등이 미리 지정되어 있으면 지정된 것으로 초기값을 바꿈 if(typeof En_type != 'undefined') default_En_type = En_type; else En_type = default_En_type; if(typeof Ko_type != 'undefined') default_Ko_type = Ko_type; else Ko_type = default_Ko_type; if(typeof ohi_KBD_type != 'undefined') default_ohi_KBD_type = ohi_KBD_type; else ohi_KBD_type = default_ohi_KBD_type; if(typeof ohi_KE != 'undefined') default_ohi_KE = ohi_KE; else ohi_KE = default_ohi_KE; if(typeof enable_sign_ext != 'undefined') default_enable_sign_ext = enable_sign_ext; if(typeof force_normal_typing != 'undefined') default_force_normal_typing = force_normal_typing; if(typeof phonemic_writing != 'undefined') default_phonemic_writing = phonemic_writing; if(typeof phonemic_writing_in_single_phoneme != 'undefined') default_phonemic_writing_in_single_phoneme = phonemic_writing_in_single_phoneme; if(typeof phonemic_writing_in_halfwidth_letter != 'undefined') default_phonemic_writing_in_halfwidth_letter = phonemic_writing_in_halfwidth_letter; if(typeof phonemic_writing_initial_ieung_ellipsis != 'undefined') default_phonemic_writing_initial_ieung_ellipsis = phonemic_writing_initial_ieung_ellipsis; if(typeof phonemic_writing_adding_space_every_syllable_end != 'undefined') default_phonemic_writing_adding_space_every_syllable_end = phonemic_writing_adding_space_every_syllable_end; if(typeof phonemic_writing_directly != 'undefined') default_phonemic_writing_directly = phonemic_writing_directly; if(typeof square_layout != 'undefined') default_square_layout = square_layout; option = new option(); option.enable_double_final_ext = default_enable_double_final_ext; option.enable_sign_ext = default_enable_sign_ext; option.force_normal_typing = default_force_normal_typing; option.only_NFD_hangeul_encoding = default_only_NFD_hangeul_encoding; option.enable_old_hangeul_input = default_enable_old_hangeul_input; option.use_hangeul_compatibility_jamo_when_entering_old_hangeul = default_use_hangeul_compatibility_jamo_when_entering_old_hangeul; option.enable_Sin3_diphthong_key = default_enable_Sin3_diphthong_key; option.enable_Sin3_adding_cheos_with_shift_key = default_enable_Sin3_adding_cheos_with_shift_key; option.phonemic_writing = default_phonemic_writing; option.phonemic_writing_in_single_phoneme = default_phonemic_writing_in_single_phoneme; option.phonemic_writing_in_halfwidth_letter = default_phonemic_writing_in_halfwidth_letter; option.phonemic_writing_initial_ieung_ellipsis = default_phonemic_writing_initial_ieung_ellipsis; option.phonemic_writing_adding_space_every_syllable_end = default_phonemic_writing_adding_space_every_syllable_end; option.phonemic_writing_directly = default_phonemic_writing_directly; option.phonemic_writing_NFD_ggeut_to_cheos = default_phonemic_writing_NFD_ggeut_to_cheos; option.abbreviation = default_abbreviation; option.convenience_combination = default_convenience_combination; option.sunalae = default_sunalae; option.turn_off_OHI = 0; option.show_layout = 1; option.square_layout = default_square_layout; converting_option = new converting_option(); converting_option.NCR_text = 0; converting_option.convert_only_NFD_hangeul_encoding_in_NCR_text = 0; converting_option.direct_typing_text = 0; converting_option.extended_hangeul_layout_reflection = 0; converting_option.combination_table_reflection = 1; converting_option.combination_table_reflection_priority = 0; converting_option.combination_table_reflection_ggeut_ss_exception = 1; } initialize_options(); var ohiQ = [0,0,0,0,0,0,0,0,0]; // 조합하고 있는 완성형 한글 낱내자의 낱자들을 담는 배열 [첫,첫,첫,가,가,가,끝,끝,끝] var ohiRQ = [0,0,0,0,0,0,0,0,0]; // 조합하고 있는 완성형 한글 낱내자의 낱자들의 추가 정보를 담는 배열 (보기: 겹홀소리 조합용 홀소리인지, 받침 붙는 홀소리인지) var prev_ohiQ = []; var prev_ohiRQ = []; var backup_ohiQ = []; // 완성형 한글 낱내자를 옛한글 상태로 바꿀 때에 복사해 두는 배열 var backup_ohiRQ = []; var backspacing_state = 0; // 뒷걸음쇠 처리를 하고 있는지를 알리는 상태 변수 (ohiInsert 함수에 알림) var prev_cursor_position = -1; // 앞선 상태의 가리키개 자리 (모아치기 자판이나 줄임말 기능으로 넣은 글을 한꺼번에 지울 때 쓰임) var backup_prev_cursor_position = -1; // 앞선 상태의 가리키개 자리 (모아치기 자판이나 줄임말 기능으로 넣은 글을 한꺼번에 지울 때 쓰임) var abbreviation_processing_state = 0; // 줄임말 처리를 하고 있는지를 나타내는 변수 var ohiStatus = document.createElement('div'); var ohiTimeout = 0; var sign_ext_state = 0; // 기호 확장 배열을 쓰고 있는지를 나타냄 var phoneme_input_state = 0 // 풀어쓰기로 넣고 있는지를 나타냄 (ohiInsert 함수에 알림) var onkeypress_skip = 0; // ohiKeypress() 처리를 건너뛰기 (보기: 오른쪽 숫자판을 눌렀을 때) var onkeyup_skip = 0; // ohiKeyup() 처리를 건너뛰기 var ohiHangeul3_HanExtKey = 0; // 한글 확장 글쇠가 눌린 상태 var shift_lock = 0; // 한글 타자기 받침 글쇠 눌린 상태 var shift_click = 0; // 배열표에서 윗글쇠 누른 상태 var shiftlock_click = 0; // 배열표에서 Shift Lock을 누른 상태 var browser = '', browser_ver = 0, nu = navigator.userAgent; var dkey, ukey; var pressed_keys = []; // 모아친 글쇠들의 값 var prev_pressed_keys = []; // 바로 앞에 모아친 글쇠들의 값 var prev_class = []; // 바로 앞에 모아친 줄임말의 종류(품사 등) var pressing_keys = 0; // 눌려 있는 글쇠 수 var double_multikey_abbreviated_state = 0; // 줄여넣기를 두 차례 잇달아 했는지를 나타냄 function NFD_stack() { // 먼저 넣은 낱자 정보가 배열의 뒤에 들어가고, 마지막으로 들어온 낱자가 배열의 맨 앞에 들어감 var phoneme = []; // 글쇠로 친 첫가끝 낱자들을 겹낱자로 조합하지 않은 채로 담음 var phoneme_R = []; // 조합하는 첫가끝 낱자들의 추가 정보를 담음 (보기: 겹홀소리 조합용 홀소리인지, 받침 붙는 홀소리인지) var virtual_phoneme = []; // 가상 낱자 var combined_phoneme = []; // 조합한 첫가끝 낱자들을 담음 (첫+가 또는 첫+가+끝) } function initialize_NFD_stack() { NFD_stack.phoneme = []; NFD_stack.phoneme_R = []; NFD_stack.combined_phoneme = []; } initialize_NFD_stack(); var ohi_cheos, ohi_ga, ohi_ggeut, ohi_hotbadchim; // OHI에서 쓰는 요즘한글 첫·가·끝 낱자 var unicode_NFD_hangeul_phoneme = [], unicode_cheos = [], unicode_ga = [], unicode_ggeut=[]; // 유니코드 한글 낱자, 유니코드 한글 첫·가·끝 낱자 var unicode_modern_hangeul_phoneme= [], unicode_modern_cheos = [], unicode_modern_ga = [], unicode_modern_ggeut = []; // 유니코드 조합형 한글 낱자, 유니코드 조합형 요즘한글 첫·가·끝 낱자 var compatibility_hangeul_phoneme = [], compatibility_modern_cheos = [], compatibility_ga = [], compatibility_ggeut = []; // 유니코드 한글 호환 자모 var halfwidth_cheos = [], halfwidth_ga = [], halfwidth_ggeut= []; var current_layout_info=[]; function browser_detect() { var trident=navigator.userAgent.match(/Trident\/(\d\.\d)/i); var trident_ver = trident===undefined || !trident ? 0 : parseFloat(trident[1]); if(nu.indexOf('MSIE')>=0 || trident_ver>=7) { browser = "MSIE"; if(trident_ver<7) browser_ver = parseFloat(nu.substring(nu.indexOf("MSIE ")+5)); else if(trident_ver==7) browser_ver=11; } else if(nu.indexOf('Firefox')>=0) { browser = "Firefox"; browser_ver = parseFloat(nu.substring(nu.indexOf('Firefox/')+8)); } else if(nu.indexOf('Chrome')>=0) { browser = "Chrome"; browser_ver = parseFloat(nu.substring(nu.indexOf('Chrome/')+7)); } } function ohiBackspace(f) { // backspace 동작 if(document.selection && browser=='MSIE' && browser_ver<9) { var s=document.selection.createRange(); s.moveStart('character', -f.value.length); var pos = s.text.length; if(f.setSelectionRange) { f.setSelectionRange(pos-1,pos); f.text=''; } else if(f.createTextRange) { var range = f.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos-length); range.select(); range.text = ''; var scrollTop = f.scrollTop, scrollLeft = f.scrollLeft, selectionStart = f.selectionStart; var endText = f.value.substr(f.selectionEnd,f.value.length); f.value = f.value.substr(0,selectionStart)+String.fromCharCode(c); var scrollHeight = f.scrollHeight, scrollWidth = f.scrollWidth; f.value += endText; if(c==13 && browser=='MSIE' && browser_ver==11 && !endText.length) { // IE 11에서 뒤에 아무 문자 없을 때 줄을 바꾸면 한글 조합이 안 됨 f.value += String.fromCharCode(32); } f.scrollTop = (scrollTop > scrollHeight-f.clientHeight) ? scrollTop : scrollHeight-f.clientHeight; f.scrollLeft = (scrollLeft > scrollWidth-f.clientWidth) ? scrollLeft : scrollWidth-f.clientWidth; f.setSelectionRange(m || c<32 ? selectionStart:selectionStart+1, selectionStart+1); } } else { var bs_start = f.selectionStart; var bs_end = f.selectionEnd; if(!bs_end) return; if(bs_start == bs_end) { if(!NFD_stack.phoneme.length && prev_cursor_position<0) { // 첫가끝 조합 상태가 아닐 때 // 첫가끝 조합형으로 넣은 한글을 낱내자 단위로 지울 수 있게 낱내자의 낱자, 채움 문자, 방점 수를 셈 var i=0, ggeut=0; do { var code = f.value.substr(bs_start-i-1,1).charCodeAt(0); if(!i && unicode_ggeut.indexOf(code)>=0) {ggeut=1; continue;} if(i-ggeut==0 && (code==0x1160 || unicode_ga.indexOf(code)>=0)) continue; if(i-ggeut==0 && (code==0x1160 || unicode_ga.indexOf(code)>=0)) continue; if(i-ggeut==1 && (code==0x115F || unicode_cheos.indexOf(code)>=0)) continue; break; } while(bs_start-(++i)); } bs_start -= i?i:1; } f.value = f.value.substr(0,bs_start)+f.value.substr(bs_end); f.selectionStart = f.selectionEnd = bs_start; } ohiInsert(f,0,0); } function ohiHangeul_moa_backspace(f,e) { if(f.selectionEnd) { if(prev_cursor_position>=0 && f.selectionEnd > prev_cursor_position) { initialize_NFD_stack(); while(f.selectionEnd && f.selectionEnd > prev_cursor_position) {if(!ohiHangeul_backspace(f,e)) ohiBackspace(f);} } else if(!ohiHangeul_backspace(f,e)) ohiBackspace(f); } prev_cursor_position = -1; prev_class = []; esc_ext_state(); return true; } function ohiCombinedCharacter_backspace(f,e) { if(e.preventDefault) e.preventDefault(); if(character_combination_queue.length) { ohiBackspace(f); character_combination_queue.pop(); if(character_combination_queue.length) { ohiInput(f,0,character_combination_queue[character_combination_queue.length-1]); ohiSelection(f,1); } return true; } return false; } function ohiHangeul_backspace(f,e) { var i,j; var KE=ohi_KE; if(e.preventDefault) e.preventDefault(); // Backspace (기호 확장 배열 상태일 때) if(option.enable_sign_ext && sign_ext_state) { if(Ko_type.substr(0,4)=='Sin3') ohiBackspace(f); esc_ext_state(); return true; } if(ohiQ[1] || ohiQ[4] || ohiQ[0]&&ohiQ[3]) { // Backspace (요즘한글 조합 상태) for(i=8; !ohiQ[i];) i--; backspacing_state=1; ohiInsert(f,ohiQ[i]=0,ohiQ); backspacing_state=0; ohiRQ[i]=0; esc_ext_state(); return true; } if(KE=='Ko' && NFD_stack.phoneme.length) { // 첫가끝 조합 상태 if(!ohiHangeul3_HanExtKey) { ohiBackspace(f); if(browser=="MSIE" && browser_ver<9 ) { // IE ~8 i=NFD_stack.combined_phoneme.length-1; while(i--) ohiBackspace(f); } var temp_NFD_stack_phoneme = NFD_stack.phoneme.slice(); var temp_NFD_stack_phoneme_R = NFD_stack.phoneme_R.slice(); initialize_NFD_stack(); for(j=0, i=temp_NFD_stack_phoneme.length-1; i>=1; --i) if(unicode_NFD_hangeul_filler.indexOf(temp_NFD_stack_phoneme[i])>=0) ++j; if(j!=temp_NFD_stack_phoneme.length-1) { // 채움 문자만 남지 않았으면 for(i=temp_NFD_stack_phoneme.length-1; i>=1; --i) { NFD_hangeul_input(f,0,(temp_NFD_stack_phoneme_R[i] ? -1:1)*temp_NFD_stack_phoneme[i]); NFD_stack.phoneme_R[i-1] = temp_NFD_stack_phoneme_R[i]; } } } if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) { // 아래아 등이 지워졌을 때 첫가끝 코드 조합 상태에서 요즘한글(완성형) 코드로 바꾸기 for(i=0;i<NFD_stack.combined_phoneme.length;++i) { if(unicode_modern_hangeul_phoneme.indexOf(NFD_stack.combined_phoneme[i])<0 && unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[i])<0) break; } if(i==NFD_stack.combined_phoneme.length) { // 첫가끝 방식으로 조합하던 낱자들을 지우고 요즘한글 완성형으로 넣기 ohiBackspace(f); if(browser=="MSIE" && browser_ver<9 ) { i=NFD_stack.combined_phoneme.length-1; while(i--) ohiBackspace(f); } initialize_NFD_stack(); ohiQ = backup_ohiQ.slice(); ohiRQ = backup_ohiRQ.slice(); for(i=9;i>=0;--i) { if(ohiQ[i]) { ohiQ[i]=0; ohiRQ[i]=0; break; } } ohiInsert(f,0,ohiQ); } } esc_ext_state(); return true; } return false; } function ohiDoubleJamo(a,c,d) { var i,j; if(typeof current_layout_info.hangeul_combination_table != 'undefined') { // 낱자 조합 규칙이 따로 있으면 var c1, c2, c3, combination_table = current_layout_info.hangeul_combination_table; for(i=0; i<combination_table.length; ++i) { c1 = convert_into_ohi_hangeul_phoneme(parseInt(combination_table[i][0]/0x10000)); c2 = convert_into_ohi_hangeul_phoneme(combination_table[i][0]%0x10000); c3 = convert_into_ohi_hangeul_phoneme(combination_table[i][1]); if(!c1 || !c2 || !c3) continue; if(a==0 && ohi_cheos.indexOf(c3)>=0) j=127; else if(a==1 && ohi_ga.indexOf(c3)>=0) j=35; else if(a==2 && ohi_ggeut.indexOf(c3)>=0) j=0; else continue; if(c1==c+j && c2==d+j) return c3-c1; } return 0; } var j=[ // Double Jamos [ [1,7,18,21,24],1,7,18,21,24 ], // Cho [ [39,44,49],[31,32,51],[35,36,51],51 ], // Jung [ [1,4,9,18,21],[1,21],[24,30],[1,17,18,21,28,29,30],[0,21],21 ] // Jong ]; a=j[a]; for(i=a[0].length; c!=a[0][i-1]; i--) if(!i) return i; for(a=a[i], i=a.length||1; 1; i--) if(!i || d==a || d==a[i-1]) return i; } function ohiInsert(f,m,q) { // Insert // q가 숫자이면 그 부호값에 맞는 유니코드 부호를 넣음 // q가 배열(ohiQ)이면 유니코드 완성형 한글로 넣음 var a,b,c=q,d=m?1:0,g=0,h=0,i=0,j=0,k=0,u=0; var character_combination_table = find_character_combination_table(); if(!sign_ext_state && c && typeof c == 'number' && unicode_NFD_hangeul_phoneme.indexOf(c)<0 && character_combination_table.length) { // 한글이 아닌 문자를 조합하여 넣기 complete_hangeul_syllable(f); a = character_combination_table.filter(function(e) {return e[0]==c}); b = character_combination_queue.length ? character_combination_table.filter(function(e) {return e[1]==c && e[0]==character_combination_queue[character_combination_queue.length-1]}) : []; if(character_combination_queue.length) { if(b.length && c==b[0][1] && b[0][0]==character_combination_queue[character_combination_queue.length-1]) { ohiSelection(f,0); ohiBackspace(f); ohiInput(f,0,b[0][2]); character_combination_queue.push(b[0][2]); ohiSelection(f,1); return; } else { // 조합이 더 이어지지 않을 때 character_combination_queue = []; ohiSelection(f,0); } } if(!character_combination_queue.length && a.length && c==a[0][0]) { ohiInput(f,0,c); ohiSelection(f,1); character_combination_queue.push(c); return; } } if(!q) { ohiQ = ohiRQ = prev_ohiQ = prev_ohiRQ = [0,0,0,0,0,0,0,0,0]; return true; } if(q.length!=9) ohiQ = ohiRQ = [0,0,0,0,0,0,0,0,0]; else { for(a=0;a<9;++a) { if(q[a]>0) ++h; if(unicode_NFD_hangeul_phoneme.indexOf(ohiQ[a])>=0) ++u; } if(!u) { // 요즘한글 자판으로 요즘낱자 처리 (완성형) var m=m||'0,0,0,0,0,0,0,0,0', i=q[0]+q[1]+q[2], j=q[3]+q[4]+q[5], k=q[6]+q[7]+q[8]; c=i&&j?0xac00+(i-(i<3?1:i<5?2:i<10?4:i<20?11:12))*588+(j-31)*28+k-(k<8?0:k<19?1:k<25?2:3):0x3130+(i||j||k); } else if(!NFD_stack.phoneme.length && !is_old_hangeul_input()) { // 요즘한글 자판을 쓰는데 옛낱자가 들어오면 첫가끝 조합형으로 바꿈 backup_ohiQ = ohiQ.slice(); backup_ohiRQ = ohiRQ.slice(); if(h>1) ohiBackspace(f); for(a=0;a<3;++a) { c=backup_ohiQ[a*3]+backup_ohiQ[a*3+1]+backup_ohiQ[a*3+2]; if(!c) continue; if(c<158) c+=!a?127:a==1?35:0; c=convert_into_unicode_hangeul_phoneme(c); if(c) NFD_hangeul_input(f,0,c); } return; } } if((is_phonemic_writing_input() || option.only_NFD_hangeul_encoding && !is_old_hangeul_input()) && !phoneme_input_state && !backspacing_state) { // 풀어쓰기를 하거나 요즘한글 자판으로 첫가끝 낱자로만 조합할 때 if(is_phonemic_writing_input() && option.phonemic_writing_directly && !option.only_NFD_hangeul_encoding && i+j+k) { // 낱자 바로 풀기 if(option.phonemic_writing_in_halfwidth_letter && !is_old_hangeul_input()) c=convert_into_halfwidth_hangeul_letter(c); else c=convert_into_compatibility_hangeul_letter(c); ohiInsert(f,0,c); return; } else { for(a=0;a<9;++a) if(prev_ohiQ[a]>0) ++g; if(g>0 && h<2 || d) { phoneme_input_state=1; ohiQ = prev_ohiQ.slice(); if(ohiQ[0]+ohiQ[3]+ohiQ[6]) { // 두벌식 자판의 도깨비불 상태에서 홀소리가 들어와 앞 낱내의 끝이 가려짐 → 앞 낱내의 받침에 들어간 닿소리를 뒤 낱내의 첫소리로 넘기고 앞 낱내의 조합을 끊음 if(Ko_type.substr(0,2)=='2-' && h&&i&&j) for(a=8;a>=0;--a) if(ohiQ[a]) { ohiQ[a]=0; break; } complete_hangeul_syllable(f); // 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) if(is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end && h && i+j+k) ohiInsert(f,0,32); } ohiQ=[h&&i?i:0,0,0,h&&j?j:0,0,0,h&&k?k:0,0,0]; phoneme_input_state=0; } } } if(is_moachigi_input() && NFD_stack.phoneme.length && unicode_NFD_hangeul_code.indexOf(c)<0) complete_hangeul_syllable(f); ohiInput(f,m,c); prev_ohiQ = ohiQ.slice(); prev_ohiRQ = ohiRQ.slice(); } function ohiInput(f,m,c) { if(document.selection && browser=="MSIE" && browser_ver<10 ) { // IE ~9 var s=document.selection.createRange(), t=s.text; if(t && document.selection.clear) document.selection.clear(); s.text=(m=='0,0,0,0,0,0,0,0,0'||c&&t.length>1?'':t.substr(0,t.length))+String.fromCharCode(c); if(!c || !m || s.moveStart('character',-1)) s.select(); } else if(f.selectionEnd+1) { if(m!='0,0,0,0,0,0,0,0,0' && f.selectionEnd-f.selectionStart==1) f.selectionStart++; var e=document.createEvent('KeyboardEvent'); if(e.initKeyEvent && !(browser=="Firefox" && browser_ver>=12 ) && browser!="Chrome") { // Gecko e.initKeyEvent('keypress',0,0,null,0,0,0,0,127,c); if(c && f.dispatchEvent(e) && m) f.selectionStart--; } else { // Firefox 12~, Chrome var scrollTop = f.scrollTop, scrollLeft = f.scrollLeft, selectionStart = f.selectionStart; var endText = f.value.substr(f.selectionEnd,f.value.length); f.value = f.value.substr(0,selectionStart)+String.fromCharCode(c); var scrollHeight = f.scrollHeight, scrollWidth = f.scrollWidth; f.value += endText; if(c==13 && browser=='MSIE' && browser_ver==11 && !endText.length) f.value += String.fromCharCode(32); // IE 11에서 뒤에 아무 문자 없을 때 줄을 바꾸면 한글 조합이 안 됨 f.scrollTop = (scrollTop > scrollHeight-f.clientHeight) ? scrollTop : scrollHeight-f.clientHeight; f.scrollLeft = (scrollLeft > scrollWidth-f.clientWidth) ? scrollLeft : scrollWidth-f.clientWidth; f.setSelectionRange(m || c<32 ? selectionStart:selectionStart+1, selectionStart+1); } } } function ohiSelection(f,length) { if(document.selection && browser=="MSIE" && browser_ver<9) { // IE ~8 } else if(f.selectionEnd+1) { var e=document.createEvent('KeyboardEvent'); if(e.initKeyEvent && !(browser=="Firefox" && browser_ver>=12 ) && browser!="Chrome") f.selectionStart-=length; // Gecko else f.selectionStart=f.selectionEnd-length; // Firefox 12~, Chrome } } function esc_ext_state() { // 기호 확장 배열 또는 한글 확장 배열을 상태에서 기본 배열을 쓰는 상태로 바꿈 var KE = ohi_KE; if(ohiHangeul3_HanExtKey || sign_ext_state) { if(KE=='Ko') { sign_ext_state=0; ohiHangeul3_HanExtKey=0; show_keyboard_layout(Ko_type); } } sign_ext_state=0; ohiHangeul3_HanExtKey=0; character_combination_queue = []; } function change_syllable_from_NFC_to_NFD(f) { // 완성형(NFC) → 첫가끝(NFD) (조합을 막 끝낸 낱내자를 바꿈) var _ohiQ = ohiQ.slice(), _ohiRQ = ohiRQ.slice(); ohiBackspace(f); i=ohi_cheos.indexOf(_ohiQ[0]+_ohiQ[1]+_ohiQ[2]+127); if(i>=0 && _ohiQ[0]+_ohiQ[1]+_ohiQ[2]) ohiInsert(f,0,unicode_cheos[i]); else ohiInput(f,0,0x115F); i=ohi_ga.indexOf(_ohiQ[3]+_ohiQ[4]+_ohiQ[5]+35); if(i>=0 && _ohiQ[3]+_ohiQ[4]+_ohiQ[5]) ohiInsert(f,0,unicode_ga[i]); else ohiInput(f,0,0x1160); i=ohi_ggeut.indexOf(_ohiQ[6]+_ohiQ[7]+_ohiQ[8]); if(i>=0 && _ohiQ[6]+_ohiQ[7]+_ohiQ[8]) ohiInsert(f,0,unicode_ggeut[i]); } function change_syllable_from_NFD_to_NFC(f) { // 첫가끝(NFD) → 완성형(NFC) (조합을 막 끝낸 낱내자를 바꿈) var i,j,k; if(unicode_modern_cheos.indexOf(NFD_stack.combined_phoneme[1])>=0 && unicode_modern_ga.indexOf(NFD_stack.combined_phoneme[0])>=0 || unicode_modern_cheos.indexOf(NFD_stack.combined_phoneme[2])>=0 && unicode_modern_ga.indexOf(NFD_stack.combined_phoneme[1])>=0 && unicode_modern_ggeut.indexOf(NFD_stack.combined_phoneme[0])>=0) { // 요즘낱자 첫+가 또는 첫+가+끝 i=NFD_stack.combined_phoneme.length; for(j=0;j<i;++j) ohiBackspace(f); ohiQ = [NFD_stack.combined_phoneme[i-1]-0x1100+11+(NFD_stack.combined_phoneme[i-1]>0x1108 ? 1:0),0,0, NFD_stack.combined_phoneme[i-2]-0x1161+31,0,0, i==3 ? (NFD_stack.combined_phoneme[0]-0x11A8+1+(NFD_stack.combined_phoneme[0]>0x11AE ? 1:0)+(NFD_stack.combined_phoneme[0]>0x11B8 ? 1:0)+(NFD_stack.combined_phoneme[0]>0x11BD ? 1:0)):0,0,0 ]; ohiInsert(f,0,ohiQ); if(typeof f.selectionEnd != 'undefined') f.selectionStart = f.selectionEnd; return; } if(!is_old_hangeul_input() && NFD_stack.combined_phoneme.length==2 && (unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[1])>=0 ^ unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[0])>=0)) { // 옛한글 자판이 아니면서 아래아를 넣는 요즘한글 자판을 쓸 때에 아래아와 아래애를 호환 자모로 바꿈 var NFD_to_compatibility_phoneme_list = [ 0x119E, 0x318D, // 아래아(ㆍ) 0x11A1, 0x318E // 아래애(ㆎ) ]; i = NFD_to_compatibility_phoneme_list.indexOf(unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[1])>=0 ? NFD_stack.combined_phoneme[0] : NFD_stack.combined_phoneme[1]); if(i>=0) { // 호환 자모로 바꿈 for(j=0;j<NFD_stack.combined_phoneme.length;++j) ohiBackspace(f); ohiInsert(f,0,NFD_to_compatibility_phoneme_list[i+1]); } } else if(option.use_hangeul_compatibility_jamo_when_entering_old_hangeul && is_old_hangeul_input() && !option.only_NFD_hangeul_encoding && ( unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[2])>=0 && unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[1])>=0 && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 || unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 || unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 && unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[1])>=0) ) { // 옛한글을 조합하는 때에 따로 들어간 낱자를 호환 자모로 바꿈 compatibility_jamo = compatibility_cheos.concat(compatibility_hol, compatibility_dah); compatibility_jamo_to_NFD_phonemes = compatibility_cheos_to_NFD_hotdah.concat(compatibility_hol_to_NFD_hothol, compatibility_dah_to_NFD_hotbadchim); for(j=0;j<compatibility_jamo_to_NFD_phonemes.length;++j) { i = compatibility_jamo_to_NFD_phonemes[j][0]; for(k=1;k<compatibility_jamo_to_NFD_phonemes[j].length;++k) { i = combine_unicode_NFD_hangeul_phoneme(i,compatibility_jamo_to_NFD_phonemes[j][k]); } if(i==NFD_stack.combined_phoneme[0] || unicode_NFD_hangeul_filler.indexOf(NFD_stack.combined_phoneme[0])>=0 && i==NFD_stack.combined_phoneme[1]) break; } if(j!=compatibility_jamo_to_NFD_phonemes.length) { for(k=0;k<NFD_stack.combined_phoneme.length;++k) ohiBackspace(f); ohiInput(f,0,compatibility_jamo[j]); } } } function combine_unicode_NFD_hangeul_phoneme(c1,c2) { // 유니코드 한글 낱자 조합하기 var i; var combination_table; var combined_phoneme; var layout_info = find_current_layout_info(); if(typeof current_layout_info.moachigi_hangeul_combination_table != 'undefined' && typeof current_layout_info.hangeul_combination_table == 'undefined') { // 모아치기 자판을 이어치기 방식으로 쓸 때 combination_table = current_layout_info.moachigi_hangeul_combination_table; for(i=0; i<combination_table.length; ++i) { if(combination_table[i].phonemes.length!=2) continue; if(combination_table[i].phonemes.indexOf(c1)<0 || combination_table[i].phonemes.indexOf(c2)<0) continue; if(c1==c2 && combination_table[i].phonemes[0]!=combination_table[i].phonemes[1]) continue; combined_phoneme=combination_table[i].char; break; } if(i==combination_table.length) return 0; return combined_phoneme; } else { // 이어치기 자판 combination_table = is_old_hangeul_input() ? hangeul_combination_table_full : hangeul_combination_table_default; // 자판 배열 정보에서 지정한 낱자 조합 규칙 if(typeof layout_info.hangeul_combination_table != 'undefined') { combination_table = layout_info.hangeul_combination_table; } if(is_old_hangeul_input() && option.enable_old_hangeul_input) { if(typeof current_layout_info.old_hangeul_layout_type_name != 'undefined' && typeof find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).hangeul_combination_table != 'undefined') combination_table = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).hangeul_combination_table; } // 편의를 높이기 위한 낱자 조합을 더함 (옛한글 자판이 아닌 한글 자판) if(!is_old_hangeul_input() && option.convenience_combination && typeof layout_info.hangeul_convenience_combination_table != 'undefined') combination_table = layout_info.hangeul_convenience_combination_table.concat(combination_table); var combined_phoneme=0x10000*c1+c2; for(i=0; i<combination_table.length; ++i) { if(combined_phoneme==combination_table[i][0]) { combined_phoneme=combination_table[i][1]; break; } } if(i==combination_table.length) return 0; return combined_phoneme; } } function complete_hangeul_syllable(f) { // 한글 낱내자 조합을 끊고 설정에 따라 한글 부호값을 바꿈 // option.only_NFD_hangeul_encoding==0 : 첫가끝 조합형 낱내자(NFD)를 완성형 낱내자(NFC)로 바꿈 // option.only_NFD_hangeul_encoding==1 : 완성형 낱내자(NFC)를 첫가끝 조합형 낱내자(NFD)로 바꿈 // 수정됨: inputText -> wpTextbox1 if(typeof f == 'undefined' || !f) f = document.getElementById('wpTextbox1'); var c,i,j,k; if(NFD_stack.phoneme.length) { // 첫가끝 조합형으로 조합하다가 채움 문자만 남았으면 채움 문자를 지움 for(j=0, i=NFD_stack.phoneme.length-1; i>=0; --i) if(unicode_NFD_hangeul_filler.indexOf(NFD_stack.phoneme[i])>=0) ++j; if(j==NFD_stack.phoneme.length) for(i=0;i<NFD_stack.combined_phoneme.length;++i) ohiBackspace(f); } if(ohiQ[0]+ohiQ[3]+ohiQ[6] || NFD_stack.phoneme.length) ohiSelection(f,0); // 풀어쓰기 처리 if(is_phonemic_writing_input()) convert_syllable_into_phonemes(f); // 첫가끝(NFD) → 완성형(NFC) else if(!option.only_NFD_hangeul_encoding && NFD_stack.phoneme.length) change_syllable_from_NFD_to_NFC(f); // 완성형(NFC) → 첫가끝(NFD) else if(option.only_NFD_hangeul_encoding && !is_old_hangeul_input() && ohiQ[0]+ohiQ[3]+ohiQ[6]) change_syllable_from_NFC_to_NFD(f); ohiInsert(f,0,0); initialize_NFD_stack(); if(!abbreviation_processing_state) prev_cursor_position = -1; } function convert_into_ohi_hangeul_phoneme(c) { // 유니코드의 요즘한글 낱자 코드와 홀소리 호환 낱자 코드를 ohi에서 쓰는 코드로 바꾸기 (옛한글 낱자는 바꾸지 않음) if(unicode_modern_cheos.indexOf(c)>=0) c=ohi_cheos[unicode_modern_cheos.indexOf(c)]; else if(unicode_modern_ga.indexOf(c)>=0) c=ohi_ga[unicode_modern_ga.indexOf(c)]; else if(unicode_modern_ggeut.indexOf(c)>=0) c=ohi_ggeut[unicode_modern_ggeut.indexOf(c)]; else if(compatibility_ga.indexOf(c)>=0) c=ohi_ga[compatibility_ga.indexOf(c)]; return c; } function convert_into_unicode_hangeul_phoneme(c) { // ohi에서 쓰는 한글 낱자값을 유니코드 조합형 낱자 부호값으로 바꾸기 if(ohi_cheos.indexOf(c)>=0) c=unicode_cheos[ohi_cheos.indexOf(c)]; else if(ohi_ga.indexOf(c)>=0) c=unicode_ga[ohi_ga.indexOf(c)]; else if(ohi_ggeut.indexOf(c)>=0) c=unicode_ggeut[ohi_ggeut.indexOf(c)]; return c; } function convert_into_compatibility_hangeul_letter(c) { // 호환 한글 자모로 바꾸기 c=convert_into_unicode_hangeul_phoneme(c); old_hangeul_cheos = [0x1140,0x114C,0x1159]; old_hangeul_ga = [0x119E]; old_hangeul_ggeut = [0x11EB,0x11F0,0x11F9]; compatibility_yeshangeul_dah = [0x317F,0x3181,0x3186]; // ㅿ,ㆁ,ㆆ compatibility_yeshangeul_hol = [0x318D]; // ㆍ if(unicode_modern_cheos.indexOf(c)>=0) c=compatibility_modern_cheos[unicode_modern_cheos.indexOf(c)]; else if(unicode_modern_ga.indexOf(c)>=0) c=compatibility_ga[unicode_modern_ga.indexOf(c)]; else if(unicode_modern_ggeut.indexOf(c)>=0) c=compatibility_ggeut[unicode_modern_ggeut.indexOf(c)]; else if(old_hangeul_cheos.indexOf(c)>=0) c=compatibility_yeshangeul_dah[old_hangeul_cheos.indexOf(c)]; else if(old_hangeul_ga.indexOf(c)>=0) c=compatibility_yeshangeul_hol[old_hangeul_ga.indexOf(c)]; else if(old_hangeul_ggeut.indexOf(c)>=0) c=compatibility_yeshangeul_dah[old_hangeul_ggeut.indexOf(c)]; return c; } function convert_into_halfwidth_hangeul_letter(c) { c=convert_into_unicode_hangeul_phoneme(c); if(unicode_modern_cheos.indexOf(c)>=0) c=halfwidth_cheos[unicode_modern_cheos.indexOf(c)]; else if(unicode_modern_ga.indexOf(c)>=0) c=halfwidth_ga[unicode_modern_ga.indexOf(c)]; else if(unicode_modern_ggeut.indexOf(c)>=0) c=halfwidth_ggeut[unicode_modern_ggeut.indexOf(c)]; else if(compatibility_modern_cheos.indexOf(c)>=0) c=halfwidth_cheos[compatibility_modern_cheos.indexOf(c)]; else if(compatibility_ga.indexOf(c)>=0) c=halfwidth_ga[compatibility_ga.indexOf(c)]; else if(compatibility_ggeut.indexOf(c)>=0) c=halfwidth_ggeut[compatibility_ggeut.indexOf(c)]; return c; } function convert_into_single_phonemes(combined_phoneme) { // 겹낱자를 홀낱자들로 바꿈 combined_phoneme = convert_into_unicode_hangeul_phoneme(combined_phoneme); var i, single_phonemes=[combined_phoneme], test_phonemes; var combination_table = hangeul_combination_table_default; if(is_old_hangeul_input()) combination_table = hangeul_combination_table_full; if(unicode_non_combined_phoneme.indexOf(combined_phoneme)>=0) return single_phonemes; for(i=0; i<combination_table.length; ++i) { if(combined_phoneme==combination_table[i][1]) { single_phonemes.splice(0,1,parseInt(combination_table[i][0]/0x10000),combination_table[i][0]%0x10000); break; } } if(i!=combination_table.length && is_old_hangeul_input()) { // 옛한글에 쓰이는 겹낱자인지 살핌 for(i=1;i>=0;--i) { test_phonemes = convert_into_single_phonemes(single_phonemes[i]); if(test_phonemes.length>1) single_phonemes.splice(i,1,test_phonemes[0], test_phonemes[1]); } } return single_phonemes; } function convert_NFC_into_NFD(NFC_c) { // 완성형 낱내자 부호값(NFC)을 받아서 첫가끝 조합형 낱자 단위 낱내자 부호값(NFD)으로 바꾸어 돌려줌 if(NFC_c<0xAC00 || NFC_c>0xD7A3) return false; var i,j,k; i=parseInt((NFC_c-0xAC00)/588)+0x1100; j=parseInt((NFC_c-0xAC00)%588/28)+0x1161; k=(NFC_c-0xAC00)%588%28+0x11A7; k = k==0x11A7 ? 0 : k; return [i,j,k]; } function convert_NFD_into_NFC(NFD_phonemes) { // 첫가끝 조합형 낱자 단위 낱내자 부호값(NFD)을 받아 완성형 낱내자 단위 부호값(NFC)으로 돌려줌 // 요즘한글에 쓰이지 않는 낱자가 있으면 거짓(false)값을 돌려줌 var p=[], h,i,j,k; if(NFD_phonemes.length>3 || NFD_phonemes.length<2) return false; h = unicode_modern_cheos.indexOf(NFD_phonemes[0])<0 ? 0 : 1; for(i=0;i<NFD_phonemes.length;++i) { // p의 낱자 차례를 '첫+가' 또는 '첫+가+끝'으로 맞춤 j = h ? i : NFD_phonemes.length-i-1; p[i]=NFD_phonemes[j]; if(unicode_modern_hangeul_phoneme.indexOf(p[i])<0) return false; } if(unicode_modern_cheos.indexOf(p[0])<0 || unicode_modern_ga.indexOf(p[1])<0) return false; if(p.length==3 && unicode_modern_ggeut.indexOf(p[2])<0) return false; return 0xAC00+(p[0]-0x1100)*588+(p[1]-0x1161)*28+(p[2]-0x11A8); } function ohiRoman(f,e,key) { // Roman keyboard basic_layouts (Dvorak, Colemak) var c=key; var layout = find_current_layout(); if(En_type!='QWERTY') c=layout[key-33]; ohiInsert(f,0,c); } function ohiSpecialKey(f,e,c) { if(c==0x1B) { // 글쇠값이 0 또는 escape이면 조합 끊기 complete_hangeul_syllable(f); return true; } if(c==0x0D) { // Enter complete_hangeul_syllable(f); ohiInsert(f,0,c); esc_ext_state(); ohiSelection(f,0); return true; } if(c==0x08) { // Backspace (되걸음쇠) if(ohiHangeul_backspace(f,e)) return true; ohiBackspace(f); return true; } return false; } function Hangeul2_galmadeuli_selection(a) { if(typeof a.length == 'undefined') return 0; if(a.length==1) return a[0]; var i, dah=0, hol=0; for(i=a.length-1;i>=0;--i) { c = convert_into_unicode_hangeul_phoneme(a[i]); if(unicode_cheos.indexOf(c)>=0) dah=c; else if(unicode_ga.indexOf(c)>=0) hol=c; } if(!dah || !hol) return dah+hol; if(ohiQ[0]&&!ohiQ[3]&&!ohiQ[6] || unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0) return hol; return dah; } function ohiHangeul2(f,e,key) { // 2-Beolsik var i; if((Ko_type.indexOf('KSX5002')>=0 || Ko_type=='2-KPS9256') && (key<65 || (key-1)%32>25)) { complete_hangeul_syllable(f); ohiInsert(f,0,key); return; } var layout_info = find_current_layout_info(); var layout = find_current_layout(); var c = layout[key-33]; if(typeof c == 'object') c = Hangeul2_galmadeuli_selection(c); c = convert_into_ohi_hangeul_phoneme(c); if(special_chars.indexOf(c)>=0) if(ohiSpecialKey(f,e,c)) return; if(is_old_hangeul_input() || option.only_NFD_hangeul_encoding || NFD_stack.phoneme.length) { c = NFD_hangeul2_preprocess(f,e,key); if(unicode_NFD_hangeul_code.indexOf(c)>=0) { NFD_hangeul_input(f,key,c); return; } } if(c==layout[key-33]) { ohiInsert(f,0,c); return; } if(ohi_cheos.indexOf(c)>=0) c-=127; else if(ohi_ga.indexOf(c)>=0) c-=35; else if(ohi_ggeut.indexOf(c)>=0) c-=127; if(c<31) { // Jaum if((!ohiQ[7] || !(ohiQ[0]=-1)) && ohiQ[3]) ohiQ[7]=ohiDoubleJamo(2,ohiQ[6],c); if(!ohiQ[3] || ohiQ[0]<0 || ohiQ[0] && (!ohiQ[6] || !ohiQ[7]) && (ohiQ[6] || c==8 || c==19 || c==25)) { i = ohiDoubleJamo(0,ohiQ[0],c); ohiInsert(f,(ohiQ=ohiQ[1]||ohiQ[3]||!i?ohiQ:0),ohiQ=[c,ohiQ?0:i,0,0,0,0,0,0,0]); } else if(!ohiQ[0] && ohiQ[3]) { // 닿소리 없이 홀소리가 들어왔고 닿소리가 눌렸을 때 새 낱내자로 조합하기 complete_hangeul_syllable(f); ohiInsert(f,0,ohiQ=[c,0,0,0,0,0,0,0,0]); } else if(!ohiQ[0] && (ohiQ[0]=c) || (ohiQ[6]=ohiQ[6]||c)) ohiInsert(f,0,ohiQ); if(ohiQ[7]) ohiQ[7]=c; } else { // Moum if(option.sunalae || Ko_type.substr(0,5)=='2-sun') { if(ohiQ[3]==c && !ohiQ[1] && !ohiQ[6] && (ohiQ[0]==1 || ohiQ[0]==7 || ohiQ[0]==18 || ohiQ[0]==21 || ohiQ[0]==24)) { // 홀소리 글쇠를 거듭 눌러 된소리 만들기 ohiQ[1]=1; ohiInsert(f,0,ohiQ); return; } } if(option.sunalae || Ko_type=='2-KPS9256' || Ko_type.substr(0,5)=='2-sun' || Ko_type=='2-Gaon26KM') { if((ohiQ[3]==37 || ohiQ[3]==33) && c==51 && !ohiQ[6]) { // ㅕ+ㅣ→ㅖ, ㅑ+ㅣ→ㅒ ohiQ[4]=1; ohiInsert(f,0,ohiQ); return; } } if(!option.sunalae && c==66-35 && ohiQ[3]==c && !ohiQ[4] && !ohiQ[6]) { // ㅏ+ㅏ→ ㆍ(아래아) ohiQ[4]=0x119E-ohiQ[3]; ohiInsert(f,0,ohiQ); return; } if(!option.sunalae && c==86-35 && ohiQ[3]==c && !ohiQ[4] && !ohiQ[6]) { // ㅣ+ㅣ→ ᅟᆢ(쌍아래아) ohiQ[4]=0x11A2-ohiQ[3]; ohiInsert(f,0,ohiQ); return; } if((!ohiQ[4] || ohiQ[6] || !(ohiQ[3]=-1)) && !ohiQ[6]) ohiQ[4]=ohiDoubleJamo(1,ohiQ[3],c); if((ohiQ[0] && ohiQ[3]>0 && ohiQ[6]) && (ohiQ[7] || !(ohiQ[7]=ohiQ[6]) || !(ohiQ[6]=0))) { ohiInsert(f,0,[ohiQ[0],ohiQ[1],0,ohiQ[3],ohiQ[4],0,ohiQ[6],0,0]); ohiInsert(f,ohiQ,ohiQ=[ohiQ[7],0,0,c,0,0,0,0,0]); } else if((!ohiQ[0] || ohiQ[3]) && (!ohiQ[4] || ohiQ[6]) || ohiQ[3]<0) ohiInsert(f,ohiQ,ohiQ=[0,0,0,c,0,0,0,0,0]); else if(ohiQ[3]=ohiQ[3]||c) ohiInsert(f,0,ohiQ); } } function NFD_hangeul2_preprocess(f,e,key) { var layout_info = find_current_layout_info(); var layout = find_current_layout(); var combined_phoneme, backup_phoneme, backup_phoneme_R; var c = layout[key-33]; if(typeof c == 'object') c = Hangeul2_galmadeuli_selection(c); c = convert_into_unicode_hangeul_phoneme(c); if(unicode_cheos.indexOf(c)>=0) { // 닿소리일 때 if(unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0) { // 바로 앞에 끝소리가 들어왔다면 combined_phoneme=combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0], unicode_cheos_to_ggeut[unicode_cheos.indexOf(c)]); if(combined_phoneme) { // 먼저 들어온 끝소리와 조합되는 닿소리이면 끝소리로 넣음 return unicode_cheos_to_ggeut[unicode_cheos.indexOf(c)]; } else { // 먼저 들어온 끝소리와 조합되지 않은 닿소리이면 조합을 끊고 첫소리로 넣음 complete_hangeul_syllable(f); if(is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInsert(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) { c = convert_into_ohi_hangeul_phoneme(c); // 완성형으로 조합하는 요즘한글 자판일 때 호환 자모로 바꿈 } return c; } } else if(unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 || NFD_stack.phoneme[0]==0x1160) { // 바로 앞에 홀소리 또는 홀소리 채움 문자가 들어왔다면 if(is_old_hangeul_input() || unicode_modern_hangeul_phoneme.indexOf(unicode_cheos_to_ggeut[unicode_cheos.indexOf(c)])>=0) { // 옛한글 자판이거나 요즘낱자에 들어가는 받침이면 c=unicode_cheos_to_ggeut[unicode_cheos.indexOf(c)]; // 끝소리로 넣음 return c; } } else return c; } else if(unicode_ga.indexOf(c)>=0 || c==0x1160) { // 홀소리 또는 홀소리 채움 문자일 때 if(unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0) { // 바로 앞에 끝소리가 들어왔다면 backup_phoneme = unicode_ggeut_to_cheos[unicode_ggeut.indexOf(NFD_stack.phoneme[0])]; backup_phoneme_R = NFD_stack.phoneme_R[0]; ohiHangeul_backspace(f,e); // 앞에 넣은 끝소리를 지움 complete_hangeul_syllable(f); if(is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInsert(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding && unicode_modern_hangeul_phoneme.indexOf(backup_phoneme)>=0 && unicode_modern_hangeul_phoneme.indexOf(c)>=0) { // 요즘한글 자판이고 앞에 들어온 닿소리와 막 들어온 홀소리가 요즘낱자이면 조합을 끊고 완성형으로 바꿈 ohiInsert(f,0,ohiQ=[convert_into_ohi_hangeul_phoneme(backup_phoneme)-127,0,0,0,0,0,0,0,0]); c = convert_into_ohi_hangeul_phoneme(c); } else { NFD_hangeul_input(f,key,backup_phoneme); // 앞에 넣은 끝소리를 첫소리로 바꾸어 넣음 NFD_stack.phoneme_R[0]=backup_phoneme_R; } } if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) { // 완성형으로 조합하는 요즘한글 자판일 때 if(unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 && !combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c)) { // 앞에 들어온 홀소리와 조합되지 않는 홀소리이면 조합을 끊고 호환 자모로 바꿈 complete_hangeul_syllable(f); c = convert_into_ohi_hangeul_phoneme(c); } } return c; } return c; } function seek_ieochigi_abbreviation(abbreviation_table, c1, c2) { // 줄임말 조합을 찾기 (이어치기 자판) var i, chars=null; for(i=0; i<abbreviation_table.length; ++i) if(abbreviation_table[i].phonemes[0]==convert_into_unicode_hangeul_phoneme(c1) && abbreviation_table[i].phonemes[1]==convert_into_unicode_hangeul_phoneme(c2)) return abbreviation_table[i].chars; return null; } function seek_moachigi_abbreviation(abbreviation_table) { // 모아치기 자판의 줄임말 조합을 찾기 var i,j,l=[]; for(i=0; i<abbreviation_table.length; ++i) { if(double_multikey_abbreviated_state) { // 줄여넣기 조합을 이미 두 차례 이어서 했을 때 // 이전 글쇠 조합이 지정된 줄임말 글쇠 조합은 건너뜀 if(typeof abbreviation_table[i].prev_keys != 'undefined' && abbreviation_table[i].prev_keys.length) continue; } if(pressed_keys.length != abbreviation_table[i].keys.length) continue; for(j=0;j<abbreviation_table[i].keys.length;++j) if(pressed_keys.indexOf(abbreviation_table[i].keys[j].charCodeAt(0))<0) break; if(j!=abbreviation_table[i].keys.length) continue; // 기호 확장 if(abbreviation_table[i].keys.length==1 && abbreviation_table[i].keys[0]<0) return abbreviation_table[i].chars; // 줄임말 종류(class) if(typeof abbreviation_table[i].prev_class != 'undefined' && abbreviation_table[i].prev_class.length) { if(prev_class.length) { for(j=0;j<abbreviation_table[i].prev_class.length;++j) if(prev_class.indexOf(abbreviation_table[i].prev_class[j])>=0) break; if(j!=abbreviation_table[i].prev_class.length) { prev_class = typeof abbreviation_table[i].class != 'undefined' ? abbreviation_table[i].class.slice() : []; prev_pressed_keys = []; backup_prev_cursor_position = prev_cursor_position; prev_cursor_position = -1; return abbreviation_table[i].chars; } else continue; } else continue; } if(typeof abbreviation_table[i].prev_keys == 'undefined' || !abbreviation_table[i].prev_keys.length) { // 줄여넣기 조합을 이미 두 차례 이어서 했을 때 if(double_multikey_abbreviated_state) { double_multikey_abbreviated_state = 0; return abbreviation_table[i].chars; } // 이전 글쇠값 정보가 없을 때 if(!prev_pressed_keys.length) { prev_class = typeof abbreviation_table[i].class != 'undefined' ? abbreviation_table[i].class.slice() : []; return abbreviation_table[i].chars; } } if(!prev_pressed_keys.length && typeof abbreviation_table[i].prev_keys != 'undefined' && abbreviation_table[i].prev_keys.length) continue; if(prev_pressed_keys.length && typeof abbreviation_table[i].prev_keys != 'undefined') { if(prev_pressed_keys.length == abbreviation_table[i].prev_keys.length) { for(j=0;j<prev_pressed_keys.length;++j) if(prev_pressed_keys.indexOf(abbreviation_table[i].prev_keys[j].charCodeAt(0))<0) break; if(j==prev_pressed_keys.length) { // 앞의 글쇠값과 들어맞을 때 prev_class = typeof abbreviation_table[i].class != 'undefined' ? abbreviation_table[i].class.slice() : []; prev_pressed_keys = []; double_multikey_abbreviated_state = 1; return abbreviation_table[i].chars; } } } // 앞의 글쇠값과 들어맞지 않으면 후보로 넣음 l.push(i); } if(l.length) { prev_class = typeof abbreviation_table[l[0]].class != 'undefined' ? abbreviation_table[l[0]].class.slice() : []; return abbreviation_table[l[0]].chars; } else return []; } function ohiHangeul3_abbreviation(f,key) { // 이어치기 세벌식 자판에서 줄임말 처리 if(!option.abbreviation || typeof current_layout_info.ieochigi_hangeul_abbreviation_table == 'undefined') return 0; var i,j; var layout = find_current_layout(); var c = layout[key-33]; var ch, chars; var ieochigi_hangeul_abbreviation_table=null; abbreviation_table = current_layout_info.ieochigi_hangeul_abbreviation_table; if(!abbreviation_table || !abbreviation_table.length) return 0; if(!NFD_stack.phoneme.length) { if(ohiQ[6]) ch=ohiQ[6]+ohiQ[7]; else if(ohiQ[3]) ch=ohiQ[3]+ohiQ[4]+35; else if(ohiQ[0]) ch=ohiQ[0]+ohiQ[1]+127; chars=seek_ieochigi_abbreviation(abbreviation_table, convert_into_unicode_hangeul_phoneme(ch), convert_into_unicode_hangeul_phoneme(c)); if(chars) { if(!is_phonemic_writing_input()) ohiBackspace(f); else { complete_hangeul_syllable(f); ohiBackspace(f); } insert_chars(f,chars); return 1; } } return 0; } function ohiHangeul3(f,e,key) { // 세벌식 자판 - 낱자 단위 처리 var i, j, c=0; var sublayout = []; var extended_sign_layout = []; var layout_info = find_current_layout_info(); var layout = find_current_layout(); if(!abbreviation_processing_state) { sublayout = find_sublayout(); extended_sign_layout = find_extended_sign_layout(); prev_cursor_position = -1; } if(unicode_cheos.indexOf(key)>=0 || unicode_ga.indexOf(key)>=0 || unicode_ggeut.indexOf(key)>=0) { // key가 유니코드 한글 낱자일 때 c=key; } else if(layout) { if(typeof layout[key-33]=='object') c=layout[key-33][0]; else c=layout[key-33]; if(ohiHangeul3_HanExtKey && typeof layout_info.extended_hangeul_layout[key-33][ohiHangeul3_HanExtKey-1] == 'number' && (layout_info.extended_hangeul_layout[key-33][ohiHangeul3_HanExtKey-1]>=0)) { // 한글 확장 배열에서 문자 넣기 c = layout_info.extended_hangeul_layout[key-33][ohiHangeul3_HanExtKey-1]; NFD_hangeul_input(f,key,c); // 첫가끝 방식으로 한글 조합하기 esc_ext_state(); return c; } } if(typeof layout_info.extended_hangeul_layout != 'undefined' && typeof layout_info.extended_hangeul_layout[key-33][0] == 'number' && (!ohiHangeul3_HanExtKey && c==-1 || ohiHangeul3_HanExtKey && layout_info.extended_hangeul_layout[key-33][ohiHangeul3_HanExtKey-1]==-1)) { // 한글 확장 배열을 쓰는 상태로 들어가기 ++ohiHangeul3_HanExtKey; if(ohiHangeul3_HanExtKey > layout_info.extended_hangeul_layout[key-33].length) esc_ext_state(); else show_keyboard_layout(); if(ohiHangeul3_HanExtKey) return c; } if(special_chars.indexOf(c)>=0) if(ohiSpecialKey(f,e,c)) return; if((c>64 && c<91 || c>96 && c<123) && !(option.enable_sign_ext && sign_ext_state && extended_sign_layout)) { // 아스키 영역의 영문자들을 한글 낱자로 처리하지 않고 그대로 넣기 위함 (기호 확장 배열을 쓰지 않을 때) if(NFD_stack.phoneme.length) complete_hangeul_syllable(f); ohiInsert(f,0,c); return c; } if(!abbreviation_processing_state || is_moachigi_input()) { if(Ko_type.substr(0,1)=='3') { if(sign_layout_input(f,e,key)) return 0; // 기호 확장 배열 if(c<0) return 0; } if(option.only_NFD_hangeul_encoding || is_old_hangeul_input()) { // 첫가끝 방식으로 조합할 때 if(is_galmadeuli_input()) { // 갈마들이 세벌식 자판 (신세벌식 자판, 갈마들이 공세벌식 자판) c = NFD_galmadeuli_preprocess(f,e,key); if(c==-1) return 0; } else if(Ko_type.substr(1,2)=='t-') { // 타자기 자판 c = hangeul_typewriter(f,key); if(c<=0) return 0; } if(c) { NFD_hangeul_input(f,key,c); // 첫가끝 조합형으로 한글 낱자 처리하기 return 0; } } else { // 첫가끝 방식을 쓰지 않을 때 c = convert_into_ohi_hangeul_phoneme(c); if(is_galmadeuli_input()) { // 갈마들이 세벌식 자판 c = NFC_galmadeuli_preprocess(f,e,key); if(c==-1) return 0; c = convert_into_ohi_hangeul_phoneme(c); } else if(Ko_type.substr(1,2)=='t-') { // 타자기 자판 c = hangeul_typewriter(f,key); if(c<=0) return 0; } } if(!is_old_hangeul_input() && Ko_type.substr(1,2)!='t-' && !is_moachigi_input() && (typeof current_layout_info.hangeul_combination_table != 'undefined' || typeof current_layout_info.moachigi_hangeul_combination_table != 'undefined' || typeof current_layout_info.hangeul_convenience_combination_table != 'undefined') ) { // 옛한글 자판이 아니고 타자기 자판이 아닐 때 낱자 결합 규칙 적용하기 var ch; if(ohiQ[6]) ch=ohiQ[6]+ohiQ[7]; else if(ohiQ[3]) ch=ohiQ[3]+ohiQ[4]+35; else if(ohiQ[0]) ch=ohiQ[0]+ohiQ[1]+127; ch=combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ch),convert_into_unicode_hangeul_phoneme(c)); if(ch) { if(ohiQ[6]) { if(!ohiQ[7]) ohiQ[7]=convert_into_ohi_hangeul_phoneme(ch)-ohiQ[6]; else { // 3타로 넣는 받침이 들어갔을 때(순아래 2014 자판의 ㄹ+ㅁ+ㅁ→ㄿ, 이어치기 방식으로 쓰는 모아치기 자판) ohiQ[8]=convert_into_ohi_hangeul_phoneme(ch)-(ohiQ[6]+ohiQ[7]); } } else if(ohiQ[3]) { if(!ohiQ[4]) ohiQ[4]=convert_into_ohi_hangeul_phoneme(ch)-ohiQ[3]-35; else { // 3타로 넣는 홀소리가 들어갔을 때 (이어치기 방식으로 쓰는 모아치기 자판 ㅗ+ㅏ+ㅣ→ㅙ) ohiQ[5]=convert_into_ohi_hangeul_phoneme(ch)-(ohiQ[3]+ohiQ[4])-35; } } else { if(!ohiQ[1]) ohiQ[1]=convert_into_ohi_hangeul_phoneme(ch)-ohiQ[0]-127; else { // 3타로 넣는 첫소리가 들어갔을 때 ohiQ[2]=convert_into_ohi_hangeul_phoneme(ch)-(ohiQ[0]+ohiQ[1])-127; } } ohiInsert(f,0,ohiQ); return ch; } } // 몇몇 공병우 세벌식 자판에서 첫소리만 들어간 채로 [ 자리 글쇠가 눌렸을 때 아래아를 넣음 if(Ko_type.substr(0,2)=='3-' && Ko_type!='3-sun1990' && Ko_type!='3-91_noshift') { if(key==0x5B && ( (ohiQ[0]&&!ohiQ[3]&&!ohiQ[6] || unicode_cheos.indexOf(NFD_stack.combined_phoneme[0])>=0 ) || NFD_stack.combined_phoneme[0]==0x119E)) { c=0x119E; } } // 3-91 조합 순아래 자판 if(Ko_type=='3-91_noshift') { if(key==0x5B && ( (ohiQ[0]&&ohiQ[3]&&!ohiQ[6] || unicode_ga.indexOf(NFD_stack.combined_phoneme[0])>=0) )) { // 첫소리와 가운뎃소리까지 들어간 채로 [ 자리 글쇠가 눌렸을 때 c=0x11FF; } } } if(!c) { // 부호값이 0이면 조합 끊기 complete_hangeul_syllable(f); return 0; } // 요즘한글 자판에서 처음 들어온 옛낱자 처리 if(!is_old_hangeul_input() && (unicode_NFD_hangeul_phoneme.indexOf(convert_into_unicode_hangeul_phoneme(c))>=0 || NFD_stack.phoneme.length)) { if(!NFD_stack.phoneme.length && unicode_NFD_hangeul_phoneme.indexOf(c)>=0) { // 처음 들어온 옛낱자(c)를 ohiQ에 넣고 ohiInsert 함수로 넘겨 한글 조합 상태를 완성형에서 첫가끝 조합형으로 바꿈 var unicode_phoneme_list = [unicode_cheos, unicode_ga, unicode_ggeut]; for(i=0;i<3;++i) { if(unicode_phoneme_list[i].indexOf(c)>=0) { if(!ohiQ[i*3]) ohiQ[i*3]=c; else if(!combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[i*3]+ohiQ[i*3+1]),convert_into_unicode_hangeul_phoneme(c))) { complete_hangeul_syllable(f); ohiQ[i*3]=c; } else if(!ohiQ[i*3+1]) ohiQ[i*3+1]=c-ohiQ[i*3]; else if(!ohiQ[i*3+2]) ohiQ[i*3+2]=c-ohiQ[i*3]-ohiQ[i*3+1]; ohiInsert(f,0,ohiQ); break; } } return 0; } if(!combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],convert_into_unicode_hangeul_phoneme(c))) { // 앞 낱자와 조합하지 않는 낱자이면 조합을 끊음 if((unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 || unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0) && (unicode_cheos.indexOf(convert_into_unicode_hangeul_phoneme(c))>=0 || unicode_ga.indexOf(convert_into_unicode_hangeul_phoneme(c))>=0)) { // 홀소리나 받침 뒤에 조합되지 않는 첫소리나 홀소리가 오면 조합을 끊기 complete_hangeul_syllable(f); if(unicode_modern_hangeul_phoneme.indexOf(convert_into_unicode_hangeul_phoneme(c))<0) { // 옛낱자일 때 ohiInsert(f,0,ohiQ=[unicode_cheos.indexOf(c)>=0 ? c:0,0,0,unicode_ga.indexOf(c)>=0 ? c:0,0,0,0,0,0]); return 1; } } else if(unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ggeut.indexOf(convert_into_unicode_hangeul_phoneme(c))>=0) { // 조합되지 않는 받침이면 조합 끊기 complete_hangeul_syllable(f); } } } // 요즘한글 자판으로 요즘한글 넣기 if(!NFD_stack.phoneme.length && c>127 && c<158 && c!=147) { // Cho if(NFD_stack.phoneme.length) ohiSelection(f,0); i=ohiQ[1]||ohiQ[3]||!ohiDoubleJamo(0,ohiQ[0],c-127); if(!i) ohiQ=0; ohiInsert(f,i,ohiQ=[c-127,ohiQ?0:1,0,0,0,0,0,0,0]); return i; } else if(!NFD_stack.phoneme.length && c>65 && c<87) { // Jung if((!ohiQ[4] || !(ohiQ[3]=-1)) && !(Ko_type.substr(1,2)=='t-' && ohiRQ[3]+ohiRQ[4]>1)) { ohiQ[4]=ohiDoubleJamo(1,ohiQ[3],c-35); i=1; } else i=0; if((!ohiQ[0] || ohiQ[3]) && (!ohiQ[4] || ohiQ[6]) || ohiQ[3]<0) { ohiInsert(f,ohiQ,ohiQ=[0,0,0,c-35,0,0,0,0,0]); i=0; } else if(ohiQ[3]=ohiQ[3]||c-35) { ohiInsert(f,0,ohiQ); i=1; } return i; } else if(!NFD_stack.phoneme.length && c<31) { // Jong i=0; if(!current_layout_info.hangeul_combination_table && (!ohiQ[7] || !(ohiQ[6]=-1))) { ohiQ[7]=ohiDoubleJamo(2,ohiQ[6],c); if(ohiQ[7]) i=1; } if(!ohiQ[0] || !ohiQ[3] || ohiQ[6] && !ohiQ[7] || ohiQ[6]<0 || (Ko_type.substr(0,3)=='4t-' && ohiRQ[3]+ohiRQ[4]>0)) { ohiInsert(f,ohiQ,ohiQ=[0,0,0,0,0,0,c,0,0]); i=0; } else if(ohiQ[6]=ohiQ[6]||c) { ohiInsert(f,0,ohiQ); i=1; } return i; } if(NFD_stack.phoneme.length) NFD_hangeul_input(f,key,c); // 첫가끝 방식으로 옛한글 조합하기 else ohiInsert(f,0,c); return 0; } function convert_syllable_into_phonemes(f) { // 낱내자를 낱자로 풀어 넣기 (풀어쓰기) var c,i,j,k,chars=[]; var single_phonemes=[], hangeul_conversion_function; if(!(ohiQ[0]+ohiQ[3]+ohiQ[6]) && !NFD_stack.phoneme.length) return; var _ohiQ = ohiQ.slice(); if(option.phonemic_writing_in_halfwidth_letter && !is_old_hangeul_input()) hangeul_conversion_function = convert_into_halfwidth_hangeul_letter; else hangeul_conversion_function = convert_into_compatibility_hangeul_letter; if(is_old_hangeul_input() || option.only_NFD_hangeul_encoding || NFD_stack.phoneme.length && !convert_NFD_into_NFC(NFD_stack.phoneme)) { // 첫가끝 조합형을 쓸 때 var _phoneme = NFD_stack.phoneme.slice(); var _combined_phoneme = NFD_stack.combined_phoneme.slice(); if(unicode_ggeut.indexOf(_combined_phoneme[0])>=0) j=3; else if(unicode_ga.indexOf(_combined_phoneme[0])>=0 || _combined_phoneme[0]==0x1160) j=2; for(i=0; i<j; ++i) ohiBackspace(f); for(i=_combined_phoneme.length-1;i>=0;--i) { // 첫소리 ㅇ 넣지 않기 (풀어쓰기) if(option.phonemic_writing_initial_ieung_ellipsis && _combined_phoneme[i]==0x110B) continue; // 채움 문자 넣지 않기 if(_combined_phoneme[i]==0x115F || _combined_phoneme[i]==0x1160) continue; single_phonemes = []; if(!option.phonemic_writing_in_single_phoneme) single_phonemes.push(_combined_phoneme[i]); else single_phonemes = convert_into_single_phonemes(_combined_phoneme[i]); for(j=0;j<single_phonemes.length;++j) { if(option.only_NFD_hangeul_encoding) NFD_hangeul_single_phoneme_syllable_input(f,single_phonemes[j]); else { c = hangeul_conversion_function(single_phonemes[j]); if(compatibility_hangeul_phoneme.indexOf(c)>=0 || unicode_NFD_hangeul_phoneme.indexOf(c)>=0) ohiInput(f,0,c); // 호환 자모에 없는 첫소리에 채움 문자를 붙임 if(single_phonemes.length==1 && unicode_NFD_hangeul_phoneme.indexOf(single_phonemes[0]>=0) && unicode_cheos.indexOf(c)>=0) ohiInput(f,0,0x1160); } } initialize_NFD_stack(); } } else if(!is_old_hangeul_input() && ohiQ[0]+ohiQ[3]+ohiQ[6]) { // 요즘한글 배열을 쓸 때 if(ohiQ[0]+ohiQ[3]+ohiQ[6]) { ohiInsert(f,0,0); ohiBackspace(f); } // 첫소리 ㅇ 넣지 않기 if(option.phonemic_writing_initial_ieung_ellipsis) { if(_ohiQ[0]+_ohiQ[1]+_ohiQ[2]==23) _ohiQ[0]=_ohiQ[1]=_ohiQ[2]=0; } for(i=0;i<3;++i) { if(!_ohiQ[i*3]) continue; k=_ohiQ[i*3]+_ohiQ[i*3+1]+_ohiQ[i*3+2]; c=k+(i==0?127:i==1?35:0); if(option.phonemic_writing_in_single_phoneme) { // 겹낱자를 홑낱자로 풀어 넣기 single_phonemes = convert_into_single_phonemes(c); if(single_phonemes.length) { for(j=0;j<single_phonemes.length;++j) { if(option.only_NFD_hangeul_encoding) NFD_hangeul_single_phoneme_syllable_input(f,single_phonemes[j]); else ohiInsert(f,0,hangeul_conversion_function(single_phonemes[j])); } continue; } } c = 0x3130+_ohiQ[i*3]+_ohiQ[i*3+1]+_ohiQ[i*3+2]; if(option.phonemic_writing_in_halfwidth_letter) c = convert_into_halfwidth_hangeul_letter(c); ohiInsert(f,0,c); } } } function ohiHangeul3_moa(f,e) { // 모아치기 세벌식 자판 처리 var i,j,k,l,m; var c; var layout=current_layout_info.layout; var extended_sign_layout; var combination_table; var pressed_chars = []; var temp_pressed_chars = []; var backup_prev_pressed_keys = []; var special_keys = [32,13,8]; // 사이띄개(32), 줄바꾸개(13), 뒷걸음쇠(8) var chars=[]; var cheos = [], ga = [], ggeut = []; var front_etc = [], rear_etc = []; var front_special = [], rear_special = []; var necessary_backspaces_cheos=0; var necessary_backspaces_ga=0; var necessary_backspaces_ggeut=0; var necessary_backspaces_sign=0; if(option.enable_sign_ext && typeof current_layout_info.extended_sign_layout != 'undefined' && sign_ext_state) { // 기호 확장 배열에서 기호를 넣음 if(sign_layout_input(f,e,pressed_keys[0])) pressed_keys.splice(0,1); if(pressed_keys.length>1) esc_ext_state(); } for(i=0;i<pressed_keys.length;++i) { // 특수 글쇠를 추림 if(special_keys.indexOf(pressed_keys[i])>=0) { if(!i) front_special.push(pressed_keys[0]); else rear_special.push(pressed_keys[i]); pressed_keys.splice(i,1); } else pressed_chars.push(convert_into_unicode_hangeul_phoneme(layout[pressed_keys[i]-33])); } if(typeof current_layout_info.moachigi_multikey_abbreviation_table != 'undefined') { // 모아치기 글쇠 기준 줄임말·예외 조합 (모아치기 조합 가운데 가장 먼저 적용됨) combination_table = current_layout_info.moachigi_multikey_abbreviation_table; backup_prev_pressed_keys = prev_pressed_keys.slice(); backup_prev_cursor_position = -1; chars = seek_moachigi_abbreviation(combination_table); if(chars.length==1 && combination_table[i].chars[0]<0 && combination_table[i].chars[0]>=-3) { // 부호값이 -1 ~ -3이면 기호 확장 배열 상태로 들어감 sign_layout_input(f,e,combination_table[i].chars[0]); return; } if(chars.length) { // 줄임말 조합을 글쇠 조합으로 이어서 하는 때에 먼저 들어간 줄임말을 지우고 다음 줄임말을 넣음 if(backup_prev_pressed_keys.length && !prev_pressed_keys.length && prev_cursor_position > -1) ohiHangeul_moa_backspace(f,e); insert_chars(f,chars); if(backup_prev_cursor_position > -1) prev_cursor_position = backup_prev_cursor_position; return; } } if(typeof current_layout_info.moachigi_hangeul_abbreviation_table != 'undefined') { // 모아치기 한글 낱자 기준 줄임말·예외 조합 combination_table = current_layout_info.moachigi_hangeul_abbreviation_table; for(i=0;i<combination_table.length;++i) { if(pressed_chars.length != combination_table[i].phonemes.length) continue; for(j=0;j<combination_table[i].phonemes.length;++j) { if(pressed_chars.indexOf(combination_table[i].phonemes[j])<0) break; } if(j!=combination_table[i].phonemes.length) continue; insert_chars(f,combination_table[i].chars); return; } } chars = []; if(typeof current_layout_info.moachigi_hangeul_combination_table != 'undefined') { // 모아치기 한글 낱자 조합 규칙 (낱자 차례를 따지지 않음) combination_table = current_layout_info.moachigi_hangeul_combination_table; for(i=0;i<combination_table.length;++i) { temp_pressed_chars = pressed_chars.slice(); for(j=0;j<combination_table[i].phonemes.length;++j) { k=temp_pressed_chars.indexOf(combination_table[i].phonemes[j]); if(k<0) break; temp_pressed_chars.splice(k,1); } if(j!=combination_table[i].phonemes.length) continue; chars.push(combination_table[i].char); for(j=0;j<combination_table[i].phonemes.length;++j) { k=pressed_chars.indexOf(combination_table[i].phonemes[j]); pressed_keys.splice(k,1); pressed_chars.splice(k,1); } } } for(i=0;i<pressed_keys.length;++i) { if(special_keys.indexOf(pressed_keys[i])>=0) c=pressed_keys[i]; else c=convert_into_unicode_hangeul_phoneme(layout[pressed_keys[i]-33]); chars.push(c); } for(i=0;i<front_special.length;++i) { c=front_special[i]; if(c==8) { // 뒷걸음쇠(backspace) if(ohiHangeul_moa_backspace(f,e)) continue; if(e.preventDefault) e.preventDefault(); ohiBackspace(f); } else ohiInsert(f,0,c); if(c==13) complete_hangeul_syllable(f); // 줄바꾸개(enter) } for(i=0;i<chars.length;++i) { c=chars[i]; if(!i && special_keys.indexOf(c)>=0) { special.push(c); continue; } if(unicode_cheos.indexOf(c)>=0) cheos.push(c); else if(unicode_ga.indexOf(c)>=0) ga.push(c); else if(unicode_ggeut.indexOf(c)>=0) ggeut.push(c); else { if(!cheos.length && !ga.length && !ggeut.length) front_etc.push(c); else rear_etc.push(c); } } prev_cursor_position = -1; for(i=0;i<front_etc.length;++i) ohiInsert(f,0,front_etc[i]); insert_chars(f,cheos.concat(ga,ggeut)); for(i=0;i<rear_etc.length;++i) { prev_cursor_position = -1; ohiInsert(f,0,rear_etc[i]); } for(i=0;i<rear_special.length;++i) { c=rear_special[i]; prev_cursor_position = -1; if(c==8) { // 뒷걸음쇠(backspace) if(!ohiHangeul_moa_backspace(f,e)) continue; if(e.preventDefault) e.preventDefault(); ohiBackspace(f); } else ohiInsert(f,0,c); if(c==13) complete_hangeul_syllable(f); // 줄바꾸개(enter) } pressed_keys = []; return; } function insert_chars(f,combination_table_chars) { // 여러 문자를 넣음 (줄임말을 넣을 때) if(typeof combination_table_chars == 'undefined' || typeof combination_table_chars.length == 'undefined') return; var chars = combination_table_chars.slice(); var a=[],h=0,i,j,k,l; abbreviation_processing_state=1; if(is_phonemic_writing_input() && option.phonemic_writing_in_single_phoneme) { // 겹낱자를 홑낱자로 풀어서 풀어쓰기할 때 for(i=0;i<chars.length;++i) { a=convert_into_single_phonemes(chars[i]); if(a.length) for(j=0;j<a.length;++j) chars.splice(i+j, !j?1:0, a[j]); } } for(i=0;i<chars.length;++i) { // 완성형 낱내자 부호값(NFC)을 첫가끝 조합형 낱자 단위 부호값(NFD)으로 바꿈 a = convert_NFC_into_NFD(chars[i]); if(a.constructor == Array && a.length==3 && a[0]+a[1]+a[2]) { chars.splice(i, 1, a[0],a[1]); if(a[2]) chars.splice(i+2, 0, a[2]); } } if(chars.length) { prev_cursor_position = f.selectionEnd; if(f.selectionStart!=f.selectionEnd && is_phonemic_writing_input()) { prev_cursor_position += ohiQ[0]+ohiQ[3]+ohiQ[6] ? 1:0; if(NFD_stack.phoneme.length) prev_cursor_position += NFD_stack.combined_phoneme.length + (NFD_stack.combined_phoneme.length==3 && !option.phonemic_writing_NFD_ggeut_to_cheos ? 1:0); } for(i=0;i<chars.length;++i) { if(unicode_NFD_hangeul_phoneme.indexOf(chars[i])>=0) ohiHangeul3(f,0,chars[i]); // 한글 낱자일 때 else ohiInsert(f,0,chars[i]); // 한글 낱자가 아닐 때 } } abbreviation_processing_state = 0; } function sign_layout_input(f,e,key) { var c, i; var layout_info = find_current_layout_info(); var sign_layout = find_extended_sign_layout(); if(!option.enable_sign_ext || !sign_layout) return 0; if(Ko_type.substr(0,3)=='3m-') { // 세모이 자판을 비롯한 모아치기 자판 if(sign_ext_state>0) { // 기호 확장 배열에서 기호를 넣음 c=sign_layout[key-33][sign_ext_state-1]; ohiInsert(f,0,c); esc_ext_state(); return 1; } else if(key<0 && key>-4) { // 기호 확장 배열로 들어감 sign_ext_state=-key; show_keyboard_layout(); return 0; } esc_ext_state(); return 0; } // 요즘한글 3-2011, 3-2012 자판의 특수기호 확장 배열 if(!is_old_hangeul_input() && (Ko_type=='3-2011' || Ko_type=='3-2012')) { if((key==118 || key==56) && ( !NFD_stack.phoneme.length&&(!ohiQ[0]&&!ohiQ[3] || ohiQ[3]) || NFD_stack.phoneme.length&&unicode_cheos.indexOf(NFD_stack.combined_phoneme[0])<0&&(unicode_ga.indexOf(NFD_stack.combined_phoneme[0])>=0 || unicode_ggeut.indexOf(NFD_stack.combined_phoneme[0])>=0)) ) { // 3-2011, 3-2012 자판의 왼쪽 특수기호 확장 글쇠(ㅗ·ㅢ)가 눌린 횟수 더하기 i=0; if(key==118 && !sign_layout[key-33][sign_ext_state%10-1]) { // 왼쪽 ㅗ if(sign_ext_state<10) ++sign_ext_state; else sign_ext_state+=2; i=1; } if(key==56 && !sign_layout[key-33][sign_ext_state%10-1]) { // 오른쪽 ㅢ if(!sign_ext_state) sign_ext_state=10; if(sign_ext_state>=10) ++sign_ext_state; else sign_ext_state+=2; i=1; } if(i) { if(sign_ext_state%10>3) sign_ext_state=0; show_keyboard_layout(Ko_type); return 1; } } if(sign_ext_state) { if(NFD_stack.phoneme.length) complete_hangeul_syllable(f); c=sign_layout[key-33][sign_ext_state%10-1]; ohiInsert(f,0,c); esc_ext_state(); return 1; } } if(!ohiHangeul3_HanExtKey && (key==0x2F || key==0x39) && (((!ohiQ[0]&&!ohiQ[3] || ohiQ[3] || ohiQ[0]&&(key==0x2F || key==0x39) && sign_ext_state) && ((unicode_cheos.indexOf(NFD_stack.phoneme[0])<0&&unicode_ga.indexOf(NFD_stack.phoneme[0])<0&&unicode_ggeut.indexOf(NFD_stack.phoneme[0])<0) || unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 || unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0))) ) { // 나머지 공세벌식 자판의 기호 확장 글쇠가 눌린 횟수 더하기 if(ohiRQ[3] && !ohiQ[4] && !ohiQ[6] && (ohiQ[3]==39 || ohiQ[3]==44)) { // 받침이 들어가지 않은 때에 오른쪽 ㅗ 또는 ㅜ 자리 글쇠가 두 번 눌리면 ㅗ 또는 ㅜ를 지움 (첫소리 다음에 확장 기호를 넣기 위함) ohiRQ[3]=0; ohiHangeul_backspace(f,e); } var i,j,k,l,sign_layout_depth=[0,0]; if(sign_layout[0].constructor == Array) { for(i=0;i<sign_layout.length;++i) { for(j=0;j<2;++j) { if(sign_layout[i][j].constructor != Array) continue; for(k=sign_layout_depth[j];k<sign_layout[i][j].length;++k) if(sign_layout[i][j][k] && k+1 > sign_layout_depth[j]) sign_layout_depth[j] = k+1; } } } if(key==0x2F && sign_ext_state%10<sign_layout_depth[0]) { // 밑기호 글쇠(오른쪽 ㅗ)가 눌렸을 때 if(sign_ext_state>10) esc_ext_state(); ++sign_ext_state; if(sign_ext_state>sign_layout_depth[0]) esc_ext_state(); else show_keyboard_layout(Ko_type); return 1; } if(key==0x39 && sign_ext_state%10<sign_layout_depth[1]) { // 윗기호 글쇠(오른쪽 ㅜ)가 눌렸을 때 if(sign_ext_state<11) {esc_ext_state(); sign_ext_state=10;} ++sign_ext_state; if(sign_ext_state%10>sign_layout_depth[1]) esc_ext_state(); else show_keyboard_layout(Ko_type); return 1; } } if(sign_ext_state) { // 나머지 공세벌식 자판의 기호 확장 배열 if(sign_layout[key-33].constructor == Array) { if(sign_ext_state<11) c=sign_layout[key-33][0][sign_ext_state-1]; if(sign_ext_state>10) c=sign_layout[key-33][1][sign_ext_state%10-1]; } else c=sign_layout[key-33]; if(NFD_stack.phoneme.length && key!=8 && unicode_NFD_hangeul_sidedot.indexOf(c)<0) complete_hangeul_syllable(f); ohiInsert(f,0,c); esc_ext_state(); return 1; } return 0; } function Sin3_extended_sign_layout_input(f,key,c1) { // 첫소리 ㅇ,ㄱ,ㅈ,ㅂ 자리 글쇠를 쓰는 신세벌식 자판의 확장 배열 기호 넣기 var c; var extended_sign_layout = find_extended_sign_layout(); if(is_old_hangeul_input() && checkCapsLock()) return 0; if(option.enable_sign_ext && sign_ext_state && extended_sign_layout) { // 신세벌식 기호 확장 배열에서 문자를 넣을 때 c = extended_sign_layout[key-33][sign_ext_state-1]; ohiBackspace(f); ohiInsert(f,0,c); esc_ext_state(); initialize_NFD_stack(); return -1; } else if(option.enable_sign_ext && !sign_ext_state && extended_sign_layout && NFD_stack.phoneme.length==1 && NFD_stack.phoneme[0]==0x110B/*ㅇ*/ && (c1==0x1100/*ㄱ*/ || c1==0x110C/*ㅈ*/ || c1==0x1107/*ㅂ*/)) { // 신세벌식 기호 확장 배열 상태로 넘어가는 조건이 갖추어졌을 때 (NFD) if(c1==0x1100) sign_ext_state=1; else if(c1==0x110C) sign_ext_state=2; else if(c1==0x1107) sign_ext_state=3; show_keyboard_layout('Sin3-ext'); return -1; } else if(option.enable_sign_ext && !sign_ext_state && extended_sign_layout && ohiQ[0]==150-92-35 && (c1==128 || c1==151 || c1==145) && !ohiQ[3] && !ohiQ[6]) { // 신세벌식 기호 확장 배열 상태로 넘어가는 조건이 갖추어졌을 때 (NFC) if(c1==128) sign_ext_state=1; else if(c1==151) sign_ext_state=2; else if(c1==145) sign_ext_state=3; show_keyboard_layout('Sin3-ext'); return -1; } return 0; } function NFD_hangeul_input(f,key,c) { // 첫가끝(세벌식) 부호계를 쓰는 요즘한글/옛한글 처리 if(c==0x1160 && NFD_stack.phoneme[0]==0x1160) return; // 가운뎃소리 채움 문자가 잇달아 들어오면 처리하지 않음 if(unicode_NFD_hangeul_sidedot.indexOf(c)>=0) { // 성조를 나타내는 방점일 때 ohiInsert(f,0,c); return; } ohiSelection(f,0); var diphthong=0; // 겹홀소리의 첫 홀소리인지 (갈마들이 자판) if(c<0) { c=-c; diphthong=1; } var type_name=''; if(typeof current_layout_info.type_name != 'undefined') type_name = current_layout_info.type_name; else if(is_old_hangeul_input() && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined') type_name = current_layout_info.old_hangeul_layout_type_name; if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) c = convert_into_unicode_hangeul_phoneme(c); if(is_old_hangeul_input() && Ko_type.substr(0,2)=='3-' && ['3-2011','3-2011-y','3-2012','3-2012-y','3-2014','3-2014-y','3-2015P','3-2015P-y'].indexOf(Ko_type)>=0) { // 전환 글쇠를 쓰는 한글 확장 배열 처리 (3-2011 / 3-2012 / 3-2014 / 3-2015P 옛한글) if(key==55 || c==0x1168) { // 첫째 한글 확장 글쇠(ㅖ 자리 글쇠)가 눌렸을 때 if(ohiHangeul3_HanExtKey%0x10==2 || ohiHangeul3_HanExtKey==0x11) { esc_ext_state(); complete_hangeul_syllable(f); return false;} if(ohiHangeul3_HanExtKey>0x10) {esc_ext_state(); return false;} ohiHangeul3_HanExtKey = (ohiHangeul3_HanExtKey&&ohiHangeul3_HanExtKey)*0x10+1; show_keyboard_layout('3-2012y_han_ext'); return false; } else if(key==56 || c==0x1174) { // 두째 한글 확장 글쇠(ㅢ 자리 글쇠)가 눌렸을 때 if(ohiHangeul3_HanExtKey%0x10==1 || ohiHangeul3_HanExtKey==0x12) { esc_ext_state(); complete_hangeul_syllable(f); return false;} if(ohiHangeul3_HanExtKey>0x10) {esc_ext_state(); return false;} ohiHangeul3_HanExtKey = (ohiHangeul3_HanExtKey&&ohiHangeul3_HanExtKey)*0x10+2; show_keyboard_layout('3-2012y_han_ext'); return false; } if(ohiHangeul3_HanExtKey) { // 한글 확장 배열에서 넣기 layout = K3_2012y_extended_hangeul_layout; c=layout[key-33][ohiHangeul3_HanExtKey%0x10-1][ohiHangeul3_HanExtKey/0x10]; c=layout[key-33][ohiHangeul3_HanExtKey%0x10-1][ohiHangeul3_HanExtKey>0x10 ? 1:0]; } } // 공세벌식 자판에서 첫소리가 들어온 뒤 [ 자리 글쇠가 눌렸을 때 아래아를 넣음 if(Ko_type.substr(0,2)=='3-' && key==0x5B && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && !ohiHangeul3_HanExtKey) c=0x119E; if(NFD_stack.phoneme[0]!=0x1160 && NFD_stack.combined_phoneme[0]==0x1160 && unicode_cheos.indexOf(NFD_stack.combined_phoneme[1])>=0 && (unicode_NFD_hangeul_phoneme.indexOf(c)>=0 || c==0x1160)) { // 바로 앞서 첫소리가 들어 왔고 한글 낱자가 들어왔다면 가운뎃소리 채움 문자를 지움 ohiBackspace(f); NFD_stack.combined_phoneme.splice(0,1); } // 가운뎃소리 채움 문자 (두벌식 자판) if(c==0x1160) { if(unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 || NFD_stack.phoneme[0]==0x1160) { // 바로 앞에 가운뎃소리가 들어왔으면 조합 끊음 complete_hangeul_syllable(f); if(is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInput(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) } // 한글을 조합하지 않던 상태였으면 첫소리 채움 문자를 넣음 if(!NFD_stack.phoneme.length) { NFD_stack.combined_phoneme.unshift(0x115F); ohiInput(f,0,0x115F); // 첫소리 채움 } // 가운뎃소리 채움 문자가 들어가지 않았으면 가운뎃소리 채움 문자를 넣음 if(NFD_stack.combined_phoneme[0]!=0x1160) { NFD_stack.phoneme.unshift(c); NFD_stack.phoneme_R.unshift(0); NFD_stack.combined_phoneme.unshift(0x1160); ohiInput(f,0,0x1160); } ohiSelection(f,NFD_stack.combined_phoneme.length); return; } var combined_phoneme=combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c); // 앞 낱자와 조합하지 않는 첫소리나 한글이 아닌 문자가 들어왔을 때에 조합을 끊음 if(!combined_phoneme&&unicode_cheos.indexOf(c)>=0 || unicode_NFD_hangeul_code.indexOf(c)<0) { if(unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && NFD_stack.combined_phoneme.indexOf(0x1160)<0) { // 첫소리만 들어 있었으면 가운뎃소리 채움 문자를 넣음 ohiInput(f,0,0x1160); NFD_stack.combined_phoneme.unshift(0x1160); } i = unicode_NFD_hangeul_code.indexOf(c)>=0 && NFD_stack.phoneme.length ? 1 : 0; complete_hangeul_syllable(f); if(i && is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInput(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) } if(!combined_phoneme && (unicode_ga.indexOf(c)>=0 || compatibility_ga.indexOf(c)>=0) && unicode_cheos.indexOf(NFD_stack.phoneme[0])<0) { // 앞에 첫소리가 없이 가운뎃소리가 들어왔을 때 i = /*unicode_NFD_hangeul_code.indexOf(c)>=0 &&*/ NFD_stack.phoneme.length ? 1 : 0; complete_hangeul_syllable(f); if(i && option.phonemic_writing_adding_space_every_syllable_end && is_phonemic_writing_input()) ohiInput(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) ohiInput(f,0,0x115F); // 첫소리 채움 문자 넣음 NFD_stack.combined_phoneme = []; NFD_stack.combined_phoneme.unshift(0x115F); } else if(!combined_phoneme && unicode_ggeut.indexOf(c)>=0) { // 끝소리가 들어왔을 때 if(!is_old_hangeul_input() && !option.only_NFD_hangeul_encoding && NFD_stack.combined_phoneme.length==2 && unicode_ga.indexOf(NFD_stack.combined_phoneme[0])>=0 && unicode_cheos.indexOf(NFD_stack.combined_phoneme[1])<0) { // 요즘한글 자판이고 앞에 첫소리 없이 홀소리가 들어왔다면 complete_hangeul_syllable(f); ohiInsert(f,0,ohiQ=[convert_into_ohi_hangeul_phoneme(c),0,0,0,0,0,0,0,0]); return; } if(unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && NFD_stack.combined_phoneme.indexOf(0x1160)<0) { // 바로 앞에 첫소리가 들어왔다면 가운뎃소리 채움 문자 넣음 ohiInput(f,0,0x1160); NFD_stack.combined_phoneme.unshift(0x1160); } else if(unicode_cheos.indexOf(NFD_stack.phoneme[0])<0 && unicode_ga.indexOf(NFD_stack.phoneme[0])<0 && NFD_stack.combined_phoneme.indexOf(0x115F)<0 && NFD_stack.combined_phoneme.indexOf(0x1160)<0) { // 바로 앞에 한글 낱자나 채움 문자가 들어오지 않았을 때 첫소리·가운뎃소리 채움 문자를 넣음 i = unicode_NFD_hangeul_code.indexOf(c)>=0 && NFD_stack.phoneme.length ? 1 : 0; complete_hangeul_syllable(f); if(i && is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInput(f,0,32); // 풀어쓰기할 때 낱내자 뒤에 빈칸 넣기 (한글 조합이 새로 이어질 때) ohiInput(f,0,0x115F); // 첫소리 채움 ohiInput(f,0,0x1160); // 가운뎃소리 채움 NFD_stack.combined_phoneme.unshift(0x1160,0x115F); } } NFD_stack.phoneme.unshift(c); NFD_stack.phoneme_R.unshift(diphthong); if(combined_phoneme) { NFD_stack.combined_phoneme[0] = combined_phoneme; ohiBackspace(f); ohiInput(f,0,combined_phoneme); } else { if(unicode_ggeut.indexOf(c)>=0 && unicode_ggeut.indexOf(NFD_stack.combined_phoneme[0])>=0) { // 먼저 들어온 끝소리와 조합하지 않는 끝소리가 들어왔으면 낱내 조합을 끊고 채움 문자를 넣음 complete_hangeul_syllable(f); ohiInput(f,0,0x115F); // 첫소리 채움 ohiInput(f,0,0x1160); // 가운뎃소리 채움 NFD_stack.combined_phoneme.unshift(0x1160,0x115F); NFD_stack.phoneme.unshift(c); } NFD_stack.combined_phoneme.unshift(c); ohiInsert(f,0,c); } if(unicode_cheos.indexOf(c)>=0) { // 넣은 낱자가 첫소리이면 가운뎃소리 채움 문자 넣음 ohiInput(f,0,0x1160); NFD_stack.combined_phoneme.unshift(0x1160); } esc_ext_state(); if(NFD_stack.combined_phoneme.length && unicode_NFD_hangeul_phoneme.indexOf(c)>=0) { ohiSelection(f,NFD_stack.combined_phoneme.length); } } function NFD_hangeul_single_phoneme_syllable_input(f,c) { var a=[],i; c=convert_into_unicode_hangeul_phoneme(c); if(is_phonemic_writing_input() && option.phonemic_writing_in_single_phoneme && option.phonemic_writing_NFD_ggeut_to_cheos) { if(unicode_ggeut.indexOf(c)>=0) { // 풀어쓰기 끝소리 → 첫소리 a=convert_into_single_phonemes(c); if(a.length>1) for(i=0;i<a.length;++i) NFD_hangeul_single_phoneme_syllable_input(f,a[i]); else c = unicode_ggeut_to_cheos[unicode_ggeut.indexOf(c)]; } } if(unicode_cheos.indexOf(c)>=0) ohiInsert(f,0,c); // 첫소리 넣기 else if(unicode_ga.indexOf(c)>=0 || unicode_ggeut.indexOf(c)>=0) ohiInput(f,0,0x115F); // 첫소리 채움 문자 넣기 if(unicode_ga.indexOf(c)>=0) ohiInsert(f,0,c); // 첫소리 채움 문자 넣기 else if(unicode_cheos.indexOf(c)>=0 || unicode_ggeut.indexOf(c)>=0) ohiInput(f,0,0x1160); // 가운뎃소리 채움 문자 넣기 if(unicode_ggeut.indexOf(c)>=0) ohiInsert(f,0,c); // 끝소리 넣기 initialize_NFD_stack(); } function converting_for_special_galmadeuli_layouts(f, e, key, c1, c2, sub_c1, sub_c2, transform) { // 신세벌식 자판과 다른 배열 방식을 쓰는 갈마들이 세벌식 자판을 신세벌식 자판의 배열 방식으로 처리할 수 있게 문자값 자리를 바꿈 var layout = find_current_layout(); var a = [c1, sub_c1, c2, sub_c2]; var _c1, _c2; if(Ko_type.substr(0,9)=='Sin3-Cham') { // 참신세벌식 if(NFD_stack.phoneme.length==1 && unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ga.indexOf(c1)>=0 && combine_unicode_NFD_hangeul_phoneme(NFD_stack.phoneme[0],c1)) { // 홀소리만 들어갔는데 먼저 들어간 것과 조합되는 홀소리이면 홀소리를 넣음 } else if(NFD_stack.phoneme.length && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ga.indexOf(c1)>=0 && !combine_unicode_NFD_hangeul_phoneme(NFD_stack.phoneme[0],sub_c1)) { // 앞의 끌소리와 조합되지 않는 끝소리이면 홀소리를 넣음 } else if((ohiQ[0] || NFD_stack.phoneme.length) && unicode_ga.indexOf(c1)>=0 && unicode_ggeut.indexOf(sub_c1)>=0) { // 한글을 조합하고 있고 가운뎃소리와 끝소리가 있는 글쇠 자리 a = [sub_c1, c2, c1, sub_c2]; } else if(unicode_ga.indexOf(c1)>=0 && unicode_cheos.indexOf(sub_c1)>=0) { if(!ohiQ[0] || !NFD_stack.phoneme.length || unicode_cheos.indexOf(NFD_stack.phoneme[0])<0) { // 기본 배열(c1)에 가운뎃소리가 있고 보조 배열(sub_c1)에 첫소리가 있는 자리 (첫소리 ㅋ과 ㅑ가 있는 b 자리) a = [sub_c1, c1, c2, sub_c2]; } } else if(unicode_NFD_hangeul_phoneme.indexOf(c1)<0 && unicode_ggeut.indexOf(sub_c1)>=0 && (ohiQ[0] && ohiQ[3] && !ohiQ[6] || unicode_ga.indexOf(NFD_stack.phoneme[0])>=0)) { // 끝소리 ㅋ (B 자리) a = [sub_c1, c1, c2, sub_c2]; } else if(sub_c1==0x1B && (ohiQ[0]+ohiQ[3]+ohiQ[6] || NFD_stack.phoneme.length)) { // 한글 조합을 멈춤 (escape) a = [0, 0, c2, sub_c2]; complete_hangeul_syllable(f); } return [a[0], a[1], a[2], a[3], transform]; } if(Ko_type.substr(0,2)=='3-') { transform = true; if(Ko_type == '3-18Na') { if(with_shift_key(key) && unicode_ggeut.indexOf(sub_c2)>=0 && unicode_ga.indexOf(c2)>=0 && (NFD_stack.phoneme.length && unicode_ggeut.indexOf(NFD_stack.phoneme[0])<0 || (ohiQ[0]+ohiQ[3])&&!ohiQ[6])) { // 윗글쇠 눌러 겹받침 넣기 a = [sub_c2, sub_c1, c2, c1]; } else if((!with_shift_key(key) && unicode_ggeut.indexOf(c2)>=0 && unicode_ggeut.indexOf(sub_c2)>=0 && unicode_ga.indexOf(c1)>=0) || (with_shift_key(key) && unicode_ggeut.indexOf(c1)>=0 && unicode_ggeut.indexOf(sub_c1)>=0 && unicode_ga.indexOf(c2)>=0)) { // 끝소리가 들어갔고 가운뎃소리와 끝소리가 있는 글쇠가 눌렸을 때 if( (unicode_non_combined_ggeut.indexOf(convert_into_unicode_hangeul_phoneme(ohiQ[6]))>=0 || NFD_stack.phoneme.length && unicode_non_combined_ggeut.indexOf(NFD_stack.combined_phoneme[0])>=0) && (!with_shift_key(key) && unicode_ggeut.indexOf(sub_c2)>=0 && (NFD_stack.phoneme.length && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && sub_c2 != NFD_stack.phoneme[0] && c2 == NFD_stack.phoneme[0] && c2==NFD_stack.combined_phoneme[0] || ohiQ[6] && !ohiQ[7] /*&& sub_c2 != convert_into_unicode_hangeul_phoneme(ohiQ[6]) && c2 == convert_into_unicode_hangeul_phoneme(ohiQ[6])*/) || with_shift_key(key) && unicode_ggeut.indexOf(sub_c1)>=0 && (NFD_stack.phoneme.length && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && sub_c1 != NFD_stack.phoneme[0] && c1 == NFD_stack.phoneme[0] && c1==NFD_stack.combined_phoneme[0] || ohiQ[6] && !ohiQ[7] /*&& sub_c1 != convert_into_unicode_hangeul_phoneme(ohiQ[6]) && c1 == convert_into_unicode_hangeul_phoneme(ohiQ[6])*/)) ) { // 보조 배열에도 끝소리가 있고 끝소리가 하나만 들어갔을 때 // 먼저 들어간 것과 조합되는 끝소리이면 끝소리를 넣고, 그렇지 않으면 가운뎃소리를 넣음 (나빌 입력기에 없는 처리) (조합이 막히는 때를 막음) _c1 = with_shift_key(key) ? sub_c1 : sub_c2; _c2 = with_shift_key(key) ? sub_c2 : sub_c1; if(!combine_unicode_NFD_hangeul_phoneme((ohiQ[6] ? convert_into_unicode_hangeul_phoneme(ohiQ[6]) : NFD_stack.combined_phoneme[0]), _c1)) { if(_c1 == convert_into_unicode_hangeul_phoneme(ohiQ[6]) || _c1 == NFD_stack.combined_phoneme[0]) {_c1 = c1;} else { if(!ohiQ[0] && !NFD_stack.phoneme.length) ohiBackspace(f,e); else ohiHangeul_backspace(f,e); } } else if(combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[6]), sub_c2)) { _c1 = sub_c2; } else { _c1 = c1; } return [_c1, a[1], a[2], a[3], 1]; } if(ohiQ[6] && with_shift_key(key) && unicode_ggeut.indexOf(c1)>=0 && unicode_non_combined_ggeut.indexOf(convert_into_unicode_hangeul_phoneme(ohiQ[6]+ohiQ[7]))<0) { // 겹받침이 조합된 다음에 받침이 또 들어왔으면 조합 끊기 (완성형) complete_hangeul_syllable(f); } } else if(!with_shift_key(key) && unicode_ggeut.indexOf(c1)>=0 && unicode_ga.indexOf(sub_c1)<0 && unicode_ga.indexOf(c2)<0 && unicode_ga.indexOf(sub_c2)<0) { if(unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && NFD_stack.combined_phoneme[0] != c1 || ohiQ[6] && convert_into_ohi_hangeul_phoneme(c1) != ohiQ[6]+ohiQ[7]) { // 끝소리만 있는 받침 ㅈ 자리(; 자리) 글쇠가 거듭 눌렸을 때 조합 끊기 complete_hangeul_syllable(f); } } } } if(Ko_type.substr(0,5)=='LGG3-') { // 조합되지 않는 받침이 들어왔으면 조합을 끊음 if(ohiQ[6] && ohiQ[7] && unicode_ggeut.indexOf(c1)>=0 && (ohiQ[8] || !combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[6]+ohiQ[7]),convert_into_unicode_hangeul_phoneme(c1)))) {complete_hangeul_syllable(f);} } if(Ko_type.substr(0,5)=='Sin3-') { // 신세벌식 원안과 달리 홀소리를 아랫글 자리에 두고 받침을 윗글 자리에 두는 배열 방식이면 transform = true if(typeof layout[64] != 'number') i=layout[64][0], j=layout[shift_table[64]][0]; // a 자리 else i=layout[64], j=layout[shift_table[64]]; if(!with_shift_key(key) && unicode_ga.indexOf(i)>=0) transform = true; else if(with_shift_key(key) && unicode_ga.indexOf(j)>=0) transform = true; } // 홀소리를 아랫글 자리에 두고 받침을 윗글 자리에 두는 신세벌식 자판을 함께 처리하기 위한 작업 if(transform && /*!with_shift_key(key) && */unicode_ga.indexOf(c1)>=0 && unicode_ggeut.indexOf(c2)>=0 && (NFD_stack.phoneme.length && (unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 || NFD_stack.phoneme.length>1 && unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 || unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2)) || ohiQ[0]&&!ohiQ[3]&&!ohiQ[6] || ohiQ[0]&&ohiQ[3]&&!ohiQ[6] || ohiQ[0]&&ohiQ[3]&&ohiQ[6]&&!ohiQ[7])) { // 한글을 차례대로 조합하고 있는데 현재 들어간 낱자와 조합되는 낱자일 때 a = [c2, sub_c1, c1, sub_c2]; } return [a[0], a[1], a[2], a[3], transform]; } function NFC_galmadeuli_preprocess(f,e,key) { // 유니코드 완성형 한글 부호계를 쓸 때의 갈마들이 세벌식 자판 전처리 함수 (신세벌식 자판을 기준으로 함) var a, i, j, c, c1, c2, sub_c1, sub_c2; var sublayout = find_sublayout(); var transform = false; // 홀소리와 받침의 자리가 신세벌식 자판과 맞바뀐 배열 방식을 쓰는지 // c1가 아랫글 자리이면 c2는 윗글 자리, 아니면 그 반대임 a = find_galmadeuli_chars(key); c1 = a[0], c2 = a[1], sub_c1 = a[2], sub_c2 = a[3]; if(Sin3_extended_sign_layout_input(f,key,convert_into_ohi_hangeul_phoneme(c1))==-1) return -1; a = converting_for_special_galmadeuli_layouts(f, e, key, c1, c2, sub_c1, sub_c2, transform); c1 = a[0], sub_c1 = a[1], c2 = a[2], sub_c2 = a[3], transform = a[4]; c = c1; ohi_c1 = convert_into_ohi_hangeul_phoneme(c1); ohi_c2 = convert_into_ohi_hangeul_phoneme(c2); ohi_sub_c1 = convert_into_ohi_hangeul_phoneme(sub_c1); ohi_sub_c2 = convert_into_ohi_hangeul_phoneme(sub_c2); if(Ko_type.substr(0,5)=='Sin3-' && ohi_c2<31 && with_shift_key(key) && !ohiQ[0] && !ohiQ[3] && ohiQ[6] && !ohiQ[7] && ohiDoubleJamo(2,ohiQ[6],ohi_c2)) { // 홑받침만 들어갔는데 윗글쇠와 함께 왼쪽 글쇠가 눌렸을 때 겹받침 조합하기 ohiQ[7]=ohiDoubleJamo(2,ohiQ[6],ohi_c2); ohiInsert(f,0,ohiQ); return -1; } if(Ko_type.substr(0,5)=='Sin3-' && ohi_c1<31 && !with_shift_key(key) && !ohiQ[0] && !ohiQ[3] && ohiQ[6] && !ohiQ[7]) { // 홑받침만 들어가 있는데 윗글쇠를 누르지 않은 채로 받침 자리 글쇠가 눌렸을 때 조합 끊기 (홑받침 쓰는 초성체 조합) complete_hangeul_syllable(f); } else if(option.enable_double_final_ext && with_shift_key(key) && sub_c1 && (ohiQ[0] || NFD_stack.phoneme.length&&unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])>=0) && (ohiQ[3] && !ohiQ[6] || with_shift_key(key) && NFD_stack.phoneme.length&&unicode_ga.indexOf(NFD_stack.phoneme[0])>=0)) { // 윗글쇠를 함께 눌렀을 때 왼쪽 윗글 자리의 겹받침 넣기 (겹받침 확장 입력) c = ohi_sub_c1; } else if(Ko_type.substr(0,5)=='LGG3-' && !with_shift_key(key) && ohiQ[0] && !ohiQ[3] && !sub_c1 && unicode_cheos.indexOf(c1)>=0 && unicode_ga.indexOf(c2)>=0) { // 첫소리만 들어갔을 때 첫소리와 조합용이 아닌 홀소리가 든 글쇠가 눌리면 홀소리를 넣음 (이건구 한 손 세벌식 자판) c = c2; } else if(!with_shift_key(key) && ohiQ[0] && !ohiQ[3] && unicode_ga.indexOf(sub_c1)>=0) { // 첫소리만 들어갔을 때 보조 배열(sublayout)에서 겹홀소리 조합용 ㅗ, ㅜ, ㅡ, ㆍ 등을 넣음 c = ohi_sub_c1; ohiRQ[3]=1; } else if((ohiRQ[3] || backup_ohiRQ[3]) && ohi_c1<31 && NFD_stack.phoneme[0]==0x119E && !(NFD_stack.phoneme.length>1 && (unicode_ga.indexOf(NFD_stack.phoneme[1])>=0 || unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0))) { // 아래아가 들어 있을 때에 ㆎ(아래애), ᆢ(쌍아래아) 조합하기 if(key==100) c1=0x1175; // ㆎ(아래애) 조합하기 else if(key==122 && NFD_stack.phoneme[1]!=0x119E) c1=0x119E; // 쌍아래아(ᆢ) 조합하기 NFD_hangeul_input(f,key,c1); return -1; } else if(NFD_stack.phoneme.length && ohi_c1<31 && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0) { // 첫가끝 조합 상태에서 받침이 들어 있는데 또 받침이 들어왔을 때 if(unicode_ggeut.indexOf(NFD_stack.phoneme[1])<0) { if(option.enable_double_final_ext && sub_c1 && NFD_stack.phoneme[0]==convert_into_unicode_hangeul_phoneme(c1)) { // 같은 받침 글쇠가 거듭 눌렸을 때 겹받침 확장 배열 적용하기 c = c1; NFD_stack.phoneme.unshift(c); NFD_stack.phoneme_R.unshift(0); NFD_stack.combined_phoneme[0]=sub_c1; ohiSelection(f,0); ohiBackspace(f); ohiInsert(f,0,sub_c1); ohiSelection(f,NFD_stack.combined_phoneme.length); return -1; } } } else if(ohiRQ[3] && /*ohi_c1<31 &&*/ !ohiQ[4] && !ohiQ[6] && combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[3]+35),c2)) { // 먼저 들어간 조합용 홀소리와 겹홀소리를 이룰 수 있는 홀소리일 때 c = ohi_c2; } else if(!with_shift_key(key) && ohi_c1<31 && ohiQ[0]&&!ohiQ[3]&&!ohiQ[6] && (ohi_c2>65 && ohi_c2<87 || key==122)) { // 왼손 쪽 아랫글 자리에서 가운뎃소리 넣기 c = ohi_c2; if(key==122 && (c2==0x119E/* || c2>157*/)) c = 0x119E; // Z 자리 아래아 ohiRQ[3]=0; } else if((transform || Ko_type.substr(0,5)=='LGG3-') && ohi_c1<31 && ohiQ[6] && !ohiQ[7] && (i=combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[6]),c1))) { // 받침을 윗글 자리에 두는 신세벌식 자판 또는 이건구 한 손 세벌식 자판의 두번째 들어온 조합되는 받침 처리 ohiQ[7]=convert_into_ohi_hangeul_phoneme(i)-ohiQ[6]; ohiInsert(f,0,ohiQ); return -1; } else if(transform && with_shift_key(key) && unicode_ggeut.indexOf(c1)>=0 && unicode_ga.indexOf(c2)>=0) { // 받침을 윗글 자리에 두는 신세벌식 자판이고 홀소리와 받침이 있는 글쇠가 윗글쇠와 함께 눌렸을 때 // 마지막으로 들어간 홀소리와 조합되는 것이면 홀소리를 넣음 if(ohiQ[3] && combine_unicode_NFD_hangeul_phoneme(convert_into_unicode_hangeul_phoneme(ohiQ[3]+ohiQ[4]+35),c2)) { c = c2; } } return c; } function NFD_galmadeuli_preprocess(f,e,key) { // 첫가끝 조합형을 쓸 때의 갈마들이 세벌식 자판 전처리 함수 (신세벌식 자판을 기준으로 함) var a, i, j, c, c1, c2, sub_c1, sub_c2; var sublayout = find_sublayout(); var transform = false; // 홀소리와 받침의 자리가 신세벌식 자판과 맞바뀐 배열 방식을 쓰는지 // c1가 아랫글 자리이면 c2는 윗글 자리, 아니면 그 반대임 a = find_galmadeuli_chars(key); c1 = a[0], c2 = a[1], sub_c1 = a[2], sub_c2 = a[3]; if(Sin3_extended_sign_layout_input(f,key,c1)==-1) return -1; a = converting_for_special_galmadeuli_layouts(f, e, key, c1, c2, sub_c1, sub_c2, transform); c1 = a[0], sub_c1 = a[1], c2 = a[2], sub_c2 = a[3], transform = a[4]; c = c1; if(Ko_type.substr(0,5)=='Sin3-' && with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])<0 && unicode_ga.indexOf(NFD_stack.phoneme[0])<0 && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2)) { // 홑받침만 들어갔는데 윗글쇠와 함께 왼쪽 글쇠가 눌렸을 때 겹받침 조합하기 c = combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2); NFD_stack.phoneme.unshift(c); NFD_stack.phoneme_R.unshift(0); NFD_stack.combined_phoneme[0]=c; ohiSelection(f,0); ohiBackspace(f); ohiInsert(f,0,c); ohiSelection(f,NFD_stack.combined_phoneme.length); return -1; } if(Ko_type.substr(0,5)=='Sin3-' && option.use_hangeul_compatibility_jamo_when_entering_old_hangeul && is_old_hangeul_input() && !option.only_NFD_hangeul_encoding && unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])<0 && unicode_ga.indexOf(NFD_stack.phoneme[0])<0 && unicode_ggeut.indexOf(NFD_stack.phoneme[0])>=0 && !combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2)) { // 신세벌식 자판에서 따로 들어간 받침의 조합을 끊어 호환 자모로 바꿈 (초성체) complete_hangeul_syllable(f); } else if(option.enable_double_final_ext && with_shift_key(key) && sub_c1 && (NFD_stack.phoneme.length&&unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])>=0) && (ohiQ[3] && !ohiQ[6] || with_shift_key(key) && NFD_stack.phoneme.length&&unicode_ga.indexOf(NFD_stack.phoneme[0])>=0)) { // 윗글쇠를 함께 눌렀을 때 왼쪽 윗글 자리의 겹받침 넣기 (겹받침 확장 입력) c = sub_c1; } else if(Ko_type.substr(0,5)=='LGG3-' && !with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && !sub_c1 && unicode_cheos.indexOf(c1)>=0 && unicode_ga.indexOf(c2)>=0) { // 첫소리만 들어갔을 때 첫소리와 조합용이 아닌 홀소리가 든 글쇠가 눌리면 홀소리를 넣음 (이건구 한손 세벌식 자판) c = c2; } else if(option.enable_Sin3_diphthong_key && !with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ga.indexOf(sub_c1)>=0) { // 첫소리만 들어갔을 때 보조 배열(sublayout)에서 겹홀소리 조합용 ㅗ, ㅜ, ㅡ, ㆍ 등을 넣음 c = -sub_c1; } else if(with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ga.indexOf(c1)>=0 && unicode_ggeut.indexOf(c2)>=0) { // 첫소리만 들어갔고, 왼손 쪽의 끝소리가 있는 글쇠가 윗글쇠와 함께 눌렸을 때 끝소리를 넣음 (홀소리만 빠진 미완성 낱내자 조합하기) c = c2; } else if(option.enable_Sin3_adding_cheos_with_shift_key && (with_shift_key(key) || !c1) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && unicode_cheos.indexOf(c2)>=0 && (unicode_ga.indexOf(c1)>=0 || unicode_ga.indexOf(sub_c2)>=0)) { // 첫소리만 들어갔고, 오른손 쪽의 홀소리가 있는 첫소리 글쇠를 윗글쇠와 함께 눌렀을 때 첫소리를 넣음 c = c2; } else if(NFD_stack.combined_phoneme.length>1 && NFD_stack.phoneme_R[0] && unicode_ga.indexOf(c2)>=0 && sub_c1 != c2 && combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2)) { // 겹홀소리 조합용 가운뎃소리가 먼저 들어갔고 윗글 자리에 있는 홀소리가 있는 글쇠가 눌렸을 때 // 1타에 한하여 먼저 들어간 홀소리와 결합되는 홀소리이면 윗글 자리의 홀소리를 넣게 함 c = c2; } else if(!with_shift_key(key) && unicode_ggeut.indexOf(c1)>=0 && unicode_cheos.indexOf(NFD_stack.phoneme[0])>=0 && unicode_ga.indexOf(c2)>=0) { // 첫소리만 들어갔을 때 왼손 쪽 끝소리가 함께 있는 글쇠 자리에서 가운뎃소리 넣기 c = c2; } else if(transform && unicode_ggeut.indexOf(c1)>=0 && unicode_ga.indexOf(c2)>=0) { // 본래 신세벌식 자판과 홀소리와 받침 자리가 뒤바뀐 꼴이고 홀소리와 받침이 있는 글쇠가 눌렸을 때 if(with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])>=0 && unicode_ga.indexOf(NFD_stack.phoneme[0])>=0 && combine_unicode_NFD_hangeul_phoneme(NFD_stack.combined_phoneme[0],c2)) { // 윗글쇠와 함께 눌렸고 마지막으로 들어간 홀소리와 조합되는 것이면 홀소리를 넣음 c = c2; } else if(!with_shift_key(key) && NFD_stack.phoneme.length && unicode_cheos.indexOf(NFD_stack.phoneme[NFD_stack.phoneme.length-1])<0 && unicode_ga.indexOf(NFD_stack.phoneme[0])>=0) { // 윗글쇠를 누르지 않았고 첫소리 없이 홀소리만 들어갔다면 홀소리를 넣음 c = c2; } } return c; } function hangeul_typewriter(f,key) { // 타자기 자판 var layout = find_current_layout(); var ch; var c1=layout[key-33]; var c2=layout[ukey[dkey.indexOf(key)]-33]; // 윗글 자리 var ohi_c1=convert_into_ohi_hangeul_phoneme(c1); var ohi_c2=convert_into_ohi_hangeul_phoneme(c2); // 윗글 자리 ch=c1; if(Ko_type=='4t-1969') { if(ohiQ[3]==68-35 && !ohiQ[4] && (!ohiRQ[3]&&c1==86 || ohiRQ[3]==1&&c1==0x3163)) { // ㅑ+ㅣ→ㅒ ohiQ[4]=1; ohiInsert(f,0,ohiQ); return -1; } if(ohiQ[3]==72-35 && !ohiQ[4] && !ohiRQ[3]&&c1==86) { // ㅕ+ㅣ→ㅖ ohiQ[4]=1; ohiInsert(f,0,ohiQ); return -1; } } if(Ko_type=='4t-1985') { if(shift_lock) { ch=c2; //if(ohi_ga.indexOf(c1)<0 || ohi_ga.indexOf(c2)<0) shift_lock=0; if(unicode_ga.indexOf(c1)<0 || unicode_ga.indexOf(c2)<0) shift_lock=0; // 홀소리만 든 글쇠를 누르면 받침 글쇠가 풀리지 않음. 그밖의 글쇠를 누르면 받침 글쇠가 풀림 } } if(ohi_cheos.indexOf(convert_into_ohi_hangeul_phoneme(ch))>=0) { ohiRQ = [0,0,0,0,0,0,0,0,0]; } if(compatibility_ga.indexOf(ch)>=0) { // 받침 안 붙는 홀소리 if(is_old_hangeul_input() || option.only_NFD_hangeul_encoding) { ch=convert_into_unicode_hangeul_phoneme(ohi_ga[compatibility_ga.indexOf(ch)]); if(NFD_stack.phoneme[0]) NFD_stack.phoneme_R[0]=1; NFD_stack.phoneme.unshift(ch); NFD_stack.phoneme_R.unshift(1); NFD_stack.combined_phoneme.unshift(0x1160); return -1; } else { ch=ohi_ga[compatibility_ga.indexOf(ch)]; if(!ohiQ[3]) ohiRQ[3]=1; else ohiRQ[4]=1; } } return ch; //return convert_into_ohi_hangeul_phoneme(ch); //return (is_old_hangeul_input() || option.only_NFD_hangeul_encoding) ? ch : convert_into_ohi_hangeul_phoneme(ch); } function is_galmadeuli_input() { var type_name = current_layout_info.type_name; if(type_name.substr(0,2)=='2-') { for(var i=0; i<current_layout_info.layout.length; ++i) if(typeof current_layout_info.layout[i] == 'object') return true; return false; } if(type_name.substr(0,5)=='Sin3-') return true; if(type_name.substr(0,5)=='LGG3-') return true; if(type_name.substr(-3)=='_gm') return true; if(type_name.substr(0,4)=='3-20' && Number(type_name.substr(2,4))>2013) return true; if(type_name.substr(0,3)=='3-P') return true; if(type_name.substr(0,3)=='3-D') return true; var a=['3-18Na']; if(a.indexOf(type_name)>=0) return true; return false; } function can_be_galmadeuli_key(key) { // 첫가끝 갈마들이를 할 수 있게 아랫글/윗글 자리에 한글 낱자가 들어간 글쇠인지 shift_key = shift_table[key-33]; var mainlayout = find_mainlayout(); var sublayout = find_sublayout(); if(unicode_cheos.indexOf(mainlayout[key-33])>=0) { if(unicode_ga.indexOf(mainlayout[shift_key-33])>=0 || unicode_ga.indexOf(sublayout[key-33])>=0) return true; } if(unicode_ga.indexOf(mainlayout[key-33])>=0) { if(unicode_ggeut.indexOf(mainlayout[shift_key-33])>=0 || unicode_ggeut.indexOf(sublayout[key-33])>=0) return true; if(unicode_cheos.indexOf(mainlayout[shift_key-33])>=0 || unicode_cheos.indexOf(sublayout[key-33])>=0) return true; } if(unicode_ggeut.indexOf(mainlayout[key-33])>=0) { if(unicode_ga.indexOf(mainlayout[shift_key-33])>=0 || unicode_ga.indexOf(sublayout[key-33])>=0) return true; } return false; } function is_moachigi_input() { if(current_layout_info.type_name.substr(0,3)!='3m-') return false; if(option.force_normal_typing) return false; return true; } function is_left_key(key) { if(key<0x21 || key>0x7E) return false; if(key==0x21) return true; if(key==0x22) return false; if(key<=0x25) return true; if(key==0x26) return false; if(key<=0x2F) return false; if(key<=0x35) return true; if(key<=0x3F) return false; if(key<=0x47) return true; if(key<=0x50) return false; if(key<=0x54) return true; if(key==0x55) return false; if(key<=0x58) return true; if(key==0x59) return false; if(key==0x5A) return true; if(key<=0x5F) return false; if(key<=0x67) return true; if(key<=0x70) return false; if(key<=0x74) return true; if(key==0x75) return false; if(key<=0x78) return true; if(key==0x79) return false; if(key==0x7A) return true; if(key<=0x7D) return false; if(key==0x7E) return true; } function is_right_key(key) { if(key<0x21 || key>0x7E) return false; return !is_left_key(key); } function with_shift_key(key) { // 윗글쇠를 누르고 친 글쇠인지 if(key<0x21) return false; else if(key<=0x26) return true; else if(key==0x27) return false; else if(key<=0x2B) return true; else if(key<=0x39) return false; else if(key==0x3A) return true; else if(key==0x3B) return false; else if(key==0x3C) return true; else if(key==0x3D) return false; else if(key<=0x5A) return true; else if(key<=0x5D) return false; else if(key<=0x5F) return true; else if(key<=0x7A) return false; else if(key<=0x7E) return true; return false; } function is_old_hangeul_input() { if(current_layout_info.type_name && current_layout_info.type_name.substr(-2)=='-y') return true; if(option.enable_old_hangeul_input && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined') return true; return false; } function is_phonemic_writing_input() { if(option.phonemic_writing) return true; return false; } function push_to_key_table(u,d,t) { u.push( [t[93],t[0],t[31],t[2],t[3],t[4],t[61],t[5],t[9],t[7],t[8],t[62],t[10],''], ['',t[48],t[54],t[36],t[49],t[51],t[56],t[52],t[40],t[46],t[47],t[90],t[92],t[91]], ['',t[32],t[50],t[35],t[37],t[38],t[39],t[41],t[42],t[43],t[25],t[1],''], ['',t[57],t[55],t[34],t[53],t[33],t[45],t[44],t[27],t[29],t[30],'']); d.push( [t[63],t[16],t[17],t[18],t[19],t[20],t[21],t[22],t[23],t[24],t[15],t[12],t[28],''], ['',t[80],t[86],t[68],t[81],t[83],t[88],t[84],t[72],t[78],t[79],t[58],t[60],t[59]], ['',t[64],t[82],t[67],t[69],t[70],t[71],t[73],t[74],t[75],t[26],t[6],''], ['',t[89],t[87],t[66],t[85],t[65],t[77],t[76],t[11],t[13],t[14]],''); } function push_layout_to_key_table(u,d,b) { var a,c,bas=[]; for(var i=0;i<94;++i) { a = typeof b[i] == 'number' ? b[i] : b[i][0]; if(a<0) c=a; else c = String.fromCharCode(a); bas.push(c); } push_to_key_table(u,d,bas); } function push_extended_hangeul_layout_to_key_table(u,d,ext_layout) { var i,c,str,charCode; var ext=[]; for(i=0;i<94;++i) { if(typeof ext_layout[i][0] == 'object' && typeof ext_layout[i][0][0] == 'number') // 3-2012 옛한글 자판에 들어간 확장 배열 c = ext_layout[i][ohiHangeul3_HanExtKey%0x10-1][ohiHangeul3_HanExtKey>0x10 ? 1:0]; else if(typeof ext_layout[i][0] == 'number') c = ext_layout[i][ohiHangeul3_HanExtKey-1]; else c=ext_layout[i]; if(c<0) s=c; else s = String.fromCharCode(c); ext.push(s); } push_to_key_table(u,d,ext); } function push_extended_sign_layout_to_key_table(u,d,e) { var ext=[], c, i, j=(sign_ext_state-1)%10; if(j>=0) { if(!is_old_hangeul_input() && (Ko_type=='3-2011' || Ko_type=='3-2012')) { if(j<3) { for(i=0;i<94;++i) { c=e[i][j]>0 ? e[i][j] : 0; ext.push(String.fromCharCode(c)); } } } else if(Ko_type.substr(0,2)=='3-') { if(sign_ext_state<11) { for(i=0;i<94;++i) { c=e[i][0][j]>0 ? e[i][0][j] : 0; ext.push(String.fromCharCode(c)); } } if(sign_ext_state>10) { for(i=0;i<94;++i) { c=e[i][1][j]>0 ? e[i][1][j] : 0; ext.push(String.fromCharCode(c)); } } } else { // 신세벌식 for(i=0;i<94;++i) { c=e[i][j]>0 ? e[i][j] : 0; ext.push(String.fromCharCode(c)); } } } push_to_key_table(u,d,ext); } function insert_sublayout_table(ue, de, uh, dh, sublayout) { var u=[], d=[], sub=[], c, i, j, ds, us; for(i=0;i<94;++i) { c = sublayout[i]; if(special_chars.indexOf(c)>=0) c = general_chars[special_chars.indexOf(c)]; s=String.fromCharCode(convert_into_unicode_hangeul_phoneme(c)); sub.push(s); } push_to_key_table(u,d,sub); for(i=0;i<de.length;++i) { for(j=0;j<de[i].length;++j) { if( (!u[i][j] || !u[i][j].charCodeAt(0)) && (!d[i][j] || !d[i][j].charCodeAt(0)) ) continue; ds = de[i][j]; us = ue[i][j]; if(ue[i][j].charCodeAt(0)) { if(d[i][j].charCodeAt(0)) { if(u[i][j].charCodeAt(0) && d[i][j]!=u[i][j]) us=u[i][j]; ds=d[i][j]; } else us=u[i][j]; } else if(d[i][j].charCodeAt(0)) ds=d[i][j]; if(!(us==ue[i][j] || us==uh[i][j] || us==dh[i][j])) ue[i][j] = us; if(!(ds==ue[i][j] || ds==uh[i][j] || ds==dh[i][j])) de[i][j] = ds; } } } function show_ohiStatusBar(op) { // 보람줄(상태 표시줄) 보이기/감추기 if(typeof op != 'undefined' && (op=='off' || op=='0' || !op)) ohiStatus.style.display='none'; else ohiStatus.style.display='block'; } function ohiChange_enable_double_final_ext(op) { // 겹받침 확장 기능 켜기/끄기 if(op===undefined || op==1) option.enable_double_final_ext=1; else option.enable_double_final_ext=0; show_keyboard_layout(); } function show_NCR_text(op) { // 문자를 유니코드 부호값과 맞대어 나타내기 (Numeric Character Reference) if(typeof op != 'undefined') { if(op) converting_option.NCR_text=1; else converting_option.NCR_text=0; } // 수정됨: inputText -> wpTextbox1 var f = document.getElementById('wpTextbox1'); var t = document.getElementById('NCR_text'); if(!f || !t) return; var opt, opts = document.getElementById('NCR_options'); if(opts) { if(ohi_menu_num && ohi_menu_num<3) { if(converting_option.NCR_text) opts.style.display = 'block'; else opts.style.display = 'inline'; } else opts.style.display = 'none'; opt = document.getElementById('converting_option_NCR_text'); if(!opt) opt = appendChild(opts,'div','option','converting_option_NCR_text','<input name="NCR_text" class="checkbox" onclick="show_NCR_text(this.checked);inputText_focus()" type="checkbox"' + (converting_option.show_NCR_text ? ' checked="checked"' : '') + '><label title="&apos;한글&apos;을 &amp;#xD55C;&amp;#xAE00; 꼴로 나타내기">HTML 문자 참조</label>'); opt = document.getElementById('converting_option_convert_only_NFD_hangeul_encoding_in_NCR_text'); if(!opt) opt = appendChild(opts,'div','option','converting_option_convert_only_NFD_hangeul_encoding_in_NCR_text','<input name="convert_only_NFD_hangeul_encoding_in_NCR_text" class="checkbox" onclick="converting_option.convert_only_NFD_hangeul_encoding_in_NCR_text=this.checked;show_NCR_text();inputText_focus()" type="checkbox"' + (converting_option.convert_only_NFD_hangeul_encoding_in_NCR_text ? ' checked="checked"' : '') + '><label title="완성형으로 나타낼 수 있는 한글은 바꾸지 않기">첫가끝 조합형만 바꾸기</label>'); if(t && converting_option.NCR_text) { t.style.display='inline-block'; opt.style.display='inline-block'; } else { t.style.display='none'; opt.style.display='none'; } opt = document.getElementById('NCR_text_copy_button'); if(!opt) { opt = appendChild(opts,'div','option','NCR_text_copy_button','<button onclick="copyToClipboard(document.getElementById(\'NCR_text\'))">베끼기</button>'); opt.style.cssFloat = 'right'; } if(converting_option.NCR_text) opt.style.display='inline-block'; else opt.style.display='none'; } var ref_char, char_code, ref_text=''; for(i=0;i<f.value.length;++i) { char_code = f.value.charCodeAt(i); ref_char = '&amp;#x'+ char_code.toString(16).toUpperCase() + ';'; if(converting_option.convert_only_NFD_hangeul_encoding_in_NCR_text) { // 첫가끝 조합형 한글만 바꿀 때 if(unicode_NFD_hangeul_code.indexOf(char_code)<0 && unicode_NFD_hangeul_sidedot.indexOf(char_code)<0) ref_char = f.value.charAt(i); } ref_text += ref_char; } if(ref_text=='') ref_text='&nbsp;'; t.innerHTML = ref_text; } function show_direct_typing_text(op) { // 쿼티 글쇠 배열 기준으로 문자열을 글쇠값들로 바꾸기 if(typeof op != 'undefined') { if(op) converting_option.direct_typing_text=1; else converting_option.direct_typing_text=0; } // 수정됨: inputText -> wpTextbox1 var f = document.getElementById('wpTextbox1'); var t = document.getElementById('direct_typing_text'); if(!f || !t) return; var opt, opts = document.getElementById('direct_typing_text_options'); var mainlayout = find_mainlayout(); if(opts) { if(ohi_menu_num && ohi_menu_num<3 && !is_moachigi_input()) { if(converting_option.direct_typing_text) opts.style.display = 'block'; else opts.style.display = 'inline'; } else opts.style.display = 'none'; opt = document.getElementById('converting_option_direct_typing_text'); if(!opt) { opt = appendChild(opts,'div','option','converting_option_direct_typing_text','<input name="direct_typing_text" class="checkbox" onclick="show_direct_typing_text(this.checked);inputText_focus()" type="checkbox"' + (converting_option.direct_typing_text ? ' checked="checked"' : '') + '><label title="글에 들어간 문자열을 쿼티 배열 기준 글쇠값들로 바꾸기 ">문자열→글쇠값</label>'); opt.style.display='inline-block'; } opt = document.getElementById('converting_option_extended_hangeul_layout_reflection'); if(!opt) opt = appendChild(opts,'div','option','converting_option_extended_hangeul_layout_reflection','<input name="extended_hangeul_layout" class="checkbox" onclick="converting_option.extended_hangeul_layout_reflection=this.checked;show_keyboard_layout();inputText_focus()" type="checkbox"' + (converting_option.combination_table_reflection_priority ? ' checked="checked"' : '') + '><label title="한글 확장 배열 반영하기">한글 확장 배열</label>'); if(converting_option.direct_typing_text && mainlayout.indexOf(-1)>=0) opt.style.display='inline-block'; else opt.style.display='none'; opt = document.getElementById('converting_option_combination_table_reflection'); if(!opt) opt = appendChild(opts,'div','option','converting_option_combination_table_reflection','<input name="combination_table_reflection" class="checkbox" onclick="converting_option.combination_table_reflection=this.checked;show_keyboard_layout();inputText_focus()" type="checkbox"' + (converting_option.combination_table_reflection ? ' checked="checked"' : '') + '><label title="낱자 조합 규칙 반영하기">낱자 조합</label>'); if(converting_option.direct_typing_text) opt.style.display='inline-block'; else opt.style.display='none'; opt = document.getElementById('converting_option_combination_table_reflection_priority'); if(!opt) opt = appendChild(opts,'div','option','converting_option_combination_table_reflection_priority','<input name="combination_table_reflection_priority" class="checkbox" onclick="converting_option.combination_table_reflection_priority=this.checked;show_keyboard_layout();inputText_focus()" type="checkbox"' + (converting_option.combination_table_reflection_priority ? ' checked="checked"' : '') + '><label title="자판 배열에 따로 있는 겹낱자에까지 낱자 조합 규칙을 우선 반영하기">낱자 조합 우선</label>'); if(converting_option.direct_typing_text && converting_option.combination_table_reflection) opt.style.display='inline-block'; else opt.style.display='none'; opt = document.getElementById('converting_option_combination_table_reflection_ggeut_ss_exception'); if(!opt) opt = appendChild(opts,'div','option','converting_option_combination_table_reflection_ggeut_ss_exception','<input name="combination_table_reflection_priority" class="checkbox" onclick="converting_option.combination_table_reflection_ggeut_ss_exception=this.checked;show_keyboard_layout();inputText_focus()" type="checkbox"' + (converting_option.combination_table_reflection_ggeut_ss_exception ? ' checked="checked"' : '') + '><label title="받침 ㅆ이 아랫글 자리에 따로 있으면 조합하여 넣은 것으로 셈하지 않음">받침 ㅆ 예외</label>'); if(converting_option.direct_typing_text && converting_option.combination_table_reflection && converting_option.combination_table_reflection_priority && mainlayout.indexOf(0x11BB)>=0 && !with_shift_key(mainlayout.indexOf(0x11BB)+33)) opt.style.display='inline-block'; else opt.style.display='none'; opt = document.getElementById('direct_typing_text_copy_button'); if(!opt) { opt = appendChild(opts,'div','option','direct_typing_text_copy_button','<button onclick="copyToClipboard(document.getElementById(\'direct_typing_text\'))">베끼기</button>'); opt.style.cssFloat = 'right'; } if(converting_option.direct_typing_text) opt.style.display='inline-block'; else opt.style.display='none'; } if(t && converting_option.direct_typing_text && !is_moachigi_input()) { t.style.display='inline-block'; } else { t.style.display='none'; return; } var conv_text=''; var key_table = []; making_key_table(key_table); for(var i=0;i<f.value.length;++i) { conv_text += convert_into_direct_typing_chars(key_table, f.value, i); } if(conv_text=='') conv_text='&nbsp;'; t.innerHTML = conv_text; } function show_reverse_direct_typing_text(op) { // 쿼티 글쇠 배열 기준으로 글쇠값들을 문자열로 바꾸기 if(typeof op != 'undefined') { if(op) converting_option.reverse_direct_typing_text=1; else converting_option.reverse_direct_typing_text=0; } // 수정됨: inputText -> wpTextbox1 var f = document.getElementById('wpTextbox1'); var t = document.getElementById('reverse_direct_typing_text'); if(!f || !t) return; var opt, opts = document.getElementById('reverse_direct_typing_text_options'); var mainlayout = find_mainlayout(); if(opts) { if(ohi_menu_num && ohi_menu_num<3 && !is_moachigi_input()) { if(converting_option.reverse_direct_typing_text) opts.style.display = 'block'; else opts.style.display = 'inline'; } else opts.style.display = 'none'; opt = document.getElementById('converting_option_reverse_direct_typing_text'); if(!opt) { opt = appendChild(opts,'div','option','converting_option_reverse_direct_typing_text','<input name="reverse_direct_typing_text" class="checkbox" onclick="show_reverse_direct_typing_text(this.checked);inputText_focus()" type="checkbox"' + (converting_option.direct_typing_text ? ' checked="checked"' : '') + '><label title="쿼티 배열 기준 글쇠값들을 문자열로 바꾸기 ">글쇠값→문자열</label>'); opt.style.display='inline-block'; } opt = document.getElementById('reverse_direct_typing_text_copy_button'); if(!opt) { opt = appendChild(opts,'div','option','reverse_direct_typing_text_copy_button','<button onclick="copyToClipboard(document.getElementById(\'reverse_direct_typing_text\'))">베끼기</button>'); opt.style.cssFloat = 'right'; } if(converting_option.reverse_direct_typing_text) opt.style.display='inline-block'; else opt.style.display='none'; opt = document.getElementById('reverse_direct_typing_text_conversion_button'); if(!opt) { opt = appendChild(opts,'div','option','reverse_direct_typing_text_conversion_button','<button onclick="show_reverse_direct_typing_text();">바꾸기</button>'); opt.style.cssFloat = 'right'; } if(converting_option.reverse_direct_typing_text) opt.style.display='inline-block'; else opt.style.display='none'; } if(t && converting_option.reverse_direct_typing_text && !is_moachigi_input()) { t.style.display='inline-block'; } else { t.style.display='none'; return; } var temp_t = document.createElement("textarea"); document.body.appendChild(temp_t); for(var i=0;i<f.value.length;++i) { convert_into_reverse_direct_typing_chars(temp_t, f.value, i); } complete_hangeul_syllable(temp_t); t.innerHTML = temp_t.value; if(t.innerHTML=='') t.innerHTML='&nbsp;'; document.body.removeChild(temp_t); } function making_key_table(table) { // 글쇠 기준 문자 변환에 쓰이는 부호값-글쇠 대응표 만들기 var i, j, k, key, keys = []; var layout_info = find_current_layout_info(); var mainlayout = find_mainlayout(); var sublayout = find_sublayout(); var combination_table = find_combination_table(); var single_phonemes = []; for(i=0x21; i<0x7E; ++i) { // 아스키 영역 일반 문자 key = mainlayout.indexOf(i)+33; if(i>0x40 && i<0x5B || i>0x60 && i<0x7B) table.push({code: i, keys: [i]}); // 영문자 else table.push({code: i, keys: [key]}); // 숫자, 기호 } for(i=0;i<compatibility_dah.length;++i) { // 호환 자모 닿소리 keys = []; for(j=0;j<compatibility_dah_to_NFD_hotbadchim[i].length;++j) { if(mainlayout.indexOf(compatibility_dah_to_NFD_hotbadchim[i][0])>=0) key = mainlayout.indexOf(compatibility_dah_to_NFD_hotbadchim[i][j])+33; else key = mainlayout.indexOf(unicode_ggeut_to_cheos[unicode_ggeut.indexOf(compatibility_dah_to_NFD_hotbadchim[i][j])])+33; if(key<33) break; if(layout_info.type_name.substr(0,2)=='2-' && with_shift_key(key) && mainlayout[key-33]==mainlayout[shift_table[key-33]-33]) key = shift_table[key-33]; else if(j && layout_info.type_name.substr(0,4)=='Sin3' && !with_shift_key(key)) key = shift_table[key-33]; keys.push(key); } if(j==compatibility_dah_to_NFD_hotbadchim[i].length && keys.length) table.push({code: compatibility_modern_dah[i], keys: keys}); // 끝소리로 넣음 } for(i=0;i<compatibility_hol.length;++i) { // 호환 자모 홀소리 keys = []; for(j=0;j<compatibility_hol_to_NFD_hothol[i].length;++j) { key = mainlayout.indexOf(compatibility_hol_to_NFD_hothol[i][j])+33; if(key<33) break; if(layout_info.type_name.substr(0,2)=='2-' && with_shift_key(key) && mainlayout[key-33]==mainlayout[shift_table[key-33]-33]) key = shift_table[key-33]; keys.push(key); } if(j==compatibility_hol_to_NFD_hothol[i].length && keys.length) table.push({code: compatibility_modern_hol[i], keys: keys}); // 끝소리로 넣음 } codes = unicode_NFD_hangeul_phoneme.concat(mainlayout, sublayout); if(mainlayout.indexOf(-1)>=0 && typeof layout_info.extended_hangeul_layout != 'undefined') // 한글 확장 배열 for(i=0;i<layout_info.extended_hangeul_layout.length;++i) for(j=0;j<layout_info.extended_hangeul_layout[i].length;++j) codes.push(layout_info.extended_hangeul_layout[i][j]); for(i=0; i<codes.length; ++i) { // 유니코드 한글 낱자들과 기본/보조 배열에 들어간 문자들을 살핌 keys = []; if(unicode_non_combined_phoneme.indexOf(codes[i])<0) { // 요즘한글 홑낱자가 아닌 한글 낱자와 기호 if(is_galmadeuli_input() && unicode_ggeut.indexOf(codes[i])>=0 && option.enable_double_final_ext && sublayout.indexOf(codes[i])>=0 && with_shift_key(sublayout.indexOf(codes[i])+33)) { // 겹받침 확장 keys.push(sublayout.indexOf(codes[i])+33); } else if(mainlayout.indexOf(codes[i])>=0) { key = mainlayout.indexOf(codes[i])+33; if(with_shift_key(key) && is_galmadeuli_input() && unicode_NFD_hangeul_phoneme.indexOf(codes[i])>=0 && can_be_galmadeuli_key(key)) keys.push(shift_table[key-33]); else keys.push(key); } else if(converting_option.extended_hangeul_layout_reflection && mainlayout.indexOf(-1)>=0 && typeof layout_info.extended_hangeul_layout != 'undefined') { // 한글 확장 배열 key = layout_info.extended_hangeul_layout.findIndex(function(n){return n.indexOf(codes[i])>=0;})+33; if(key>32) { for(j=0;j<layout_info.extended_hangeul_layout[key-33].indexOf(codes[i])+1;++j) keys.push(mainlayout.indexOf(-1)+33); keys.push(key); } } if(keys.length) { table.push({code: codes[i], keys: keys.slice()}); continue; } } single_phonemes = convert_into_single_phonemes(codes[i]); keys = find_direct_typing_keys(single_phonemes); if(keys.length) table.push({code: codes[i], keys: keys.slice()}); } if(layout_info.type_name.substr(0,1)=='2') { // 2벌식 자판 for(i=0;i<table.length;++i) { if(unicode_cheos.indexOf(table[i].code)>=0) { // 끝소리→첫소리 var ggeut = unicode_cheos_to_ggeut[unicode_cheos.indexOf(table[i].code)]; table.push({code: ggeut, keys: table[i].keys.slice()}); } } for(i=0;i<unicode_ggeut.length;++i) { // 첫소리에 없는 겹받칩 keys=[]; if(unicode_non_combined_ggeut.indexOf(unicode_ggeut[i])>=0) continue; single_phonemes = convert_into_single_phonemes(unicode_ggeut[i]); for(j=0;j<single_phonemes.length;++j) { // 배열에 따로 없는 낱자를 홑낱자로 나누어서 글쇠 치는 차례를 찾음 k = table.filter(function(e) {return e.code == single_phonemes[j]}); if(k.length) keys.push(k[0].keys[0]); } if(keys.length) table.push({code: unicode_ggeut[i], keys: keys.slice()}); } } for(i=0; i<codes.length; ++i) { // 낱자 조합 규칙 if(converting_option.combination_table_reflection && combination_table.length && unicode_NFD_hangeul_phoneme.indexOf(codes[i])>=0) { var divided_phonemes = [codes[i]]; do { for(j=0,k=0;j<combination_table.length;++j) { if(combination_table[j][1]==divided_phonemes[0]) { divided_phonemes.splice(0,1,parseInt(combination_table[j][0]/0x10000),combination_table[j][0]%0x10000); ++k; } } } while(k); if(divided_phonemes.length && find_direct_typing_keys(divided_phonemes).length) { if(converting_option.combination_table_reflection_priority) { // 낱자 조합 규칙을 우선 적용하기 if(codes[i]!=0x11BB || !converting_option.combination_table_reflection_ggeut_ss_exception) // 받침 ㅆ 예외에 걸리지 않으면 table.unshift({code: codes[i], keys: find_direct_typing_keys(divided_phonemes)}); } else table.push({code: codes[i], keys: find_direct_typing_keys(divided_phonemes)}); } } } } function find_direct_typing_keys(single_phonemes) { var i, j, k, key; var keys = []; var layout_info = find_current_layout_info(); var mainlayout = find_mainlayout(); var sublayout = find_sublayout(); for(i=0;i<single_phonemes.length;++i) { // 배열에 따로 없는 낱자를 홑낱자로 나누어서 글쇠 치는 차례를 찾음 if(mainlayout.indexOf(single_phonemes[i])>=0) { key = -1; for(j=0;j<mainlayout.length && j<sublayout.length;++j) if(mainlayout[j]==single_phonemes[i] && sublayout[j]!=single_phonemes[i]) key = j+33; if(key<0) key = mainlayout.indexOf(single_phonemes[i])+33; if(with_shift_key(key) && mainlayout[key-33]==mainlayout[shift_table[key-33]-33]) { // 윗글쇠를 눌렀고 아랫글 자리에도 같은 문자가 있을 때 아랫글 글쇠로 넣음 key = shift_table[key-33]; } else if(with_shift_key(key) && is_galmadeuli_input()) { // 윗글쇠를 눌렀고 갈마들이를 쓰고 있을 때 아랫글 글쇠로 넣음 if(unicode_ga.indexOf(single_phonemes[i])>=0 && (i>(sublayout.indexOf(single_phonemes[0])>=0 ? 1 : 0) || is_old_hangeul_input() && !option.enable_Sin3_diphthong_key && layout_info.type_name.substr(0,4)=='Sin3')) {} else key = shift_table[key-33]; } else if(!with_shift_key(key) && !is_galmadeuli_input() && !i && unicode_ga.indexOf(single_phonemes[i])>=0 && mainlayout.lastIndexOf(single_phonemes[i])>=0 && mainlayout.indexOf(single_phonemes[i]) != mainlayout.lastIndexOf(single_phonemes[i])) { // 갈마들이를 쓰지 않는 세벌식 자판의 같은 것이 2개 있는 홑홀소리(ㅗ, ㅜ 등) 가려 넣기 j = ((unicode_cheos.indexOf(mainlayout[0x6A-33])>=0) + (single_phonemes.length>1) + is_left_key(mainlayout.indexOf(single_phonemes[i])+33)); if(j%2) key = mainlayout.lastIndexOf(single_phonemes[i])+33; else key = mainlayout.indexOf(single_phonemes[i])+33; } if(i+1==single_phonemes.length || !combine_unicode_NFD_hangeul_phoneme(single_phonemes[i], single_phonemes[i+1])) { // 마지막 낱자이거나 다음 낱자와 조합되지 않을 때 keys.push(key); return keys; } else if(sublayout.indexOf(single_phonemes[i])<0) keys.push(key); } if(sublayout.indexOf(single_phonemes[i])>=0) { if(unicode_ga.indexOf(single_phonemes[i])>=0 && is_old_hangeul_input() && !option.enable_Sin3_diphthong_key && layout_info.type_name.substr(0,4)=='Sin3') {} else key = sublayout.indexOf(single_phonemes[i])+33; keys.push(key); } if(converting_option.extended_hangeul_layout_reflection && mainlayout.indexOf(-1)>=0 && typeof layout_info.extended_hangeul_layout != 'undefined' && mainlayout.indexOf(single_phonemes[i])<0 && sublayout.indexOf(single_phonemes[i])<0) { // 한글 확장 배열 key = layout_info.extended_hangeul_layout.findIndex(function(n){return n.indexOf(single_phonemes[i])>=0})+33; if(key>32) keys.push(mainlayout.indexOf(-1)+33, key); } } return keys; } function convert_into_direct_typing_chars(key_table, text, nth) { // 글에 들어간 문자를 쿼티 기준으로 글쇠 자리에 있는 문자로 바꿈 (글쇠 기준 문자 변환) var layout_info = find_current_layout_info(); var i, j, r, key; var char_codes = [], codes = []; var str = ''; if(nth>0) { var prev_char_code = text.charCodeAt(nth-1); char_codes.push(prev_char_code); } else char_codes.push(-1); var char_code = text.charCodeAt(nth); char_codes.push(char_code); if(text.length>=nth) { var next_char_code = text.charCodeAt(nth+1); char_codes.push(next_char_code); } else char_codes.push(-1); if(typeof char_code == 'undefined') return ''; if(char_code==32) return ' '; // 사이띄개(Space Bar) if(char_code==10) return '\n'; // Line Feed if(char_code==9) return '\t'; // Tab if(char_code==0x115F) return ''; // 첫소리 채움 문자 if(char_code==0x1160) { // 가운뎃소리 채움 문자 if(layout_info.type_name.substr(0,1)=='3' || layout_info.type_name.substr(0,4)=='Sin3') return ''; if(layout_info.type_name.substr(0,1)=='2') { // 2벌식 자판 : 앞 문자가 첫소리이고 뒤 문자가 끝소리가 아니면 가운뎃소리 채움 문자를 다루지 않음 if(unicode_cheos.indexOf(text.charCodeAt(nth-1))>=0 && text.length>=nth && unicode_ggeut.indexOf(next_char_code<0)) return ''; } } for(i=0;i<char_codes.length;++i) { codes[i] = []; if(char_codes[i]<0) continue; if(char_codes[i]>=0xAC00 && char_codes[i]<=0xD7AF) { // 완성형 한글 낱내자 var p = convert_NFC_into_NFD(char_codes[i]); for(j=0; j<p.length; ++j) { if(!p[j]) continue; codes[i].push(p[j]); } } else codes[i].push(char_codes[i]); } for(i=0; i<codes[1].length; ++i) { r = key_table.filter(function(e) {return e.code==codes[1][i]})[0]; if(typeof r != 'undefined') { for(j=0;j<r.keys.length;++j) { if(r.keys[j]>-1) { key = r.keys[j]; if(!j && nth>1 && layout_info.type_name.substr(0,4)=='Sin3') if(unicode_cheos.indexOf(text.charCodeAt(nth-2))>=0 && prev_char_code==0x1160 && unicode_ggeut.indexOf(char_code)>=0) key = shift_table[r.keys[0]-33]; // 신세벌식 자판 : 가운뎃소리가 빠진 미완성 낱내자의 끝소리는 윗글쇠를 눌러 넣음 str += String.fromCharCode(key); } } } } if(layout_info.type_name.substr(0,1)=='2' && str.length && (unicode_modern_hangeul_phoneme.indexOf(codes[2][0])<0 || converting_option.combination_table_reflection_priority)) { // 2벌식 자판 if(unicode_ggeut.indexOf(codes[1][codes[1].length-1])>=0 && codes[2].length>=1 && unicode_cheos.indexOf(codes[2][0])>=0) { // 현재 낱자가 끝소리이고 다음 낱자가 첫소리일 때 // 끝소리/첫소리 조합 경계를 따로 끊어 주어야 하는 때 (이ᄣᅢ, 입ᄯᅢ) if(convert_into_single_phonemes(codes[2][0]).length>1) { i = combine_unicode_NFD_hangeul_phoneme(codes[1][codes[1].length-1], unicode_cheos_to_ggeut[unicode_cheos.indexOf(convert_into_single_phonemes(codes[2][0])[0])]); j = combine_unicode_NFD_hangeul_phoneme(i, unicode_cheos_to_ggeut[unicode_cheos.indexOf(convert_into_single_phonemes(codes[2][0])[1])]); if(i || j) str += '🄴'; } } if(unicode_ga.indexOf(codes[1][codes[1].length-1])>=0 && unicode_cheos.indexOf(codes[2][0])>=0) { // 현재 낱자가 홀소리이고 다음 낱자가 첫소리일 때 // 홀소리를 넣은 다음에 낱내자 조합 경계를 따로 끊어 주어야 하는 때 (차ᄡᅡᆯ) if(convert_into_single_phonemes(codes[2][0]).length>1) str += '🄴'; } } if(!str.length) str = '■'; // 대응하는 글쇠 자리나 조합 규칙을 찾지 못한 문자를 ■로 바꿈 return str; } function convert_into_reverse_direct_typing_chars(f, text, nth) { // 글에 들어간 문자를 쿼티 기준으로 글쇠 자리에 있는 문자로 바꿈 (글쇠 기준 문자 변환) var key = text.charCodeAt(nth); if(key<0x21 || key>0x7e) { complete_hangeul_syllable(f); if(key==32) f.value += ' '; // 사이띄개(Space Bar) else if(key==10) f.value += '\n'; // Line Feed else if(key==9) f.value += '\t'; // Tab } else if(ohi_KE.substr(0,2)=='Ko') { if(current_layout_info.type_name.substr(0,2)=='2-') ohiHangeul2(f,0,key); else if(!ohiHangeul3_abbreviation(f,key)) ohiHangeul3(f,0,key); } } function add_option(opts, opt_name, footer) { var opt = document.getElementById('option_' + opt_name); eval('var opt_var = option.' + opt_name); var opt_html = '<input name="' + opt_name + '" class="checkbox" type="checkbox"' + (opt_var ? ' checked="checked"' : '') + ' onclick="option.' + opt_name + '=this.checked; ' + footer; if(!opt) opt = appendChild(opts, 'div', 'option', 'option_' + opt_name, opt_html); opt.style.display = 'block'; return opt; } function show_options() { var i; var KE=ohi_KE, opt, opt_name, ft; var opts = document.getElementById('top_options'); var type_name = typeof current_layout_info.type_name != 'undefined' ? current_layout_info.type_name : ''; if(typeof ohi_menu_num == 'undefined') ohi_menu_num=0; show_NCR_text(); show_direct_typing_text(); show_reverse_direct_typing_text(); if(opts) { if(ohi_menu_num && ohi_menu_num<3) opts.style.display = 'block'; else opts.style.display = 'none'; opt_name = 'only_NFD_hangeul_encoding'; ft = 'show_options();inputText_focus()"><label title="한글을 모두 첫가끝 조합형으로 넣기">첫가끝 조합</label>'; add_option(opts, opt_name, ft); opt_name = 'phonemic_writing'; ft = 'complete_hangeul_syllable();ohiChange_enable_phonemic_writing();inputText_focus()"><label title="한글을 낱자 단위로 풀어서 넣기">풀어쓰기</label>'; add_option(opts, opt_name, ft); opt_name = 'phonemic_writing_in_halfwidth_letter'; ft = 'inputText_focus()"><label title="한글을 반각 낱자로 넣기">반각</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && !is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'phonemic_writing_directly'; ft = 'show_options();inputText_focus()"><label title="낱자를 조합하지 않고 바로 넣기">바로 풀기</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && !is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'phonemic_writing_adding_space_every_syllable_end'; ft = 'inputText_focus()"><label title="낱내자(음절자) 사이에 빈칸 넣기">낱내 띄기</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && (!option.phonemic_writing_directly || option.only_NFD_hangeul_encoding)) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'phonemic_writing_in_single_phoneme'; ft = 'show_options();inputText_focus()"><label title="모든 겹낱자를 풀어서 홑낱자로 나타내기">겹낱자 풀기</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && /*(!is_old_hangeul_input() || option.only_NFD_hangeul_encoding) &&*/ (!option.phonemic_writing_directly || option.only_NFD_hangeul_encoding)) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'phonemic_writing_NFD_ggeut_to_cheos'; ft = 'show_options();inputText_focus()"><label title="끝소리를 첫소리로 바꾸어 넣기">끝→첫</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && option.only_NFD_hangeul_encoding && option.phonemic_writing_in_single_phoneme) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'phonemic_writing_initial_ieung_ellipsis'; ft = 'inputText_focus()"><label title="첫소리 ㅇ(이응) 빼기">첫ㅇ 빼기</label>'; opt = add_option(opts, opt_name, ft); if(is_phonemic_writing_input() && (!option.phonemic_writing_directly || option.only_NFD_hangeul_encoding)) opt.style.display = 'block'; else opt.style.display = 'none'; } opts = document.getElementById('middle_options'); if(opts) { opts.style.display = 'block'; opt = document.getElementById('text_copy_button'); if(!opt) { opt = appendChild(opts,'div','option','text_copy_button','<button onclick="copyToClipboard(document.getElementById(\'inputText\'))">글 베끼기</button>'); //opt.style.marginLeft = '7px'; } opt_name = 'turn_off_OHI'; ft = 'ohiStart();inputText_focus()"><label title="온라인 한글 입력기의 입력 기능 끄기">OHI 끄기</label>'; opt = add_option(opts, opt_name, ft); if(ohi_menu_num && ohi_menu_num<3) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'sunalae'; ft = 'show_options();inputText_focus()"><label title="두벌식 자판으로 홀소리 글쇠를 거듭 눌러 겹닿소리(된소리) 넣기">순아래 조합 <a href="https://sites.google.com/site/tinyduckn/dubeolsig-sun-alae" target="_blank">ⓘ</a></label>'; opt = add_option(opts, opt_name, ft); if(!is_old_hangeul_input() && !is_phonemic_writing_input() && !is_galmadeuli_input() && type_name.substr(0,2)=='2-' && type_name.substr(0,5)!='2-sun') opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'enable_sign_ext'; ft = 'show_keyboard_layout();inputText_focus()"><label title="기호 확장 배열">기호 확장</label>'; opt = add_option(opts, opt_name, ft); if(KE=='Ko' && (typeof current_layout_info.extended_sign_layout != 'undefined' && current_layout_info.extended_sign_layout) && Ko_type!='Sin3-2015') opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'enable_old_hangeul_input'; ft = 'ohiChange_enable_old_hangeul_input();ohiStart();inputText_focus()"><label title="옛한글 넣기">옛한글</label>'; opt = add_option(opts, opt_name, ft); if(typeof current_layout_info.old_hangeul_layout_type_name != 'undefined' && !(Ko_type.substr(0,2)=='2-' && option.sunalae)) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'enable_Sin3_diphthong_key'; ft = 'show_keyboard_layout();inputText_focus()"><label title="오른손 쪽에서 ㅗ,ㅜ,ㅡ,ㆍ 넣기">오른쪽 홀소리</label>'; opt = add_option(opts, opt_name, ft); if(type_name.substr(0,5)=='Sin3-' && is_old_hangeul_input()) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'enable_Sin3_adding_cheos_with_shift_key'; ft = 'inputText_focus()"><label title="오른쪽 홀소리 자리에서 윗글쇠 눌러 첫소리 넣기">윗글 첫소리</label>'; opt = add_option(opts, opt_name, ft); if(type_name.substr(0,5)=='Sin3-' && is_old_hangeul_input()) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'use_hangeul_compatibility_jamo_when_entering_old_hangeul'; ft = 'show_keyboard_layout();inputText_focus()"><label title="낱자를 호환 자모로 넣기">호환 자모</label>'; opt = add_option(opts, opt_name, ft); if(is_old_hangeul_input() && !option.only_NFD_hangeul_encoding) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'abbreviation'; ft = 'inputText_focus()"><label title="이어치기 방식으로 쓰는 줄여넣기">줄임말 조합</label>'; opt = add_option(opts, opt_name, ft); if(Ko_type.substr(0,3)!='3m-' && typeof current_layout_info.ieochigi_hangeul_abbreviation_table != 'undefined' && !is_old_hangeul_input()) opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'force_normal_typing'; ft = 'show_keyboard_layout();inputText_focus()"><label title="한 글쇠씩 이어서 넣는 방식으로 모아치기 자판 쓰기">이어치기</label>'; opt = add_option(opts, opt_name, ft); if(KE=='Ko' && Ko_type.substr(0,3)=='3m-') opt.style.display = 'block'; else opt.style.display = 'none'; opt_name = 'convenience_combination'; ft = 'show_keyboard_layout();inputText_focus()"><label title="입력 편의를 높이기 위한 요즘한글 낱자 조합">편의 낱자 조합</label>'; opt = add_option(opts, opt_name, ft); if(!is_old_hangeul_input() && typeof current_layout_info.hangeul_convenience_combination_table!='undefined') opt.style.display = 'block'; else opt.style.display = 'none'; var layout_info = find_current_layout_info(); var sublayout = find_sublayout(layout_info); opt_name = 'enable_double_final_ext'; ft = 'ohiChange_enable_double_final_ext(this.checked);inputText_focus()"><label title="윗글쇠를 함께 누르거나 같은 글쇠를 거듭 눌러 겹받침 넣기">겹받침 확장</label>'; opt = add_option(opts, opt_name, ft); if(Ko_type.substr(0,3)!='3m-' && !is_old_hangeul_input() && sublayout.indexOf(0x11AD)>=0) opt.style.display = 'block'; else opt.style.display = 'none'; } opts = document.getElementById('bottom_options'); if(opts) { opts.style.display = 'block'; opt_name = 'square_layout'; ft = 'show_keyboard_layout();inputText_focus()"><label>가지런한 배열표</label>'; opt = add_option(opts, opt_name, ft); if(ohi_menu_num && ohi_menu_num<3 && option.show_layout) opt.style.display = 'block'; else opt.style.display = 'none'; } } function show_keyboard_layout(type) { var e=window.event; var rows = document.getElementById('keyboardLayout'); if(!rows) return false; rows.style.position = 'relative'; rows.style.display = 'block'; var inner_html=''; shift_click=0; var KE = ohi_KE; if(typeof ohi_menu_num=='undefined') ohi_menu_num=0; show_options(); if(typeof type=='undefined') type = current_layout_info.type_name; else if(type==1) { option.show_layout = 1; type = current_layout_info.type_name; } else if(!type) { option.show_layout = 0; rows.innerHTML = '<div class="show_layout"><span class="menu" onclick="option.show_layout=1;show_keyboard_layout(1);inputText_focus()">배열표 보이기</span></div>'; var opt = document.getElementById('option_square_layout'); opt.style.display = 'none'; return false; } if(!option.show_layout) return; if(ohi_menu_num && ohi_menu_num>2) { rows.style.display = 'none'; var opts = document.getElementById('middle_options'); opts.style.display = 'none'; opts = document.getElementById('bottom_options'); opts.style.display = 'none'; } var layout_info = find_current_layout_info(); var layout=[], ue=[], de=[], uh=[], dh=[], l=[]; layout = find_layout_info('En', En_type).layout; // 영문 자판 배열 for(i=0;i<layout.length;++i) l[i]=String.fromCharCode(layout[i]); push_to_key_table(ue,de,l); ue[0][13] = 'Back'; ue[1][0] = 'Tab'; ue[2][0] = ue[3][0] = ue[3][11] = 'Shift'; de[2][0] = 'Lock'; de[3][0] = de[3][11] = ' '; ue[2][12] = 'Enter'; de[0][13] = 'Space'; for(i=0;i<ue.length;++i) for(j=0;j<ue[i].length;++j) if(typeof de[i][j] != 'undefined' && ue[i][j].toLowerCase()==de[i][j].toLowerCase()) de[i][j]=' '; if(KE=='Ko') { if(sign_ext_state > 0) { // 기호 확장 배열 if(typeof current_layout_info.extended_sign_layout != 'undefined') layout = current_layout_info.extended_sign_layout; if(!layout) return; if(is_old_hangeul_input() && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined') if(typeof find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).extended_sign_layout != 'undefined') layout = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).extended_sign_layout; if(layout.length) push_extended_sign_layout_to_key_table(uh, dh, layout); } else if(ohiHangeul3_HanExtKey) { // 한글 확장 배열 layout = typeof layout_info.extended_hangeul_layout != 'undefined' ? layout_info.extended_hangeul_layout : K3_2012y_extended_hangeul_layout; push_extended_hangeul_layout_to_key_table(uh, dh, layout); } else if(typeof current_layout_info != 'undefined' && typeof current_layout_info.layout != 'undefined') { // 기본 배열 layout = find_current_layout(); push_layout_to_key_table(uh, dh, layout); } } var sublayout = find_sublayout(); if(sublayout.length && !is_old_hangeul_input() && (option.enable_double_final_ext || current_layout_info.type_name.substr(0,3)=='3m-' || current_layout_info.type_name=='3-18Na' || current_layout_info.type_name.substr(0,3)=='3-D' || current_layout_info.type_name.substr(0,9)=='Sin3-Cham') && sign_ext_state<=0) { insert_sublayout_table(ue, de, uh, dh, sublayout); } ue.push(['기준','영문','한글','Space','한/영','2벌식','3벌식']); de[4] = ['자판','바꿈','바꿈','','','바꿈','바꿈']; inner_html += '<div id="keyboardLayoutInfo"></div><span class="menu" onclick="show_keyboard_layout(0);inputText_focus()" onmouseover="this.className=\'menu over\'" onmouseout="this.className=\'menu\'">배열표 숨기기</span>'; inner_html += '<div id="keyboardLayoutTable">'; for(i=0;i<5;++i) inner_html += '<div id="row'+i+'" class="row"></div>'; inner_html += '</div>'; rows.innerHTML = inner_html; var charCode, charCode_uh; for(i=0, k=-1; ue[i]; i++) { var row = document.getElementById('row'+i); for(j=0; ue[i][j]; j++) { var tdclass = 'e1'; var tdid = 'key'+(++k); if(dh[i] && dh[i][j]) { if(typeof dh[i][j].charCodeAt == 'function') { charCode = dh[i][j].charCodeAt(0); dh[i][j] = String.fromCharCode(convert_into_compatibility_hangeul_letter(charCode)); if(compatibility_hangeul_phoneme.indexOf(dh[i][j].charCodeAt(0))<0) dh[i][j] = (unicode_ga.indexOf(charCode)>=0 ? String.fromCharCode(0x115F) : '') + (unicode_ggeut.indexOf(charCode)>=0 ? String.fromCharCode(0x115F)+String.fromCharCode(0x1160) : '') + dh[i][j]; } else charCode = dh[i][j]; if(unicode_cheos.indexOf(charCode)>=0) tdclass = 'h1'; else if(Ko_type.substr(1,2)=='t-' && charCode>=0x314F && charCode<0x3164) tdclass = 'h2 gin-hol'; else if(unicode_ga.indexOf(charCode)>=0) tdclass = 'h2'; else if(unicode_ggeut.indexOf(charCode)>=0) tdclass = 'h3'; if(special_chars.indexOf(charCode)>=0) dh[i][j] = special_chars_string[special_chars.indexOf(charCode)]; if(tdclass.substr(0,1)!='h') if(unicode_modern_ggeut.indexOf(uh[i][j].charCodeAt(0))>=0) tdclass = 'h3'; } charCode = ue[i][j].charCodeAt(0); if(KE=='En' && ue[i][j].length==1) if(charCode>64 && charCode<91 || charCode>96 && charCode<123) tdclass = 'e2'; if(unicode_NFD_hangeul_phoneme.indexOf(charCode)>=0) { ue[i][j] = String.fromCharCode(convert_into_compatibility_hangeul_letter(charCode)); } var col = appendChild(row,'div',tdclass,tdid,'','36px','0 0 0 0'); col.onclick = function(e){ e=e||window.event; tableKey_clicked(e, this.id.substr(3), dkey[this.id.substr(3)], ukey[this.id.substr(3)]); }; col.tabindex = 0; if(!option.square_layout) { if(k==13) col.style.width = '62px'; // backspace if(k==14) col.style.width = '56px'; // tab if(k==27) col.style.width = '42px'; // \ 글쇠 if(k==28) col.style.width = '67px'; // shift lock if(k==40) col.style.width = '71px'; // Enter if(k==41) col.style.width = '87px'; // 왼쪽 shift if(k==52) col.style.width = '91px'; // 오른쪽 shift } else { // 가지런한 배열표 if(k==0) col.style.width = '62px'; // ` 글쇠 if(k==13) col.style.letterSpacing = '-1px'; // backspace if(k==14) col.style.width = '62px'; // tab if(k==27) col.style.width = '36px'; // \ 글쇠 if(k==28) col.style.width = '62px'; // shift lock if(k==40) col.style.width = '76px'; // Enter if(k==41) col.style.width = '62px'; // 왼쪽 shift if(k==52) col.style.width = '116px'; // 오른쪽 shift } if(ue[i][j]=='Back' || ue[i][j]=='Tab' || ue[i][j]=='Enter' || ue[i][j]=='Shift') col.style.textAlign = 'center'; if(i==4) { if(ue[i][j]=='Space') col.style.width = '312px'; else col.style.width = '41px', col.className = 'e3 special'; } var function_keys = [13,14,28,40,41,52,56]; if(function_keys.indexOf(k)>=0) col.className += ' function'; var up = appendChild(col,'div','up','up'+k); appendChild(up,'div','ue','ue'+k,ue[i][j]); if(uh[i]) { if(uh[i][j]) { if(typeof uh[i][j].charCodeAt == 'function') { charCode = uh[i][j].charCodeAt(0); uh[i][j] = String.fromCharCode(charCode); if(compatibility_hangeul_phoneme.indexOf(uh[i][j].charCodeAt(0))<0) uh[i][j] = (unicode_ga.indexOf(charCode)>=0 ? String.fromCharCode(0x115F) : '') + (unicode_ggeut.indexOf(charCode)>=0 ? String.fromCharCode(0x115F)+String.fromCharCode(0x1160) : '') + uh[i][j]; } else charCode = uh[i][j]; if(special_chars.indexOf(charCode)>=0) uh[i][j] = special_chars_string[special_chars.indexOf(charCode)]; if(Ko_type.substr(0,2)=='2-' && is_galmadeuli_input() && unicode_ga.indexOf(charCode)>=0) { // 두벌식 갈마들이 자판 // 윗글 자리에 홀소리가 있으면 홀소리 글쇠로 나타냄 if(unicode_ga.indexOf(charCode)>=0) document.getElementById('key'+k).className = 'h2'; } if( (Ko_type.substr(0,2)=='3-' && is_galmadeuli_input() || typeof current_layout_info.sublayout != 'undefined') && unicode_modern_ggeut.indexOf(charCode)>=0 && unicode_modern_hotbadchim.indexOf(charCode)<0) { // 갈마들이 공세벌식 자판의 기본 배열에 들어가는 겹받침을 회색으로 나타냄 uh[i][j] = '<span style="color:gray;">'+uh[i][j]+'</span>'; } if(unicode_NFD_hangeul_phoneme.indexOf(charCode)>=0) { uh[i][j] = String.fromCharCode(convert_into_compatibility_hangeul_letter(charCode)); } if(uh[i][j]==dh[i][j] && uh[i][j]!=de[i][j]) uh[i][j]=' '; // 한글 배열에서 윗글과 아랫글 자리의 문자가 같을 때 윗글 자리를 나타내지 않음 } if(!sign_ext_state&&uh[i][j]==ue[i][j] || uh[i][j]=='&'&&ue[i][j]=='&amp;' || uh[i][j]=='<'&&ue[i][j]=='&lt;' || uh[i][j]=='>'&&ue[i][j]=='&gt;') uh[i][j]=' '; appendChild(up,'div','uh','uh'+k,uh[i][j]); } if(de[i][j]) { var down = appendChild(col,'div','down','down'+k); charCode = de[i][j].charCodeAt(0); if(unicode_NFD_hangeul_phoneme.indexOf(charCode)>=0) de[i][j] = String.fromCharCode(convert_into_compatibility_hangeul_letter(charCode)); appendChild(down,'div','de','de'+k,de[i][j]); if(!sign_ext_state && dh[i] && (!dh[i][j] || dh[i][j]==de[i][j])) dh[i][j]=' '; if(dh[i] && dh[i][j]) appendChild(down,'div','dh','dh'+k,dh[i][j]); } } } var sign_ext_tag = '<span style="margin-left:-1px;background:black;color:#fff;letter-spacing:0px;font-size:0.7em;">기호</div>'; var sign_ext_tag1 = '<span style="margin:0;padding:0;background:black;color:#fff;letter-spacing:-2px;font-size:0.7em;">기호①</span>'; var sign_ext_tag2 = '<span style="margin:0;padding:0;background:black;color:#fff;letter-spacing:-2px;font-size:0.7em">기호②</span>'; var han_ext_tag1 = '<span style="margin:0;padding:0;background:black;color:#fff;letter-spacing:-2px;font-size:0.7em;">한글①</span>'; var han_ext_tag2 = '<span style="margin:0;padding:0;background:black;color:#fff;letter-spacing:-2px;font-size:0.7em;">한글②</span>'; var Moachigi_modifier_tag = '<span style="background:black;color:#fff;font-size:1em;">⇦</span>'; if(option.enable_sign_ext && KE=='Ko' && Ko_type.substr(0,2)=='3-' && typeof current_layout_info.extended_sign_layout != 'undefined') { // 공세벌식 자판의 기호 확장 글쇠 나타내기 if(Ko_type=='3-87' || Ko_type=='3-891' || Ko_type=='3-91' || Ko_type=='3-95') { document.getElementById('de51').innerHTML = sign_ext_tag; } else if(!is_old_hangeul_input() && (Ko_type=='3-2011' || Ko_type=='3-2012')) { document.getElementById('de8').innerHTML = sign_ext_tag; document.getElementById('de45').innerHTML = sign_ext_tag; } else { document.getElementById('uh9').innerHTML = sign_ext_tag2; document.getElementById('uh51').innerHTML = sign_ext_tag1; } } if(KE=='Ko' && is_old_hangeul_input() && ['3-2011','3-2011-y','3-2012','3-2012-y','3-2014','3-2014-y','3-2015P','3-2015P-y'].indexOf(Ko_type)>=0) { document.getElementById('dh7').innerHTML = han_ext_tag1; document.getElementById('dh8').innerHTML = han_ext_tag2; document.getElementById('uh7').innerHTML = '<span style="color:#666;font-size:0.8em">(ㅣ)</span>'; document.getElementById('uh8').innerHTML = '<span style="color:#666;font-size:0.8em">(ㅡ)</span>'; } if(KE=='Ko' && Ko_type.substr(0,4)=='Sin3' && Ko_type.substr(0,9)!='Sin3-Cham') { // 신세벌식 자판의 첫소리 자리에서 넣는 홀소리들 if(!sign_ext_state && !(!option.enable_Sin3_diphthong_key && is_old_hangeul_input()) && !(checkCapsLock() && typeof layout_info.capslock_extended_sign_layout != 'undefined' && !layout_info.capslock_extended_sign_layout)) { if(sublayout.length) { if(sublayout[14]) // 빗금(/) 자리의 겹낱자 확장 배열 홀소리 document.getElementById('uh51').innerHTML = ohiHangeul3_HanExtKey ? '' : '<font size="1">('+String.fromCharCode(convert_into_compatibility_hangeul_letter(sublayout[14]))+')</font>'; if(sublayout[72]) // 신세벌식 P2의 오른쪽 ㅡ 자리 (i 자리) document.getElementById('de22').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="font-size:10px; letter-spacing:-2px;color:#333;">'+String.fromCharCode(convert_into_compatibility_hangeul_letter(sublayout[72]))+'</span>'; if(sublayout[78]) // 신세벌식 P2의 오른쪽 ㅜ 자리 (o 자리) document.getElementById('de23').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="font-size:10px; letter-spacing:-2px;color:#333;">'+String.fromCharCode(convert_into_compatibility_hangeul_letter(sublayout[78]))+'</span>'; if(sublayout[79]) { if(sublayout[79]==0x119E) // P 자리의 겹낱자 확장 배열 (신세벌식 P2의 오른쪽 아래아) document.getElementById('de24').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="font-size:10px; letter-spacing:-3px;color:#333;">'+String.fromCharCode(convert_into_compatibility_hangeul_letter(sublayout[79]))+'</span>'; else document.getElementById('de24').innerHTML = ohiHangeul3_HanExtKey ? '' : '<font size="1">'+String.fromCharCode(convert_into_compatibility_hangeul_letter(sublayout[79]))+'</font>'; } } else { if(En_type=='Dvorak') document.getElementById('de51').innerHTML = '<font size="1">(ㅗ)</font>'; else if(typeof current_layout_info.layout[30] == 'number' && current_layout_info.layout[30]==0x3F) document.getElementById('uh51').innerHTML = ohiHangeul3_HanExtKey ? '' : '<font size="1">(ㅗ)</font>'; } } if(option.enable_sign_ext && typeof current_layout_info.extended_sign_layout != 'undefined' && current_layout_info.extended_sign_layout && !(checkCapsLock() && typeof layout_info.capslock_extended_sign_layout != 'undefined' && !layout_info.capslock_extended_sign_layout)) { // 신세벌식 기호 확장 배열 전환 글쇠 document.getElementById('de35').innerHTML = ohiHangeul3_HanExtKey ? '' : sign_ext_tag; for(i=0;i<3;++i) document.getElementById('de'+(36+i)).innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="padding:0 1px;background:black;color:#fff;font-size:10px;">'+String.fromCharCode(0x2460+i)+'</span>'; } if(is_old_hangeul_input() && !sign_ext_state) { // 신세벌식 P, P2 옛한글 받침 배열 document.getElementById('de32').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="margin-left:-1px;background:black;color:#fff;letter-spacing:0px;font-size:0.7em;">받침</span>'; document.getElementById('de15').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="color:#666">ㅿ</span>'; document.getElementById('de29').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="color:#666">ㆁ</span>'; document.getElementById('de31').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="color:#666">ㆆ</span>'; document.getElementById('de31').innerHTML = ohiHangeul3_HanExtKey ? '' : '<span style="color:#666">ㆆ</span>'; } } if(KE=='Ko' && En_type!='Dvorak' && !sign_ext_state && !ohiHangeul3_HanExtKey) { if((Ko_type.substr(0,5)=='Sin3-' && typeof current_layout_info.sublayout != 'undefined' && current_layout_info.sublayout[58]==0x119E) || Ko_type.substr(0,3) == '3-P' || Ko_type.substr(0,7)=='3-2015P' || Ko_type.substr(0,6)=='3-2014' || Ko_type.substr(0,6)=='3-2012' || Ko_type=='3-90') { document.getElementById('dh25').innerHTML = ohiHangeul3_HanExtKey ? '' : '<font size="1">(ㆍ)</font>'; } } if(KE=='Ko' && Ko_type=='3m-Semoe2014') document.getElementById('uh38').innerHTML += Moachigi_modifier_tag; if(KE=='Ko' && Ko_type=='3m-Semoe2015') { document.getElementById('uh24').innerHTML = Moachigi_modifier_tag; document.getElementById('uh38').innerHTML = Moachigi_modifier_tag; document.getElementById('ue45').removeAttribute('class'); document.getElementById('ue45').style.float = 'left'; document.getElementById('ue45').innerHTML = Moachigi_modifier_tag; } if(KE=='Ko' && Ko_type=='3m-Semoe2016') { if(sign_ext_state<=0) { document.getElementById('uh25').innerHTML = Moachigi_modifier_tag; document.getElementById('uh38').innerHTML = Moachigi_modifier_tag; document.getElementById('ue45').removeAttribute('class'); document.getElementById('ue45').style.float = 'left'; document.getElementById('ue45').innerHTML = Moachigi_modifier_tag; } if(option.enable_sign_ext) { document.getElementById('uh35').innerHTML = sign_ext_tag; if(sign_ext_state<0) { for(i=0;i<3;++i) document.getElementById('uh'+(36+i)).innerHTML = '<span style="padding:0 1px;background:black;color:#fff;font-size:10px;">'+String.fromCharCode(0x2460+i)+'</span>'; } } } if(KE=='Ko' && Ko_type.substr(0,8)=='3m-Semoe' && (!Number(Ko_type.substr(0,8)) || Number(Ko_type.substr(0,8))>=2017)) { if(sign_ext_state<=0) { document.getElementById('uh38').innerHTML = Moachigi_modifier_tag; document.getElementById('ue45').removeAttribute('class'); document.getElementById('ue45').style.float = 'left'; document.getElementById('ue45').innerHTML = Moachigi_modifier_tag; document.getElementById('uh50').innerHTML = Moachigi_modifier_tag; } if(option.enable_sign_ext) { document.getElementById('de35').innerHTML = sign_ext_tag; for(i=0;i<3;++i) document.getElementById('de'+(36+i)).innerHTML = '<span style="padding:0 1px;background:black;color:#fff;font-size:10px;">'+String.fromCharCode(0x2460+i)+'</span>'; } } if(KE=='Ko' && (Ko_type=='3t-Oesol' || Ko_type=='4t-1985')) { document.getElementById('ue41').innerHTML = 'Shift'; document.getElementById('de41').innerHTML = '(받침)'; document.getElementById('ue52').innerHTML = 'Shift'; document.getElementById('de52').innerHTML = '(받침)'; if(Ko_type=='4t-1985') { // 홀소리와 받침이 함께 든 글쇠를 받침이 든 글쇠와 같은 색으로 나타냄 document.getElementById('key25').className = 'h3'; document.getElementById('key38').className = 'h3'; document.getElementById('key39').className = 'h3'; } } if(shiftlock_click) { var shiftlock = document.getElementById('key28'); shiftlock.style.backgroundColor = 'orange'; } show_keyboard_layout_info(); } function checkCapsLock() { var e=window.event; if(typeof e != 'undefined' && e.getModifierState && e.getModifierState("CapsLock")) return true; return false; } function copyToClipboard(text) { var t; if(typeof text.value == 'undefined') { t = document.createElement("textarea"); document.body.appendChild(t); t.value = text.innerHTML; } else t=text; t.select(); t.setSelectionRange(0, 99999); /* For mobile devices */ document.execCommand('copy'); if(typeof text.value == 'undefined') document.body.removeChild(t); } function ohiStart() { var i; // 수정됨: inputText -> wpTextbox1 var textarea=document.getElementById('wpTextbox1'); var inputs=document.getElementsByTagName("INPUT"); complete_hangeul_syllable(this); if(option.turn_off_OHI) { show_ohiStatusBar(0); // 보람줄(상태 표시줄) 감추기 if(textarea) textarea.style.imeMode = 'active'; if(inputs) { for(i=0;i<inputs.length;++i) { if(inputs[i].className=='text') inputs[i].style.imeMode = 'active'; } } return; } if(typeof current_layout_info=='undefined' || !current_layout_info || typeof current_layout_info.KE=='undefined' || !current_layout_info.KE) { ohiChange(default_ohi_KE, default_ohi_KE=='En' ? default_En_type : default_Ko_type); } ohi_KE = current_layout_info.KE; ohiStatus.innerHTML = '<a href="javascript:ohiChange_KE();" style="color:White;text-decoration:none;">&nbsp;' + ohi_KE.toUpperCase() + ' </a>' + ' | <a href="javascript:ohiChange_between_same_type(\'Ko\');"><span style="color:yellow">Ko:</span><span style="color:Aquamarine">' + (is_old_hangeul_input() && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined' ? current_layout_info.old_hangeul_layout_type_name : Ko_type) + '</span></a>' + ' / <a href="javascript:ohiChange_between_same_type(\'En\');"><span style="color:LightPink">En:</span><span style="color:Aquamarine">' + En_type + '</span></a>' + ' | <a href="javascript:ohiChange_KBD_type();" style="color:WhiteSmoke;text-decoration:none;">' + ohi_KBD_type + '&nbsp;</a>'; if(document.body) { show_ohiStatusBar(1); var onmousedown = function(e) { complete_hangeul_syllable(this); prev_cursor_position = -1; }; if(textarea) { textarea.style.imeMode = 'disabled'; textarea.onmousedown = onmousedown; } if(inputs) { for(i=0;i<inputs.length;++i) { if(inputs[i].className=='text') { inputs[i].style.imeMode = 'disabled'; inputs[i].onmousedown = onmousedown; } } } if(document.all) { ohiStatus.style.position = 'fixed'; ohiStatus.style.right = -(document.body.scrollLeft||document.documentElement.scrollLeft)+'px'; ohiStatus.style.bottom = -(document.body.scrollTop||document.documentElement.scrollTop)+'px'; } if(document.body != ohiStatus.parentNode) { if(!ohiStatus.style.position) { ohiStatus.style.position = 'fixed'; ohiStatus.style.right = '0px'; ohiStatus.style.bottom = '0px'; } ohiStatus.target = '_blank'; //ohiStatus.href = 'http://ohi.pat.im'; ohiStatus.style.fontFamily = 'GulimChe,monospace'; ohiStatus.style.fontWeight = 'normal'; ohiStatus.style.color = 'white'; ohiStatus.style.backgroundColor = 'royalblue'; ohiStatus.style.fontSize = '13px'; ohiStatus.style.lineHeight = '13px'; ohiStatus.style.zIndex = '2550000'; /* --- 수정됨: document 전역 대신 텍스트 영역에만 이벤트 리스너 바인딩 --- */ if(textarea) { if(textarea.addEventListener) { textarea.addEventListener('keypress', ohiKeypress, true); textarea.addEventListener('keydown', ohiKeydown, true); textarea.addEventListener('keyup', ohiKeyup, true); } else { textarea.onkeydown = ohiKeydown; textarea.onkeypress = ohiKeypress; textarea.onkeyup = ohiKeyup; } } for(var i=0; i<window.frames.length; i++) { try { if(typeof(window.frames[i].document) == 'unknown') continue; } catch(e) { continue; } var ohi = document.createElement('script'); ohi.type= 'text/javascript'; var js_list = ['keyboard_layouts','additional_layouts','ohi']; for(var j=0;j<js_list.length;++j) { ohi.src = '//ohi.pat.im/'+js_list[j]+'.js'; window.frames[i].document.body.appendChild(ohi); } } show_NCR_text(); show_direct_typing_text(); } } else ohiTimeout = setTimeout("ohiStart()",100); } function show_keyboard_layout_info() { var KE=ohi_KE; var kbd = ohi_KBD_type=='QWERTY' ? '' : ':'+ohi_KBD_type; var type_name = typeof current_layout_info.type_name != 'undefined' ? current_layout_info.type_name : ''; var name='', link='', keyboardLayoutInfo = document.getElementById('keyboardLayoutInfo'); if(keyboardLayoutInfo) { if(KE=='En') { name = '<strong>[영문' + kbd + ']</strong> '; if(En_type=='QWERTY') name += '쿼티 (QWERTY)'; else if(En_type=='Dvorak') name += '드보락 (Dvorak)'; else if(En_type=='Colemak') name += '콜맥 (Colemak)'; else if(En_type=='Workman') name += '워크맨 (Workman)'; } else { var beol = '3'; if(type_name.substr(0,1)=='2') beol = '2'; else if(type_name.substr(0,1)=='4') beol = '4'; name = '<strong>[한글 ' + beol + '벌식' + kbd + (is_galmadeuli_input() ? ': 갈마들이' : '') + (is_moachigi_input() ? ': 모아치기' : '') + ']</strong> '; var full_name = typeof current_layout_info.full_name != undefined ? current_layout_info.full_name : ''; if(is_old_hangeul_input()) { if(typeof find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).full_name != 'undefined') full_name = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).full_name; } if(typeof current_layout_info.full_name != 'undefined') name += full_name; if(is_old_hangeul_input() && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined' && typeof find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).link != 'undefined' && find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).link) link = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).link; else if(typeof current_layout_info.link != 'undefined' && current_layout_info.link) link = current_layout_info.link; if(link) name += ' <a href="'+link+'" target="_blank">ⓘ</a>'; } keyboardLayoutInfo.innerHTML = name; } } function find_combination_table() { // 낱자 조합 규칙 정보 찾기 var i; var combination_table = []; var layout_info = find_current_layout_info(); if(typeof current_layout_info.moachigi_hangeul_combination_table != 'undefined' && typeof current_layout_info.hangeul_combination_table == 'undefined') { // 모아치기 자판을 이어치기 방식으로 쓸 때 combination_table = current_layout_info.moachigi_hangeul_combination_table; } else { // 이어치기 자판 combination_table = is_old_hangeul_input() ? hangeul_combination_table_full : hangeul_combination_table_default; // 자판 배열 정보에서 지정한 낱자 조합 규칙 if(typeof layout_info.hangeul_combination_table != 'undefined') { combination_table = layout_info.hangeul_combination_table; } if(is_old_hangeul_input() && option.enable_old_hangeul_input) { if(typeof current_layout_info.old_hangeul_layout_type_name != 'undefined' && typeof find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).hangeul_combination_table != 'undefined') combination_table = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name).hangeul_combination_table; } // 편의를 높이기 위한 낱자 조합을 더함 (옛한글 자판이 아닌 한글 자판) if(!is_old_hangeul_input() && option.convenience_combination && typeof layout_info.hangeul_convenience_combination_table != 'undefined') combination_table = layout_info.hangeul_convenience_combination_table.concat(combination_table); } return combination_table; } function find_galmadeuli_chars(key) { // 쿼티 자판 기준으로 p 자리 글쇠가 눌렸다면 // [c2, sub_c2] [0x003B,0x0000], /* 0x50 P: semicolon */ // [c1, sub_c1] [0x1111,0x119E], /* 0x70 p: choseong pieup, jungseong alae-a */ // c1 : 기본 배열 아랫글 자리 (0x1111) // c2 : 기본 배열 윗글 자리 (0x003B) // sub_c1 : 보조 배열 아랫글 자리 (0x119E) // sub_c2 : 보조 배열 윗글 자리 (0x0000) var c1, c2, sub_c1=0, sub_c2=0; var layout = find_current_layout(); var sublayout = find_sublayout(); if(typeof layout[key-33] == 'number') { c1 = layout[key-33]; c2 = layout[shift_table[key-33]-33]; if(sublayout.length) { sub_c1 = sublayout[key-33]; sub_c2 = sublayout[shift_table[key-33]-33]; } } else { c1 = layout[key-33][0]; c2 = layout[shift_table[key-33]-33][0]; sub_c1 = layout[key-33][1]; sub_c2 = layout[shift_table[key-33]-33][1]; } return [c1, c2, sub_c1, sub_c2]; } function find_character_combination_table() { var layout_info = find_current_layout_info(); var character_combination_table = []; if(typeof layout_info.character_combination_table != 'undefined') character_combination_table = layout_info.character_combination_table.slice(); if(is_old_hangeul_input()) character_combination_table = character_combination_table.concat(yeshangeul_sign_combination); return character_combination_table; } function find_layout_info(KE, type_name) { if(typeof type_name == 'undefined' || !type_name) return false; var i,j; var a=[keyboard_layouts]; if(typeof additional_layouts != 'undefined') a.push(additional_layouts); if(typeof test_layouts != 'undefined') a.push(test_layouts); for(i=0;i<a.length;++i) for(j=0;j<a[i].length;++j) if(KE==a[i][j].KE && typeof a[i][j].type_name != 'undefined' && type_name.toLowerCase()==a[i][j].type_name.toLowerCase()) return a[i][j]; return false; } function find_current_layout() { var layout_info = find_current_layout_info(); var layout = layout_info.layout; if(checkCapsLock() && typeof layout_info.capslock_layout != 'undefined') layout = layout_info.capslock_layout; return layout; } function find_current_layout_info() { var layout_info = current_layout_info; if(is_old_hangeul_input() && typeof current_layout_info.old_hangeul_layout_type_name != 'undefined') layout_info = find_layout_info('Ko', current_layout_info.old_hangeul_layout_type_name); return layout_info; } function find_extended_sign_layout(layout_info) { if(typeof layout_info == 'undefined') layout_info = find_current_layout_info(); var sign_layout = null; if(typeof layout_info.extended_sign_layout != 'undefined') sign_layout = layout_info.extended_sign_layout; if(checkCapsLock()) { if(typeof layout_info.capslock_extended_sign_layout != 'undefined') sublayout=layout_info.capslock_extended_sign_layout; } return sign_layout; } function find_mainlayout(layout_info) { var layout = typeof layout_info == 'undefined' ? find_current_layout() : layout_info.layout; var mainlayout = []; if(typeof layout[0] == 'number') for(i=0;i<layout.length;++i) mainlayout.push(layout[i]); else for(i=0;i<layout.length;++i) mainlayout.push(layout[i][0]); return mainlayout; } function find_sublayout(layout_info) { if(typeof layout_info == 'undefined') layout_info = find_current_layout_info(); var sublayout = []; if(checkCapsLock() && typeof layout_info.capslock_sublayout != 'undefined') sublayout=layout_info.capslock_sublayout; else if(checkCapsLock() && typeof layout_info.capslock_layout != 'undefined' && typeof layout_info.capslock_layout[0] != 'number') { for(i=0;i<layout_info.capslock_layout.length;++i) sublayout.push(layout_info.capslock_layout[i][1]); } else if(typeof layout_info.sublayout != 'undefined') sublayout=layout_info.sublayout; else if(typeof layout_info.layout != 'undefined' && typeof layout_info.layout[0] != 'number') { for(i=0;i<layout_info.layout.length;++i) sublayout.push(layout_info.layout[i][1]); } return sublayout; } function ohiChange(KE, type_name) { // 수정됨: inputText -> wpTextbox1 var f=document.getElementById('wpTextbox1'); inputText_focus(); if(NFD_stack.phoneme.length && f) complete_hangeul_syllable(f); esc_ext_state(); var prev_layout_info = typeof current_layout_info != 'undefined' ? current_layout_info : null; if(KE.toLowerCase()=='en') KE='En'; else if(KE.toLowerCase()=='ko' || KE.toLowerCase()=='k2' || KE.toLowerCase()=='k3') KE='Ko'; ohi_KE = ohi_KE.replace(/(En|Ko)/, KE.substr(0,2)); var layout_info = find_layout_info(KE, type_name); if(layout_info) { current_layout_info = layout_info; if(KE=='En') En_type = current_layout_info.type_name; else Ko_type = current_layout_info.type_name; } if(layout_info != prev_layout_info) { ohiStart(); show_keyboard_layout(KE=='En' ? En_type : Ko_type); } } function ohiChange_between_same_type(type) { // 같은 한·영 종류의 배열 바꾸기 (Ko는 주요 배열만 간추림) var i,j=-1; var En_type_array = ['QWERTY','Dvorak','Colemak','Workman']; var Ko_type_array = ['2-KSX5002','2-KPS9256','Sin3-P2','3m-Semoe','3-P3']; if(type=='En') { for(i=0;i<En_type_array.length;++i) if(En_type.toLowerCase()==En_type_array[i].toLowerCase()) j=i; En_type = En_type_array[(j+1)%i]; ohiChange('En',En_type); return; } var a=[keyboard_layouts]; if(typeof additional_layouts != 'undefined') a.push(additional_layouts); if(typeof test_layouts != 'undefined') a.push(test_layouts); for(i=0;i<Ko_type_array.length;++i) { if(type=='K2' && Ko_type_array[i].substr(0,1)!='2' || type=='K3' && Ko_type_array[i].substr(0,1)=='2') { // 두벌식 자판 이름(type_name)의 앞에 2이 붙지 않았거나 세벌식 자판 이름에 2이 붙은 것은 뺌 Ko_type_array.splice(i--,1); } } if(type!='Ko') { // type이 K2이면 Ko_type_array에 모든 두벌식 자판 이름을 넣고, K3이면 모든 세벌식 자판 이름을 넣음 for(i=0;i<a.length;++i) for(j=0;j<a[i].length;++j) if(a[i][j].KE=='Ko' && typeof a[i][j].type_name != 'undefined' && Ko_type_array.indexOf(a[i][j].type_name)<0) { if(type=='K2' && a[i][j].type_name.substr(0,1)=='2') Ko_type_array.push(a[i][j].type_name); if(type=='K3' && a[i][j].type_name.substr(0,1)!='2') Ko_type_array.push(a[i][j].type_name); } } for(i=0;i<Ko_type_array.length;++i) if(Ko_type.toLowerCase()==Ko_type_array[i].toLowerCase()) j=i; if(type!='Ko' && (Ko_type.substr(0,1)=='2'&&Ko_type_array[(j+1)%i].substr(0,1)!='2' || Ko_type.substr(0,1)!='2'&&Ko_type_array[(j+1)%i].substr(0,1)=='2')) Ko_type = Ko_type_array[0]; else Ko_type = Ko_type_array[(j+1)%i]; ohiChange('Ko',Ko_type); } function ohiChange_KE(type) { // 한·영 상태 바꾸기 var KE = ohi_KE; if(type === undefined || !type) { if(KE=='En') ohiChange('Ko',Ko_type); else if(KE=='Ko') ohiChange('En',En_type); } else if(type=='En') ohiChange('En',En_type); else if(type=='Ko') ohiChange('Ko',Ko_type); } function ohiChange_KBD_type(type) { // 기준 자판 바꾸기 if(type === undefined || !type) { ohi_KBD_type = ohi_KBD_type=='QWERTY' ? 'QWERTZ' : ohi_KBD_type=='QWERTZ' ? 'AZERTY' : 'QWERTY'; ohiStart(); } else { ohi_KBD_type = type; ohiStart(); } show_keyboard_layout(option.show_layout); } function ohiChange_enable_sign_ext(op) { if(op=='off' || op=='0') option.enable_sign_ext = 0; else option.enable_sign_ext = 1; show_keyboard_layout(option.show_layout); var checkbox = document.getElementsByName('sign_extension'); if(checkbox && typeof checkbox[0].checked != 'undefined') checkbox[0].checked = option.enable_sign_ext; } function ohiChange_enable_old_hangeul_input(op) { if(typeof op != 'undefined') { if(op=='off' || op=='0') option.enable_old_hangeul_input = 0; else option.enable_old_hangeul_input = 1; } complete_hangeul_syllable(); Sin3_hangeul_extension(); show_keyboard_layout(option.show_layout); } function ohiChange_enable_phonemic_writing(op) { if(op=='off' || op=='0') option.enable_phonemic_writing = 0; else option.enable_phonemic_writing = 1; show_keyboard_layout(option.show_layout); } function ohiChange_force_normal_typing(op) { // 모아치기 자판을 이어치기(일반 타자법)으로 치게 하기 if(typeof op != 'undefined' && op=='off' || op=='0') option.force_normal_typing = 0; else option.force_normal_typing = 1; var checkbox = document.getElementsByName('force_normal_typing'); if(checkbox && typeof checkbox[0].checked != 'undefined') checkbox[0].checked = option.force_normal_typing; } function Sin3_hangeul_extension() { if(Ko_type.substr(0,5)!='Sin3-') return; var opt = document.getElementById('option_enable_Sin3_diphthong_key'); if(opt) { if(option.enable_old_hangeul_input && current_layout_info.type_name.substr(0,5)=='Sin3-') opt.style.display = 'block'; else opt.style.display = 'none'; } } function ohiKeyswap(e,key) { var KE=ohi_KE; var i=0, swaped=[]; if(ohi_KBD_type=='QWERTZ') swaped=[89,90,90,89,121,122,122,121]; if(ohi_KBD_type=='AZERTY') swaped=[65,81,81,65,87,90,90,87,97,113,113,97,119,122,122,119,77,58,109,59,44,109,58,46,59,44]; while(swaped[i] && swaped[i]!=key) i+=2; if(i!=swaped.length) key=swaped[i+1]; if(KE!='En' || En_type!='QWERTY') { // 영문 쿼티 자판이 아니면 Caps Lock이 적용되지 않게 함 if(key>64 && key<91 && !e.shiftKey) key+=32; if(key>96 && key<123 && e.shiftKey) key-=32; } return key; } function ohiKeypress(e) { if(onkeypress_skip) return false; var key_pressed=0; // 특수 기능 글쇠가 아닌 글쇠(일반 글쇠)가 눌렸는지 var KE=ohi_KE.substr(0,2); var e=e||window.event, f=e.target||e.srcElement, n=f.nodeName||f.tagName, key=e.which||e.which==0?e.which:e.keyCode; key=ohiKeyswap(e,key); if(f.id=='inputText') {show_NCR_text();show_direct_typing_text();} if(option.turn_off_OHI) { tableKey_press(key); return false; } var i = ohiQ[0]+ohiQ[3]+ohiQ[6] || NFD_stack.phoneme.length ? 1 : 0; if(f.type=='text' && n=='INPUT' || n=='TEXTAREA') { if((key==13 || key==32) && !e.ctrlKey && !e.shiftKey && !e.altKey) { // 줄바꾸개(enter)와 사이띄개(space bar) if(!(browser=="MSIE" && browser_ver<9)) { if(key==32) if(e.preventDefault) e.preventDefault(); if(!i && f.selectionEnd!=f.selectionStart) ohiBackspace(f); } prev_cursor_position = -1; complete_hangeul_syllable(f); // 풀어쓰기를 하고 있고 '낱내 뒤 빈칸 넣기' 기능을 쓰는데 한글을 조합하다가 사이띄개가 눌리면 빈칸을 더하여 넣음 if(i && key==32 && is_phonemic_writing_input() && option.phonemic_writing_adding_space_every_syllable_end) ohiInsert(f,0,32); ohiInsert(f,0,key); esc_ext_state(); } else if((key==10 || key==13 || key==32) && (e.ctrlKey^e.shiftKey)) { // Toggle if(e.preventDefault) e.preventDefault(); if((key==10 || key==13) && e.ctrlKey) ohiChange_KBD_type(); // 기준 자판 바꾸기 else if(key==32 && (e.ctrlKey || e.shiftKey)) ohiChange_KE(); // 한·영 상태 바꾸기 key_pressed=0; } else if(key==49 && e.altKey && !e.ctrlKey && !e.shiftKey) { // 영문 배열 종류 바꾸기 (QWERTY/Dvorak/Colemak) ohiChange_between_same_type('En'); if(e.preventDefault) e.preventDefault(); key_pressed=0; } else if(key==50 && e.altKey && !e.ctrlKey && !e.shiftKey) { // 두벌식 배열 종류 바꾸기 (한국/조선 표준 자판) ohiChange_between_same_type('K2'); if(e.preventDefault) e.preventDefault(); key_pressed=0; } else if(key==51 && e.altKey && !e.ctrlKey && !e.shiftKey) { // 세벌식 배열 종류 바꾸기 ohiChange_between_same_type('K3'); if(e.preventDefault) e.preventDefault(); key_pressed=0; } else if(ohi_KE.substr(0,2)=='En' && key>32 && key<127 && e.keyCode<127 && !e.altKey && !e.ctrlKey) { // 영문 상태에서 일반 글쇠 if(e.preventDefault) e.preventDefault(); ohiRoman(f,e,key); key_pressed=1; } else if(ohi_KE.substr(0,2)!='En' && key>32 && key<127 && e.keyCode<127 && !e.altKey && !e.ctrlKey) { // 영문 상태가 아닐 때 일반 글쇠 if(e.preventDefault) e.preventDefault(); key_pressed=1; if(is_moachigi_input()) pressed_key_accumulation(f,e,key); else { if((document.selection && document.selection.createRange().text.length!=1) || (f.selectionEnd+1 && f.selectionEnd-f.selectionStart!=1)) ohiInsert(f,0,0); if(ohi_KE.substr(0,2)=='Ko') { if(current_layout_info.type_name.substr(0,2)=='2-') ohiHangeul2(f,e,key); else if(!ohiHangeul3_abbreviation(f,key)) ohiHangeul3(f,e,key); } } } } tableKey_press(key); return false; } function ohiKeydown(e) { if(option.turn_off_OHI) { show_NCR_text(); show_direct_typing_text(); return false; } onkeypress_skip=0; // 참이면 ohiKeypress() 처리를 건너뜀 onkeyup_skip=0; // 참이면 ohiKeyup() 처리를 건너뜀 var i=0; var e=e||window.event, f=e.target||e.srcElement, n=f.nodeName||f.tagName, key=e.which||e.which==0?e.which:e.keyCode; var KE = ohi_KE; if(f.type=='text' && n=='INPUT' || n=='TEXTAREA') { if(e.keyCode>=96 && e.keyCode<=111) { // 오른쪽 숫자판(키패드) 글쇠일 때 onkeypress_skip=1; esc_ext_state(); var c=Array(/*0*/48,/*1*/49,/*2*/50,/*3*/51,/*4*/52,/*5*/53,/*6*/54,/*7*/55,/*8*/56,/*9*/57, /***/42,/*+*/43,0,/*-*/45,/*.*/46,/*/*/47)[e.keyCode-96]; ohiInsert(f,0,c); if(e.preventDefault) e.preventDefault(); return false; } if(e.keyCode==8) { // Backspace tableKey_press(e.keyCode); if(is_moachigi_input()) { if(e.preventDefault) e.preventDefault(); pressed_key_accumulation(f,e,key); onkeyup_skip=0; return false; } else if(option.abbreviation && prev_cursor_position>=0) { // 이어치기 자판으로 줄임말을 넣은 뒤 ohiHangeul_moa_backspace(f,e); return false; } if(character_combination_queue.length && ohiCombinedCharacter_backspace(f,e)) return false; if(ohiHangeul_backspace(f,e)) return false; if(e.preventDefault) e.preventDefault(); ohiBackspace(f); onkeyup_skip=1; } if(e.keyCode==13) { // Enter (한글 조합 상태) tableKey_press(e.keyCode); if(character_combination_queue.length) ohiSelection(f,0); prev_class = []; if(is_moachigi_input()) { if(e.preventDefault) e.preventDefault(); if(!(ohiQ[0]+ohiQ[3]+ohiQ[6]) && !NFD_stack.phoneme.length && typeof f.selectionEnd != 'undefined' && f.selectionStart != f.selectionEnd) ohiBackspace(f); pressed_key_accumulation(f,e,key); esc_ext_state(); return false; } } if(e.keyCode==32) { // Space tableKey_press(e.keyCode); if(NFD_stack.phoneme.length || ohiQ[0]+ohiQ[3]+ohiQ[6] || character_combination_queue.length) ohiSelection(f,0); prev_class = []; if(is_moachigi_input()) { if(!pressing_keys) return false; if(e.preventDefault) e.preventDefault(); pressed_key_accumulation(f,e,key); onkeyup_skip = 0; return false; } } if(e.keyCode==20) { // Caps Lock //tableKey_press(e.keyCode); show_keyboard_layout(); } if((e.keyCode>=35 && e.keyCode<=40) || e.keyCode==45 || e.keyCode==46) { // end(35), home(36), 화살표(37~40), insert(45), del(46) if(NFD_stack.phoneme.length || ohiQ[0]+ohiQ[3]+ohiQ[6]) { // 한글 조합 상태 complete_hangeul_syllable(f); prev_class = []; prev_cursor_position = -1; } esc_ext_state(); } if(e.keyCode==17) { // ctrl if(!pressing_keys && pressed_keys.indexOf(17)<0) pressed_key_accumulation(f,e,key); } if(e.keyCode!=17 && pressing_keys && pressed_keys.indexOf(17)>=0) { // ctrl + ? pressed_keys=[]; pressing_keys=0; if(ohiQ[0]+ohiQ[3]+ohiQ[6] || NFD_stack.phoneme.length) { complete_hangeul_syllable(f); } } /* if(e.keyCode==18) { // Alt } if(e.keyCode==91 || e.keyCode==93) { // menu } if(e.keyCode>=112 && e.keyCode<=123) { // F1~F12 } */ if(e.keyCode==16) { // shift if(KE=='Ko' && Ko_type=='2-Gaon26KM') { pressed_key_accumulation(f,e,key); tableKey_press(e.keyCode); } if(KE=='Ko' && Ko_type=='4t-1985') tableKey_press(e.keyCode); if(Ko_type.substr(0,3)=='3m-' && !option.force_normal_typing) tableKey_press(e.keyCode); } if(e.keyCode<45 && e.keyCode!=16 && e.keyCode!=17 && e.keyCode!=18 && e.keyCode!=13 && e.keyCode!=32) { if(NFD_stack.phoneme.length || ohiQ[0]+ohiQ[3]+ohiQ[6]) complete_hangeul_syllable(f); esc_ext_state(); prev_class = []; prev_cursor_position = -1; } } if(f.id=='inputText') {show_NCR_text();show_direct_typing_text();} } function ohiKeyup(e) { var e=e||window.event, f=e.target||e.srcElement; var KE=ohi_KE.substr(0,2); var exceptional_keys = [32,13,8,16]; // 사이띄개(32), 줄바꾸개(13), 뒷걸음쇠(8), 윗글쇠(16) if(onkeyup_skip || option.turn_off_OHI || (e.keyCode<47 && exceptional_keys.indexOf(e.keyCode)<0)) {} else if(!option.force_normal_typing && KE=='Ko' && Ko_type.substr(0,3)=='3m-') { if(e.keyCode==16 || pressing_keys && !--pressing_keys) { // 윗글쇠(16)를 떼었거나 모든 글쇠를 뗌 while(pressed_keys.indexOf(16)>=0) pressed_keys.splice(pressed_keys.indexOf(16),1); if(pressed_keys.length) ohiHangeul3_moa(f,e); prev_pressed_keys = pressed_keys.slice(); pressed_keys=[]; pressing_keys=0; } } else if(KE=='Ko' && Ko_type=='2-Gaon26KM') { if(pressing_keys && !--pressing_keys) { // 윗글쇠(shift)가 눌렸으면 한글 조합을 끊음 if(pressed_keys.length==1 && pressed_keys[0]==16 && e.keyCode==16) complete_hangeul_syllable(f); pressed_keys=[]; } } else if(KE=='Ko' && Ko_type=='4t-1985') { // 윗글쇠(shift)를 때에 따라 고정되는 윗글쇠(shift lock)로 씀 if(e.keyCode==16) shift_lock=1; } if(e.keyCode==17 && pressing_keys && pressed_keys.indexOf(17)>=0) { // ctrl을 떼었을 때 --pressing_keys; pressed_keys.splice(pressed_keys.indexOf(17)); } if(f.id=='inputText') {show_NCR_text();show_direct_typing_text();} } function pressed_key_accumulation(f,e,key) { if(pressed_keys.length && pressed_keys[pressed_keys.length-1]==key) { // 한 글쇠가 오래 눌려서 같은 문자가 들어왔을 때 ohiHangeul3_moa(f,e); pressed_keys=[]; pressing_keys=0; prev_cursor_position = -1; if(key==8) { ohiHangeul_moa_backspace(f,e); return; } ohiHangeul3(f,e,key); } else { ++pressing_keys; pressed_keys.push(key); } } function inputText_focus(f) { // 수정됨: inputText -> wpTextbox1 if(typeof f =='undefined') f=document.getElementById('wpTextbox1'); if(f) f.focus(); } function inputText_rows(r) { // 글상자(textarea)의 줄 수를 바꿈 // 수정됨: inputText -> wpTextbox1 var f=document.getElementById('wpTextbox1'); if(f) f.rows=r.toString(); } function url_query() { var field, value, TF; var address = unescape(location.href); var fields = (address.slice(address.indexOf('?')+1,address.length)).split('&'); for(var i=0; i<fields.length; ++i){ field = fields[i].split('=')[0].toLowerCase(); value = fields[i].split('=')[1]; TF = !value || value=='0' || value.toLowerCase=='f' || value.toLowerCase=='false' ? 0 : 1; if(value===undefined || !value) continue; if(field=='kbd') { // 기준 자판 if(value.toUpperCase()=='QWERTY' || value.toUpperCase()=='QWERTZ' || value.toUpperCase()=='AZERTY') ohiChange_KBD_type(value.toUpperCase()); } else if(field=='en') { // 영문 자판 ohiChange('En',value.toLowerCase()); } else if(field=='ko' || field=='k2' || field=='k3') { // 한글 자판 ohiChange('Ko',value.toLowerCase()); } else if(field=='statusbar') { // 오른쪽 아래에 보람줄 나타내기 setTimeout(function(){show_ohiStatusBar(TF);}, 250); } else if(field=='sign_ext') { // 기호 확장 ohiChange_enable_sign_ext(TF); } else if(field=='double_final_ext' || field=='df_ext') { // 겹받침 확장 ohiChange_enable_double_final_ext(TF); } else if(field=='sl' || field=='square layout') { option.square_layout = TF; } else if(field=='normal_typing' || field=='nt') { // 모아치기 자판을 이어치기로 쓰기 option.force_normal_typing = TF; } else if(field=='ncr') { // HTML 문자 참조 보이기 option.NCR = TF; } else if(field=='ncr_only_cgg') { // 첫가끝 조합형 한글만 HTML 문자 참조로 바꾸어 보이기 converting_option.convert_only_NFD_hangeul_encoding_in_NCR_text = TF; } else if(field=='y') { // 신세벌식 자판으로 옛한글 겹낱자 조합하기 option.enable_old_hangeul_input = TF; ohiChange_enable_old_hangeul_input(); } else if(field=='hcj') { // 옛한글 자판을 쓸 때에 낱자를 호환용 한글 자모(Hangul Compatibility Jamo)로 넣기 option.use_hangeul_compatibility_jamo_when_entering_old_hangeul = TF; } else if(field=='diph' || field=='diphthong') { // 신세벌식 자판으로 옛한글을 조합할 때 오른쪽 아랫글 자리에서 홀소리를 넣을지 option.enable_Sin3_diphthong_key = TF; } else if(field=='pw' || field=='phonemic_writing') { // 풀어쓰기 option.phonemic_writing = TF; } else if(field=='abbr' || field=='abbreviation') { // 이어치기 자판에서 줄임말 기능 쓰기 option.abbreviation = TF; } else if(field=='cc' || field=='convenience_combination') { // 입력 편의를 높이는 한글 편의 조합 쓰기 (조합표가 지정되어 있을 때) option.convenience_combination = TF; } else if(field=='sun' || field=='sunalae') { // 두벌식 자판 순아래 조합 option.sunalae = TF; } else if(field == 'row') { // 글상자(textarea)의 줄 수 setTimeout(function(){inputText_rows(value);},250); } else if(field == 'sq') { // 가지런한 배열표로 나타내기 option.square_layout = TF; } } } function tableKey_press(key) { var shift1 = document.getElementById('key41'); var shift2 = document.getElementById('key52'); if(!option.show_layout || !shift1) return; shift1.className = shift1.className.substr(0,2); shift2.className = shift2.className.substr(0,2); var layout_name = current_layout_info.type_name; if(key==188) key=44; // , 자리 글쇠 if(key==190) key=46; // . 자리 글쇠 if(key==222) key=39; // ' 자리 글쇠 if(key==219) key=91; // [ 자리 글쇠 if(key==221) key=93; // ] 자리 글쇠 if(key==220) key=92; // \ 자리 글쇠 if(key==173) key=45; // - 자리 글쇠 if(key==191) key=47; // / 자리 글쇠 if(key==192) key=96; // ` 자리 글쇠 if(key==16 || current_layout_info.type_name=='4t-1985'&&shift_lock) { shift1.className += ' pressed'; shift2.className += ' pressed'; } var key_td; for(j=0;j<dkey.length;++j) { if(j==41 || j==52) continue; key_td = document.getElementById('key'+j); key_td.className = key_td.className.replace(/ clicked| pressed/,''); if(key==dkey[j] || key==ukey[j] || (layout_name.substr(0,3)=='3m-' && !option.force_normal_typing && (pressed_keys.indexOf(dkey[j])>=0 || pressed_keys.indexOf(ukey[j])>=0))) { key_td.className += ' pressed'; } if(key==ukey[j] && key!=dkey[j]) { shift1.className += ' pressed'; shift2.className += ' pressed'; } } } function tableKey_clicked(e, key_num, dk, uk){ inputText_focus(); // 수정됨: inputText -> wpTextbox1 var key, f = document.getElementById('wpTextbox1'); var n=f.nodeName||f.tagName; if(!f || n!='TEXTAREA') return false; KE=ohi_KE.substr(0,2); var shiftlock = document.getElementById('key28'); var shift1 = document.getElementById('key41'); var shift2 = document.getElementById('key52'); if(dk==20) { // 배열표에서 Shift Lock이 눌렸을 때 if(!shiftlock_click) { shiftlock.style.backgroundColor = 'orange'; shiftlock_click = 1; } else { shiftlock.style.backgroundColor = ''; shiftlock_click = 0; } } if(dk==16 && !shift_click) { // 배열표에서 윗글쇠가 눌렸을 때 shift_click = 1; shift1.style.backgroundColor = 'orange'; shift2.style.backgroundColor = 'orange'; return; } if((dk==32 || dk==13 || dk==9) && !shift_click) { // 사이띄개(32), 줄바꾸개(13), Tab(9) complete_hangeul_syllable(f); esc_ext_state(); ohiInsert(f,0,dk); return; } if(dk==8 && !shift_click) { // Backspace if(option.abbreviation && prev_cursor_position>=0) { // 이어치기 자판으로 줄임말을 넣은 뒤 ohiHangeul_moa_backspace(f,e); return false; } if(ohiHangeul_backspace(f,e)) return; ohiBackspace(f); inputText_focus(); esc_ext_state(); return; } if(dk==-1) { // 기준 자판 바꾸기 단추 ohiChange_KBD_type(); inputText_focus(); } if(dk==-2) { // 영문 자판 바꾸기 단추 ohiChange_between_same_type('En'); } if(dk==-3) { // 한글 자판 바꾸기 단추 ohiChange_between_same_type('Ko'); inputText_focus(); } if(dk==-11) { // 3벌식 자판 바꾸기 단추 ohiChange_between_same_type('K3'); inputText_focus(); } if(dk==-12) { // 2벌식 자판 바꾸기 단추 ohiChange_between_same_type('K2'); } if(dk==-13) { // 한·영 상태 바꾸기 단추 ohiChange_KE(); inputText_focus(); } key = (shift_click+shiftlock_click)%2 ? uk : dk; if(ohi_KE.substr(0,2)=='En' && key>32 && key<127) ohiRoman(f,0,key); if(ohi_KE.substr(0,2)!='En' && key>32 && key<127) { if(document.selection && document.selection.createRange().text.length!=1) ohiInsert(f,0,0); if(KE=='Ko') { if(current_layout_info.type_name.substr(0,2)=='2-') ohiHangeul2(f,e,key); else if(!ohiHangeul3_abbreviation(f,key)) ohiHangeul3(f,e,key); } } for(var j=0;j<dkey.length;++j) { var key_td =document.getElementById('key'+j); key_td.className = key_td.className.replace(/ clicked| pressed/g,''); } if(dk!=16 && dk!=20) document.getElementById('key'+key_num).className += ' clicked'; shift_click = 0; shift1.style.backgroundColor = ''; shift2.style.backgroundColor = ''; } function ohi_code_tables() { var i; ohi_cheos = [/*ㄱ*/128,/*ㄲ*/129,/*ㄴ*/131,/*ㄷ*/134,/*ㄸ*/135,/*ㄹ*/136,/*ㅁ*/144,/*ㅂ*/145,/*ㅃ*/146,/*ㅅ*/148,/*ㅆ*/149,/*ㅇ*/150,/*ㅈ*/151,/*ㅉ*/152,/*ㅊ*/153,/*ㅋ*/154,/*ㅌ*/155,/*ㅍ*/156,/*ㅎ*/157]; ohi_ga = [/*ㅏ*/66,/*ㅐ*/67,/*ㅑ*/68,/*ㅒ*/69,/*ㅓ*/70,/*ㅔ*/71,/*ㅕ*/72,/*ㅖ*/73,/*ㅗ*/74,/*ㅘ*/75,/*ㅙ*/76,/*ㅚ*/77,/*ㅛ*/78,/*ㅜ*/79,/*ㅝ*/80,/*ㅞ*/81,/*ㅟ*/82,/*ㅠ*/83,/*ㅡ*/84,/*ㅢ*/85,/*ㅣ*/86]; ohi_ggeut = [/*ㄱ*/1,/*ㄲ*/2,/*ㄳ*/3,/*ㄴ*/4,/*ㄵ*/5,/*ㄶ*/6,/*ㄷ*/7,/*ㄹ*/9,/*ㄺ*/10,/*ㄻ*/11,/*ㄼ*/12,/*ㄽ*/13,/*ㄾ*/14,/*ㄿ*/15,/*ㅀ*/16, /*ㅁ*/17,/*ㅂ*/18,/*ㅄ*/20,/*ㅅ*/21,/*ㅆ*/22,/*ㅇ*/23,/*ㅈ*/24,/*ㅊ*/26,/*ㅋ*/27,/*ㅌ*/28,/*ㅍ*/29,/*ㅎ*/30]; ohi_hangeul_phoneme = ohi_cheos.concat(ohi_ga,ohi_ggeut); ohi_hotbadchim = [/*ㄱ*/1,/*ㄴ*/4,/*ㄷ*/7,/*ㄹ*/9,/*ㅁ*/17,/*ㅂ*/18,/*ㅅ*/21,/*ㅇ*/23,/*ㅈ*/24,/*ㅊ*/26,/*ㅋ*/27,/*ㅌ*/28,/*ㅍ*/29,/*ㅎ*/30]; unicode_modern_hotbadchim = [/*ㄱ*/0x11A8,/*ㄴ*/0x11AB,/*ㄷ*/0x11AE,/*ㄹ*/0x11AF,/*ㅁ*/0x11B7,/*ㅂ*/0x11B8,/*ㅅ*/0x11BA,/*ㅇ*/0x11BC,/*ㅈ*/0x11BD,/*ㅊ*/0x11BE,/*ㅋ*/0x11BF,/*ㅌ*/0x11C0,/*ㅍ*/0x11C1,/*ㅎ*/0x11C2]; compatibility_cheos = [/*ㄱ*/0x3131,/*ㄲ*/0x3132,/*ㄴ*/0x3134,/*ㄵ*/0x3135,/*ㄶ*/0x3136,/*ㄷ*/0x3137,/*ㄸ*/0x3138,/*ㄹ*/0x3139,/*ㄺ*/0x313A,/*ㄻ*/0x313B,/*ㄼ*/0x313C,/*ㄽ*/0x313D,/*ㅀ*/0x3140, /*ㅁ*/0x3141,/*ㅂ*/0x3142,/*ㅃ*/0x3143,/*ㅄ*/0x3144,/*ㅅ*/0x3145,/*ㅆ*/0x3146,/*ㅇ*/0x3147,/*ㅈ*/0x3148,/*ㅉ*/0x3149,/*ㅊ*/0x314A,/*ㅋ*/0x314B,/*ㅌ*/0x314C,/*ㅍ*/0x314D,/*ㅎ*/0x314E, /*ㅥ*/0x3165,/*ㅦ*/0x3166,/*ㅧ*/0x3167,/*ㅪ*/0x316A,/*ㅮ*/0x316E,/*ㅯ*/0x316F,/*ㅱ*/0x3171,/*ㅲ*/0x3172,/*ㅳ*/0x3173,/*ㅴ*/0x3174,/*ㅵ*/0x3175,/*ㅶ*/0x3176,/*ㅷ*/0x3177,/*ㅸ*/0x3178,/*ㅹ*/0x3179,/*ㅺ*/0x317A,/*ㅻ*/0x317B,/*ㅼ*/0x317C,/*ㅽ*/0x317D,/*ㅾ*/0x317E, /*ㅿ*/0x317F,/*ㆀ*/0x3180,/*ㆁ*/0x3181,/*ㆄ*/0x3184,/*ㆅ*/0x3185,/*ㆆ*/0x3186]; compatibility_modern_cheos = [0x3131,0x3132,0x3134,0x3137,0x3138,0x3139,0x3141,0x3142,0x3143,0x3145,0x3146,0x3147,0x3148,0x3149,0x314A,0x314B,0x314C,0x314D,0x314E, 0x317F,0x3181,0x3186]; i=0x314F; while(i<=0x3163) compatibility_ga.push(i++); compatibility_ga.push(0x318D); compatibility_ggeut = [0x3131,0x3132,0x3133,0x3134,0x3135,0x3136,0x3137,0x3139,0x313A,0x313B,0x313C,0x313D,0x313E,0x313F,0x3140,0x3141,0x3142,0x3144,0x3145,0x3146,0x3147,0x3148,0x314A,0x314B,0x314C,0x314D,0x314E, 0x317F,0x3181,0x3186]; compatibility_dah = [], compatibility_modern_dah = [], compatibility_hol = [], compatibility_modern_hol = []; i=0x3131; while(i<=0x314E) {compatibility_dah.push(i); compatibility_modern_dah.push(i++);} i=0x3165; while(i<=0x3186) compatibility_dah.push(i++); i=0x314F; while(i<=0x3163) {compatibility_hol.push(i); compatibility_modern_hol.push(i++);} i=0x3187; while(i<=0x318E) compatibility_hol.push(i++); compatibility_hangeul_phoneme = compatibility_dah.concat(compatibility_hol); compatibility_modern_hangeul_phoneme = compatibility_modern_dah.concat(compatibility_modern_hol); compatibility_cheos_to_NFD_hotdah = [/*ㄱ*/[0x1100],/*ㄲ*/[0x1100,0x1100],/*ㄴ*/[0x1102],/*ㄵ*/[0x1102,0x110C],/*ㄶ*/[0x1102,0x1112],/*ㄷ*/[0x1103],/*ㄸ*/[0x1103,0x1103],/*ㄹ*/[0x1105],/*ㄺ*/[0x1105,0x1100],/*ㄻ*/[0x1105,0x1106],/*ㄼ*/[0x1105,0x1107],/*ㄽ*/[0x1105,0x1109],/*ㅀ*/[0x1105,0x1112], /*ㅁ*/[0x1106],/*ㅂ*/[0x1107],/*ㅃ*/[0x1107,0x1107],/*ㅄ*/[0x1107,0x1109],/*ㅅ*/[0x1109],/*ㅆ*/[0x1109,0x1109],/*ㅇ*/[0x110B],/*ㅈ*/[0x110C],/*ㅉ*/[0x110C,0x110C],/*ㅊ*/[0x110E],/*ㅋ*/[0x110F],/*ㅌ*/[0x1110],/*ㅍ*/[0x1111],/*ㅎ*/[0x1112], /*ㅥ*/[0x1102,0x1102],/*ㅦ*/[0x1102,0x1103],/*ㅧ*/[0x1102,0x1109],/*ㅪ*/[0x1105,0x1103],/*ㅮ*/[0x1106,0x1107],/*ㅯ*/[0x1106,0x1109],/*ㅱ*/[0x1106,0x110B],/*ㅲ*/[0x1107,0x1100],/*ㅳ*/[0x1107,0x1103],/*ㅴ*/[0x1107,0x1109,0x1100],/*ㅵ*/[0x1107,0x1109,0x1103],/*ㅶ*/[0x1107,0x110C],/*ㅷ*/[0x1107,0x1110],/*ㅸ*/[0x1107,0x110B],/*ㅹ*/[0x1107,0x1107,0x110B],/*ㅺ*/[0x1109,0x1100],/*ㅻ*/[0x1109,0x1102],/*ㅼ*/[0x1109,0x1103],/*ㅽ*/[0x1109,0x1107],/*ㅾ*/[0x1109,0x110C], /*ㅿ*/[0x1140],/*ㆀ*/[0x110B,0x110B],/*ㆁ*/[0x114C],/*ㆄ*/[0x1111,0x110B],/*ㆅ*/[0x1112,0x1112],/*ㆆ*/[0x1159]]; compatibility_dah_to_NFD_hotbadchim = [/*ㄱ*/[0x11A8],/*ㄲ*/[0x11A8,0x11A8],/*ㄳ*/[0x11A8,0x11BA],/*ㄴ*/[0x11AB],/*ㄵ*/[0x11AB,0x11BD],/*ㄶ*/[0x11AB,0x11C2],/*ㄷ*/[0x11AE],/*ㄸ*/[0x11AE,0x11AE],/*ㄹ*/[0x11AF],/*ㄺ*/[0x11AF,0x11A8],/*ㄻ*/[0x11AF,0x11B7],/*ㄼ*/[0x11AF,0x11B8],[0x11AF,0x11BA],[0x11AF,0x11C0],[0x11AF,0x11C1],[0x11AF,0x11C2],[0x11AE],[0x11B8],[0x11B8,0x11B8],[0x11B8,0x11BA],[0x11BA],[0x11BA,0x11BA],[0x11BC],[0x11BD],[0x11BD,0x11BD],[0x11BE],[0x11BF],[0x11C0],[0x11C1],[0x11C2], /*ㅥ*/[0x11AB,0x11AB],/*ㅦ*/[0x11AB,0x11AE],/*ㅧ*/[0x11AB,0x11BA],/*ㅨ*/[0x11AB,0x11EB],/*ㅩ*/[0x11AF,0x11A8,0x11BA],/*ㅪ*/[0x11AF,0x11AE],/*ㅫ*/[0x11AF,0x11B8,0x11BA],/*ㅬ*/[0x11AF,0x11EB],/*ㅭ*/[0x11AF,0x11F9],/*ㅮ*/[0x11B7,0x11B8],/*ㅯ*/[0x11B7,0x11BA],/*ㅰ*/[0x11B7,0x11EB],/*ㅱ*/[0x11B7,0x11BC],/*ㅲ*/[0x11B8,0x11A8],/*ㅳ*/[0x11B8,0x11AE],/*ㅴ*/[0x11B8,0x11BA,0x11A8],/*ㅵ*/[0x11B8,0x11BA,0x11AE],/*ㅶ*/[0x11B8,0x11BD],/*ㅷ*/[0x11B8,0x11C0],/*ㅸ*/[0x11B8,0x11BC],/*ㅹ*/[0x11B8,0x11B8,0x11BC],/*ㅺ*/[0x11BA,0x11A8],/*ㅻ*/[0x11BA,0x11AB],/*ㅼ*/[0x11BA,0x11AE],/*ㅽ*/[0x11BA,0x11B8],/*ㅾ*/[0x11BA,0x11BD],/*ㅿ*/[0x11EB],/*ㆀ*/[0x11BC,0x11BC],/*ㆁ*/[0x11F0],/*ㆂ*/[0x11F0,0x11BA],/*ㆃ*/[0x11F0,0x11EB],/*ㆄ*/[0x11C1,0x11BC],/*ㆅ*/[0x11C2,0x11C2],/*ㆆ*/[0x11F9]]; compatibility_hol_to_NFD_hol = [0x1161,0x1162,0x1163,0x1164,0x1165,0x1166,0x1167,0x1168,0x1169,0x116A,0x116B,0x116C,0x116D,0x116E,0x116F,0x1170,0x1171,0x1172,0x1173,0x1174,0x1175, /*ㆇ*/0x1184,/*ㆈ*/0x1185,/*ㆉ*/0x1188,/*ㆊ*/0x1191,/*ㆋ*/0x1192,/*ㆌ*/0x1194,/*ㆍ*/0x119E,/*ㆎ*/0x11A1]; compatibility_hol_to_NFD_hothol = [[0x1161],[0x1162],[0x1163],[0x1164],[0x1165],[0x1166],[0x1167],[0x1168],[0x1169],[0x1169,0x1161],[0x1169,0x1162],[0x1169,0x1175],[0x116D],[0x116E],[0x116E,0x1165],[0x116E,0x1166],[0x116E,0x1175],[0x1172],[0x1173],[0x1173,0x1175],[0x1175], /*ㆇ*/[0x116D,0x1163],/*ㆈ*/[0x116D,0x1164],/*ㆉ*/[0x116D,0x1175],/*ㆊ*/[0x1172,0x1167],/*ㆋ*/[0x1172,0x1168],/*ㆌ*/[0x1172,0x1175],/*ㆍ*/[0x119E],/*ㆎ*/[0x119E,0x1175]]; halfwidth_cheos = [0xFFA1,0xFFA2,0xFFA4,0xFFA7,0xFFA8,0xFFA9,0xFFB1,0xFFB2,0xFFB3,0xFFB5,0xFFB6,0xFFB7,0xFFB8,0xFFB9,0xFFBA,0xFFBB,0xFFBC,0xFFBD,0xFFBE]; for(i=0;i<4;++i) for(j=0;j<(i==3?3:6);++j) halfwidth_ga.push(0xFFC2+i*8+j); halfwidth_ggeut = [0xFFA1,0xFFA2,0xFFA3,0xFFA4,0xFFA5,0xFFA6,0xFFA7,0xFFA9,0xFFAA,0xFFAB,0xFFAC,0xFFAD,0xFFAE,0xFFAF,0xFFB0, 0xFFB1,0xFFB2,0xFFB4,0xFFB5,0xFFB6,0xFFB7,0xFFB8,0xFFBA,0xFFBB,0xFFBC,0xFFBD,0xFFBE]; halfwidth_hangeul_phoneme = halfwidth_cheos.concat(halfwidth_ga, halfwidth_ggeut); i=0x1100; while(i<=0x115E) unicode_cheos.push(i++); i=0xA960; while(i<=0xA97C) unicode_cheos.push(i++); i=0x1161; while(i<=0x11A7) unicode_ga.push(i++); i=0xD7B0; while(i<=0xD7C6) unicode_ga.push(i++); i=0x11A8; while(i<=0x11FF) unicode_ggeut.push(i++); i=0xD7CB; while(i<=0xD7FB) unicode_ggeut.push(i++); unicode_ggeut_to_cheos = [0x1100,0x1101,0,0x1102,0,0,0x1103,0x1105,0,0,0,0,0,0,0,0x1106,0x1107,0,0x1109,0x110A,0x110B,0x110C,0x110E,0x110F,0x1110,0x1111,0x1112, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x111D,0,0,0,0x112B,0,0,0,0,0x1140,0,0,0,0,0x114C,0,0,0,0x1157,0,0,0,0,0x1159,0,0,0,0,0,0, // ~ 0x11F9 0,0,0x1104,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x1108,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x110D,0,0]; // 0xD7CB ~ 0xD7FB unicode_cheos_to_ggeut = [0x11A8,0x11A9,0x11AB,0x11AE,0xD7CD,0x11AF,0x11B7,0x11B8,0xD7E6,0x11BA,0x11BB,0x11BC,0x11BD,0xD7F9,0x11BE,0x11BF,0x11C0,0x11C1,0x11C2, 0,0,0,0,0,0,0,0,0xD7DD,0,0x11E2,0,0,0,0,0,0,0,0,0,0,0,0,0,0x11E6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x11EB,0,0,0,0,0,0,0,0,0,0,0,0x11F0,0,0,0,0,0,0,0,0,0,0,0,0,0x11F9]; unicode_NFD_hangeul_phoneme = unicode_cheos.concat(unicode_ga, unicode_ggeut); // 첫가끝 조합형 한글 낱자 unicode_NFD_hangeul_filler = [0x115F,0x1160]; // 첫가끝 조합형 첫소리·가운뎃소리 채움 문자 unicode_NFD_hangeul_code = unicode_NFD_hangeul_phoneme.concat(unicode_NFD_hangeul_filler); // 첫가끝 조합형에서 한글 낱내자를 나타내는 데에 쓰이는 낱자/채움 부호값들 unicode_NFD_hangeul_sidedot = [0x302E,0x302F]; // 옛한글에서 성조를 나타내는 방점 i=0x1100; while(i<=0x1112) unicode_modern_cheos.push(i++); i=0x1161; while(i<=0x1175) unicode_modern_ga.push(i++); i=0x11A8; while(i<=0x11C2) unicode_modern_ggeut.push(i++); unicode_modern_hangeul_phoneme = unicode_modern_cheos.concat(unicode_modern_ga, unicode_modern_ggeut); // 유니코드 조합형 요즘한글 낱자 unicode_non_combined_cheos = [0x1100,0x1102,0x1103,0x1105,0x1106,0x1107,0x1109,0x110B,0x110C,0x110E,0x110F,0x1110,0x1111,0x1112]; unicode_non_combined_ga = [0x1161,0x1162,0x1164,0x1165,0x1166,0x1167,0x1168,0x1169,0x116D,0x116E,0x1172,0x1173,0x1175,0x119E]; unicode_non_combined_ggeut = [0x11A8,0x11AB,0x11AE,0x11AF,0x11B7,0x11B8,0x11BA,0x11BC,0x11BD,0x11BE,0x11BF,0x11C0,0x11C1,0x11C2]; unicode_non_combined_phoneme = unicode_non_combined_cheos.concat(unicode_non_combined_ga, unicode_non_combined_ggeut); var han_ext_tag = '<span style="margin:0;padding:0;background:black;color:#fff;letter-spacing:0px;font-size:0.7em;">한글</span>'; special_chars = [-1, 0x08, 0x0D, 0x1B, 0x1160]; special_chars_string = [han_ext_tag, '⌫', '⏎', '🄴', '🄵']; // 쿼티를 기준으로 한 화상 배열표의 아랫글 자리 부호값 dkey = [96,49,50,51,52,53,54,55,56,57,48,45,61,8, 9,113,119,101,114,116,121,117,105,111,112,91,93,92, 20,97,115,100,102,103,104,106,107,108,59,39,13, 16,122,120,99,118,98,110,109,44,46,47,16, -1,-2,-3,32,-13,-12,-11]; // 쿼티를 기준으로 한 화상 배열표의 윗글 자리 부호값 ukey = [126,33,64,35,36,37,94,38,42,40,41,95,43,8, 9,81,87,69,82,84,89,85,73,79,80,123,125,124, 20,65,83,68,70,71,72,74,75,76,58,34,13, 16,90,88,67,86,66,78,77,60,62,63,16, -1,-2,-3,32,-13,-12,-11]; shift_table = [ 0x31, /* 0x21 exclam: 1 */ 0x27, /* 0x22 quotedbl: apostrophe */ 0x33, /* 0x23 numbersign: 3 */ 0x34, /* 0x24 dollar: 4 */ 0x35, /* 0x25 percent: 5 */ 0x37, /* 0x26 ampersand: 7 */ 0x22, /* 0x27 apostrophe: quotatioin mark */ 0x39, /* 0x28 parenleft */ 0x30, /* 0x29 parenright */ 0x38, /* 0x2A asterisk: 8 */ 0x3D, /* 0x2B plus: equal */ 0x3C, /* 0x2C comma: less */ 0x5F, /* 0x2D minus: underscore */ 0x3E, /* 0x2E period: greater */ 0x3F, /* 0x2F slash: question */ 0x29, /* 0x30 0: parenright */ 0x21, /* 0x31 1: exclam */ 0x40, /* 0x32 2: at */ 0x23, /* 0x33 3: numbersign */ 0x24, /* 0x34 4: dollar */ 0x25, /* 0x35 5: percent */ 0x5E, /* 0x36 6: asciicircum */ 0x26, /* 0x37 7: ampersand */ 0x2A, /* 0x38 8: asterisk */ 0x28, /* 0x39 9: parenleft */ 0x3B, /* 0x3A colon: semicolon */ 0x3A, /* 0x3B semicolon: colon */ 0x2C, /* 0x3C less: comma */ 0x2B, /* 0x3D equal: plus */ 0x2E, /* 0x3E greater: period */ 0x2F, /* 0x3F question: slash */ 0x32, /* 0x40 at: 2 */ 0x61, /* 0x41 A: a */ 0x62, /* 0x42 B: b */ 0x63, /* 0x43 C: c */ 0x64, /* 0x44 D: d */ 0x65, /* 0x45 E: e */ 0x66, /* 0x46 F: f */ 0x67, /* 0x47 G: g */ 0x68, /* 0x48 H: h */ 0x69, /* 0x49 I: i */ 0x6A, /* 0x4A J: j */ 0x6B, /* 0x4B K: k */ 0x6C, /* 0x4C L: l */ 0x6D, /* 0x4D M: m */ 0x6E, /* 0x4E N: n */ 0x6F, /* 0x4F O: o */ 0x70, /* 0x50 P: p */ 0x71, /* 0x51 Q: q */ 0x72, /* 0x52 R: r */ 0x73, /* 0x53 S: s */ 0x74, /* 0x54 T: t */ 0x75, /* 0x55 U: u */ 0x76, /* 0x56 V: v */ 0x77, /* 0x57 W: w */ 0x78, /* 0x58 X: x */ 0x79, /* 0x59 Y: y */ 0x7A, /* 0x5A Z: z */ 0x7B, /* 0x5B bracketleft: braceleft */ 0x7C, /* 0x5C backslash: bar */ 0x7D, /* 0x5D bracketright: braceright */ 0x36, /* 0x5E asciicircum: 6 */ 0x2D, /* 0x5F underscore: minus */ 0x7E, /* 0x60 quoteleft: asciitilde */ 0x41, /* 0x61 a: A */ 0x42, /* 0x62 b: B */ 0x43, /* 0x63 c: C */ 0x44, /* 0x64 d: D */ 0x45, /* 0x65 e: E */ 0x46, /* 0x66 f: F */ 0x47, /* 0x67 g: G */ 0x48, /* 0x68 h: H */ 0x49, /* 0x69 i: I */ 0x4A, /* 0x6A j: J */ 0x4B, /* 0x6B k: K */ 0x4C, /* 0x6C l: L */ 0x4D, /* 0x6D m: M */ 0x4E, /* 0x6E n: N */ 0x4F, /* 0x6F o: O */ 0x50, /* 0x70 p: P */ 0x51, /* 0x71 q: Q */ 0x52, /* 0x72 r: R */ 0x53, /* 0x73 s: S */ 0x54, /* 0x74 t: T */ 0x55, /* 0x75 u: U */ 0x56, /* 0x76 v: V */ 0x57, /* 0x77 w: W */ 0x58, /* 0x78 x: X */ 0x59, /* 0x79 y: Y */ 0x5A, /* 0x7A z: Z */ 0x5B, /* 0x7B braceleft: bracketleft */ 0x5C, /* 0x7C bar: backslash */ 0x5D, /* 0x7D braceright: bracketright */ 0x60 /* 0x7E asciitilde: quoteleft */ ]; } // ohi_code_tables() // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf if(!Array.prototype.indexOf) { Array.prototype.indexOf = function(searchElement, fromIndex) { var k; if(!this) throw new TypeError('"this" is null or not defined'); var O=Object(this); var len=O.length >>> 0; if(len===0) return -1; var n= +fromIndex || 0; if(Math.abs(n) === Infinity) n=0; if(n>=len) return -1; k = Math.max(n>=0 ? n : len-Math.abs(n), 0); while(k<len) { var kValue; if (k in O && O[k] === searchElement) return k; k++; } return -1; }; } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr if('ab'.substr(-1) != 'b') { String.prototype.substr = function(substr) { return function(start, length) { if(start<0) start=this.length+start; return substr.call(this, start, length); } }(String.prototype.substr); } ohi_code_tables(); browser_detect(); // 미디어위키 훅을 사용하여 편집 화면 로드 완료 시 OHI 초기화 및 UI 삽입 $(function() { mw.hook('wikipage.editform').add(function() { ohiStart(); // 미디어위키 Edittools 요소 찾기 var $editTools = $('.mw-editTools').length ? $('.mw-editTools') : $('#editpage-specialchars'); // OHI UI 뼈대가 아직 없고 편집도구가 존재한다면 HTML 삽입 if ($editTools.length && $('#ohi_wrap').length === 0) { var uiHtml = '<div id="ohi_wrap" style="margin: 10px 0; background:#f8f9fa; border:1px solid #a2a9b1; padding:10px;">' + '<div id="ohi_menu" style="border-bottom: 1px solid #ccc; padding-bottom: 5px;">' + '<strong style="font-size:1.1em; color:#202122; margin-right: 15px;">화상 한글 자판 (OHI)</strong>' + '<div class="this" id="menu1" style="display:inline-block; font-size: 12px; font-weight: bold; padding: 2px 8px; border: 1px solid #c8ccd1; background-color: #eaecf0; cursor: pointer;">자판 배열표</div>' + '</div>' + '<div id="ohi_content" style="padding-top: 10px;">' + '<div id="top_options" style="text-align: left; margin-bottom: 5px;"></div>' + '<div id="middle_options" style="text-align: left; margin-bottom: 5px;"></div>' + '<div id="keyboardLayout" style="text-align: center;"></div>' + '<div id="bottom_options" style="text-align: left;"></div>' + '</div>' + '<div id="converting_options">' + '<div id="NCR_options" class="options"></div><div id="NCR_text" class="converting_text"></div>' + '<div id="direct_typing_text_options" class="options"></div><div id="direct_typing_text" class="converting_text"></div>' + '<div id="reverse_direct_typing_text_options" class="options"></div><div id="reverse_direct_typing_text" class="converting_text"></div>' + '</div>' + '</div>'; $editTools.after(uiHtml); } // 자판 배열표 렌더링 함수 실행 if (typeof show_keyboard_layout === 'function') { show_keyboard_layout(1); } }); }); 1opetcbcehejws47d33shlmnjgt9rjt User:Johannes Richter (WMDE)/test 2 167021 740057 734966 2026-05-01T18:38:38Z Johannes Richter (WMDE) 61456 test 740057 wikitext text/x-wiki According to scientists, the Sun is pretty big.<ref name="Miller" details="[[#CITEREFvan_Riemsdijk1994|van Riemsdijk, 1994]], p. 41">E. Miller, ''The Sun''. New York: Academic Press, 2005</ref> In fact, it is very big. Take their word for it.<ref name="Miller" details="Page 48." /> Don't look directly at the sun!<ref name="Miller" details="Page 23." /> {{sfn|van Riemsdijk|1994|p=41}} = <ref></ref> [[#CITEREFMiller89|Miller, 1989]], p 2 == Bibliography == *{{cite book |last=van Riemsdijk |first=John T. |title=Compound Locomotives: An International Survey |year=1994 |publisher=Atlantic Books |location=Penryn |isbn=0-906899-61-3 }} == References == {{Reflist}} 7p57gct2hqsebfkrildwctuva5hrd0w User:Ponor/wAwB-worker.js 2 171632 740037 739044 2026-05-01T16:31:57Z Ponor 47975 +mediawiki.page.gallery.styles 740037 javascript text/javascript /* * wAwB – An in-browser application for automated editing of wiki pages. * Features: customizable regex or JavaScript search-and-replace rules, * custom JavaScript pre/post-processing functions and function libraries, * granular protection or targeting of different parts of wikitext, * a full-fledged CodeMirror editor, and options to move, delete, and protect pages. * Author: [[User:Ponor]] * Documentation: [[User:Ponor/wAwB]] * License: GNU General Public License (GPL) */ //<nowiki> mw.loader.using([ 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'mediawiki.api', 'mediawiki.diff.styles', 'mediawiki.util', 'mediawiki.page.gallery.styles', 'oojs-ui.styles.icons-content', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-moderation', 'oojs-ui.styles.icons-editing-core', 'oojs-ui.styles.icons-editing-advanced' ]).then(function() { // ===================================================================== // 1. STATE & CONFIGURATION // ===================================================================== var SCRIPT_TIMEOUT_MS = window.wa_timeout || 5000; var FETCH_SAFETY_LIMIT = window.wa_fetchLimit || 10000; var APP_NAME = "wAwB"; var DO_TAG = false; var SUMMARY_SUFFIX = window.wa_suffix || " [[:en:User:Ponor/wAwB| #wAwB]]"; var APP_VERSION = "0.6"; var DOC_URL = window.wa_docUrl || "https://en.wikipedia.org/wiki/User:Ponor/wAwB"; document.title = window.wa_editIn || "Edit in wAwB"; var PERMS = { canSave: false, allowBot: false, saveDelay: 0 }; var IS_ADMIN = mw.config.get('wgUserGroups').includes('sysop'); var CAN_MOVE = IS_ADMIN || mw.config.get('wgUserGroups').includes('extendedmover') || mw.config.get('wgUserGroups').includes('filemover') || mw.config.get('wgUserGroups').includes('pagemover'); var WIKI = mw.config.get('wgDBname'); var SAVED_RUN = 0; var SAVED_SESSION = 0; var currentPageExists = false; var isRunning = false; var isFetching = false; var currentTitle = null; var currentVars = {}; var currentLibrary = { name: null, code: null }; var originalWikitext = ""; var baseRevId = 0; var currentViewMode = 'diff'; var autoSaveTimer = null; var propNamesLoaded = false; var hasNewSources = false; var currentHeightMode = 1; // 0=25%, 1=45% (default), 2=72% var heightValues = ['25%', '45%', '72%']; // EXTERNAL RULES STATE var wikiTypos = []; var localTypos = []; // LOADING FLAG var isLoadingProject = false; // NAMESPACE ALIASES var nsIds = mw.config.get('wgNamespaceIds'); var catAliases = [], fileAliases = []; for (var key in nsIds) { if (nsIds[key] === 14) catAliases.push(key.replace(/_/g, ' ')); if (nsIds[key] === 6) fileAliases.push(key.replace(/_/g, ' ')); } catAliases.sort((a, b) => b.length - a.length); fileAliases.sort((a, b) => b.length - a.length); var REGEX_CAT_PFX = catAliases.map(mw.util.escapeRegExp).join('|'); var REGEX_FILE_PFX = fileAliases.map(mw.util.escapeRegExp).join('|'); // MASTER PROTECTION DEFINITIONS var PROTECTION_DEFS = [{ id: 'nowiki', isOn: true, label: 'Nowiki: <nowiki>', regex: /<nowiki>[\s\S]*?<\/nowiki>|<nowiki\s*\/>/gi }, { id: 'comments', isOn: true, label: 'Comments: <!' + '-- -->', regex: new RegExp('<!' + '--[\\s\\S]*?--' + '>', 'g') }, { id: 'headers', isOn: false, label: 'Headers: == Title ==', regex: /^==+[\s\S]+?==+\s*$/gm }, { id: 'templates', isOn: false, label: 'Templates: {{...}}', open: '{{', close: '}}', species: null, regex: null }, { id: 'tables', isOn: false, label: 'Tables: {|...|}', open: '\n{|', close: '\n|}', regex: null }, { id: 'images', isOn: false, label: 'Images: [[File:...|...|...]]', open: '[[', close: ']]', species: '(?:' + REGEX_FILE_PFX + ')\\s*:', regex: null }, { id: 'refs', isOn: true, label: 'Refs: <ref...', regex: /<ref[^>]*?\/>|<ref[^>]*?(?<!\/)>[\s\S]*?<\/ref>/gi }, { id: 'blocks', isOn: false, label: 'Blocks: math, gallery...', regex: null }, { id: 'categories', isOn: true, label: 'Categories: [[Category:...]]', regex: new RegExp('\\[\\[\\s*(' + REGEX_CAT_PFX + ')\\s*:[^\\]]+\\]\\]', 'giu') }, { id: 'files', isOn: true, label: 'File names: File:...', regex: new RegExp('(?<=\\[\\[\\s*:?(:?' + REGEX_FILE_PFX + ')\\s*:)[^|\\]]+' + '|^\\s*(?:' + REGEX_FILE_PFX + ')\\s*:([^\\][}{|\\n]{1,150}\\.(?:svg|png|jpe?g|gif|tiff|webp|xcf|mp3|midi|ogg|webm|flac|wav|mpe?g|pdf|djv))', 'gmiu') }, { id: 'targets', isOn: false, label: 'Targets of [[...|', regex: /(?<=\[\[:?)[^|\]]+?(?=\||\]\])/g }, { id: 'extlinks', isOn: true, label: 'External links: [...]', regex: /(?<=\[)(https?:\/\/|ftps?:\/\/|mailto:)[^\]]+(?=\])/gi }, { id: 'urls', isOn: true, label: 'URLs: http...', regex: /https?:\/\/[^\s<>[\]"'`()]+/gi } ]; // ===================================================================== // 2. CSS STYLES // ===================================================================== var styles = ` * { box-sizing: border-box; } #wa-root { font-family: sans-serif; height: 100vh; width: 100vw; overflow: hidden; display: flex; font-size: 14px; } #wa-left-panel { width: 400px; min-width: 400px; max-width: 400px; background: var(--background-color-base, #fff); border-right: 1px solid #c8ccd1; display: flex; flex-direction: column; z-index: 10; overflow-x: hidden; } #wa-left-panel h3 { color: #3f6fcf; text-align: center; margin: 12px 0 0 0; } #wa-username { color: #3f6fcf; text-align: center; margin: 2px 0; font-size: 92%; } #wa-content-area { flex: 1; padding: 10px 10px 100px 10px; overflow-y: auto; overflow-x: hidden; } #wa-right-panel { flex: 1; display: flex; flex-direction: column; height: 100%; background: var(--background-color-interactive, #eaecf0); overflow: hidden; } #wa-visual-output { flex: 0 0 45%; min-height: 0; overflow-y: auto; background: var(--background-color-base, #fff); padding: 20px; border-bottom: 1px solid #c8ccd1; } .wa-editor-header { flex: 0 0 40px; gap:4em; min-height: 40px; padding: 0 7px; background: var(--background-color-interactive-subtle, #f8f9fa); border-bottom: 1px solid #c8ccd1; color: var(--color-subtle, #54595d); display: flex; justify-content: space-between; align-items: center; white-space: nowrap; z-index: 10; } .wa-editor-header.wa-dirty { background: var(--background-color-warning-subtle, #fdf2d5); border-bottom: 1px solid #e6a700; } .wa-header-left { flex: 1; display: flex; align-items: center; overflow: hidden; } .wa-title-link { font-weight: bold; font-size: 1.1em; color: var(--color-progressive--focus, #36c) !important; text-decoration: none; text-overflow: ellipsis; overflow: hidden; max-width: 300px; } .wa-title-link:hover { text-decoration: underline; } .wa-header-sep { margin: 0 10px; border-right: 1px solid #ccc; height: 16px; display: inline-block; } .wa-unsaved-wrapper { display: none; align-items: center; } .wa-dirty .wa-unsaved-wrapper { display: inline-flex; } .wa-unsaved-dot { color: var(--color-destructive, #bf3c2c); font-size: 1.2em; margin-right: 5px; line-height: 1; } .wa-unsaved-text { color: var(--color-placeholder, #72777d); font-size: 0.9em; font-weight: normal; } .wa-header-center { flex: 0 0 auto; text-align: center; } .wa-status-label { font-weight: bold; font-size: 0.85em; color: var(--color-base, #202122); text-transform: uppercase; letter-spacing: 0.5px; } .wa-status-error { color: var(--color-error, #bf3c2c); } .wa-status-working { color: var(--color-progressive--focus, #36c); } .wa-header-right { flex: 1; text-align: right; font-size: 0.85em; color: var(--color-placeholder, #72777d); display: flex; justify-content: flex-end; align-items: center; gap: 8px; } .wa-info-container { margin-right: 10px; } .wa-tools-container { display: flex; align-items: center; gap: 2px; } .wa-resize-container { display: flex; flex-direction: column; justify-content: center; height: 100%; margin-left: 10px; padding-left: 5px; border-left: 1px solid #ccc; } .wa-resize-btn { cursor: pointer; color: #72777d; user-select: none; width: 20px; height: 14px; display: flex; align-items: center; justify-content: center; transition: color 0.1s ease-in-out; } .wa-resize-btn:hover { color: #36c; } .wa-resize-btn.wa-resize-disabled { color: #ccc; cursor: default; } #wa-proc-header { margin-top: 15px !important; border-bottom: none !important; cursor: default; } #wa-proc-title { font-weight: bold; padding: 10px; display: block; } #wa-proc-content { padding: 0 10px 15px 10px; } #wa-editor-area { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--background-color-base, #fff); position: relative; overflow: hidden; } #wa-editor-textarea { flex: 1; height: 100%; font-family: monospace; font-size: 13px; border: none; outline: none; padding: 10px; resize: none; width: 100%; } .cm-editor { height: 100% !important; flex: 1; } .wa-section-header { margin-top: 12px; border-bottom: 1px solid #eee; width: 100%; display: block; margin-left: 0 !important; } #wa-content-area .wa-section-header:first-child, #wa-content-area .wa-section-header.oo-ui-buttonElement-frameless:first-child { margin-top: 0; margin-left: 0 !important; } .wa-section-header > .oo-ui-buttonElement-button { text-align: left; padding: 10px 10px !important; margin: 0 !important; display: block; width: 100%; position: relative; border-left: 3px solid #3f6fcf !important; border-radius: 3px !important; background-color: transparent !important; } .wa-section-header > .oo-ui-buttonElement-button:focus { outline: none !important; } .wa-section-header .oo-ui-labelElement-label { font-weight: bold; padding-left: 0 !important; margin-left: 0 !important; color: var(--color-base, #202122); } .wa-section-header .oo-ui-indicatorElement-indicator { position: absolute; right: 10px !important; top: 50%; margin-top: -10px; left: auto !important; width: 20px; } .wa-foldable-content { display: none; padding: 10px 0; } .wa-source-options { background: var(--background-color-interactive-subtle, #f8f9fa); border: 1px solid #c8ccd1; border-top: none; padding: 8px; margin-bottom: 10px; font-size: 0.9em; } .wa-opt-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 5px; } .wa-opt-label { font-weight: bold; width: 100%; margin-bottom: 5px; color: var(--color-base, #202122); } .wa-opt-row > div { margin-top: 8px !important; margin-bottom: 8px !important; } .wa-rule-row { background: var(--background-color-interactive-subtle, #f8f9fa); border: 1px solid #c8ccd1; padding: 8px; margin-bottom: 8px; border-radius: 4px; display: flex; align-items: stretch; transition: background-color 0.3s; } .wa-rule-row.wa-highlight { background-color: var(--background-color-interactive, #eaecf0); border-color: #36c; } .wa-rule-controls { display: flex; flex-direction: column; justify-content: center; gap: 0px; padding-right: 4px; border-right: 1px solid #eee; margin-right: 8px; } .wa-rule-btn { margin: 0 !important; margin-right: 0 !important; margin-left: 0 !important; } .wa-rule-btn > .oo-ui-buttonElement-button { margin: 0 !important; } .wa-rule-content { flex: 1; min-width: 0; } .wa-rule-opt-row { display: flex; justify-content: space-between; align-items: center; margin-top: 5px; } #wa-ns-selector { width: 100%; margin-bottom: 10px; font-family: sans-serif; font-size: 0.9em; border: 1px solid #a2a9b1; } .wa-lib-dialog > .oo-ui-window-frame { width: 80vw !important; max-width: none !important; height: 80vh !important; max-height: none !important; } .wa-lib-editorwrapper { height: 100%; border: 1px solid #c8ccd1; position: relative; boxSizing: border-box; } .wa-page-list-raw textarea { font-family: monospace; font-size: 0.9em; white-space: pre; overflow-x: auto; } .wa-list-running textarea { background-color: var(--background-color-neutral-subtle, #f8f8f8) !important; color: var(--color-base, #202122) !important; } .wa-grid-container { display: flex; gap: 6px; margin-bottom: 10px; } .wa-grid-col { flex: 1; display: flex; flex-direction: column; gap: 6px; } .wa-grid-col .oo-ui-buttonWidget { width: 100%; } .wa-grid-col .oo-ui-buttonWidget .oo-ui-buttonElement-button { width: 100%; text-align: center; justify-content: center; } .wa-toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 4px; border-bottom: 1px solid #eee; padding-bottom: 4px; margin-bottom: 4px; } .wa-list-counter { margin-right: auto; font-weight: bold; color: var(--color-subtle, #54595d); font-size: 0.9em; padding-left: 5px; } .wa-project-bar { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 10px; margin: 8px 0; justify-content: center; } .wa-project-bar .oo-ui-buttonElement-button { padding-left: 36px !important; padding-right: 12px !important; font-size: 0.9em; } .wa-project-bar .oo-ui-iconElement-icon { left: 10px !important; } .wa-settings-header { font-weight: bold; color: var(--color-subtle, #54595d); margin-bottom: 8px; display: block; text-transform: uppercase; font-size: 0.85em; } .wa-setting-row { display: flex; align-items: center; margin-bottom: 6px; } .wa-bot-row { background: var(--background-color-success-subtle, #dff2eb); border: 1px solid #a5d6a7; padding: 8px; margin-bottom: 10px; border-radius: 4px; display: flex; align-items: center; justify-content: flex-start; gap: 15px; } table.diff { width: 100%; font-family: "Adwaita Mono", "Courier New", monospace } table.diff td { vertical-align: top; } table.diff tr:hover td { background-color: var(--background-color-progressive-subtle--hover, #d9e2ff); cursor: pointer; } @keyframes wa-pulse-red { 0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.4); border-color: #ff0000; } 70% { box-shadow: 0 0 0 6px rgba(255, 0, 0, 0); border-color: #ff0000; } 100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); border-color: #ff0000; } } .wa-summary-warning input { animation: wa-pulse-red 1s infinite; border-color: #ff0000 !important; } `; $('<style>').text(styles).appendTo('head'); $('body').empty(); // ===================================================================== // 3. HELPER FUNCTIONS // ===================================================================== function checkPermissions() { return new Promise(function(resolve) { var api = new mw.Api(); var projectNs = mw.config.get('wgFormattedNamespaces')[4]; var checkTitles = { 'permissions': projectNs + ':AutoWikiBrowser/CheckPageJSON', 'tag': 'MediaWiki:Tag-wAwB' }; api.get({ action: 'query', prop: 'revisions', titles: Object.values(checkTitles).join('|'), rvprop: 'content', rvslots: 'main', formatversion: 2 }).then(function(data) { var pagePerms = data.query.pages.find(p => p.title === checkTitles['permissions']); var pageTag = data.query.pages.find(p => p.title === checkTitles['tag']); DO_TAG = pageTag.missing === undefined; var userName = mw.config.get('wgUserName'); var userGroups = mw.config.get('wgUserGroups'); var isSysop = userGroups.includes('sysop'); if (!pagePerms.missing) { try { var content = pagePerms.revisions[0].slots.main.content; var json = JSON.parse(content); var inEnabledUsers = json.enabledusers && json.enabledusers.includes(userName); var inEnabledBots = json.enabledbots && json.enabledbots.includes(userName); var isBotGroup = userGroups.includes('bot'); var canSave = inEnabledUsers || inEnabledBots || isSysop; var allowBot = inEnabledBots && isBotGroup; resolve({ canSave: canSave, allowBot: allowBot, saveDelay: 0 }); } catch (e) { resolve({ canSave: false, allowBot: false, saveDelay: 0 }); } } else { var editCount = mw.config.get('wgUserEditCount'); if (editCount > 500) resolve({ canSave: true, allowBot: false, saveDelay: 20000 }); else resolve({ canSave: false, allowBot: false, saveDelay: 0 }); } }).catch(function() { resolve({ canSave: false, allowBot: false, saveDelay: 0 }); }); }); } function getUserCode(widget, globalName) { var val = widget.getValue().trim(); if (!val || val.startsWith('// Enter')) { if (window[globalName] && typeof window[globalName] === 'function') { var s = window[globalName].toString(); return s.substring(s.indexOf('{') + 1, s.lastIndexOf('}')); } return ""; } if (val.startsWith('function')) { return val.substring(val.indexOf('{') + 1, val.lastIndexOf('}')); } return val; } function normalizeLine(line) { if (!line) return null; // Pass through comments/STOP commands (trimmed) if (line.trim().startsWith('####')) return line.trim(); // Handle Title|Variables var parts = line.split('|'); var title = parts[0].trim(); if (!title) return null; // Skip if title is empty // Reassemble: Clean Title + Original Variables (preserving whitespace) var rest = parts.length > 1 ? parts.slice(1).join('|') : null; return title + (rest !== null ? '|' + rest : ''); } function getNormalizedList(text) { if (!text) return []; return text.split('\n') .map(normalizeLine) .filter(function(l) { return l !== null; }); } function getDeduplicatedList(text) { if (!text) return []; var seen = new Set(); var out = []; var lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { var clean = normalizeLine(lines[i]); if (clean && !seen.has(clean)) { seen.add(clean); out.push(clean); } } return out; } function parseTypoContent(content) { if (!content) return []; try { var $wrapper = $('<body>').html(content); var rules = []; $wrapper.find('Typo:not([disabled])').each(function() { var $t = $(this); var find = $t.attr('find'); var replace = $t.attr('replace'); if (find && replace !== undefined) { rules.push({ find: find, replace: replace, regex: true, flags: 'gmu', enabled: true, isFunc: false }); } }); return rules; } catch (e) { return []; } } // ===================================================================== // 4. UI CONSTRUCTION // ===================================================================== checkPermissions().then(function(pState) { PERMS = pState; var $main = $('<div>').attr('id', 'wa-root').appendTo('body'); var $left = $('<div>').attr('id', 'wa-left-panel').appendTo($main); $left.append($('<h3>').append($('<a>').attr('href', DOC_URL).attr('target', '_blank').text(APP_NAME).css({ 'text-decoration': 'none', 'color': 'inherit' }))); $left.append($('<div>').attr('id', 'wa-username').append($('<a>').attr('href', mw.util.getUrl('Special:Contributions/' + mw.config.get('wgUserName'))).attr('target', '_blank').text('User: ' + mw.config.get('wgUserName')).css({ 'text-decoration': 'none', 'color': 'inherit' }))); var btnSaveProj = new OO.ui.ButtonWidget({ icon: 'download', label: 'Save project', framed: false, flags: 'progressive' }); var btnLoadProj = new OO.ui.ButtonWidget({ icon: 'upload', label: 'Load project', framed: false }); var $projBar = $('<div>').addClass('wa-project-bar').append(btnSaveProj.$element, btnLoadProj.$element); $left.append($projBar); var $fileInput = $('<input type="file" accept=".json">').hide().appendTo('body'); var $content = $('<div>').attr('id', 'wa-content-area').appendTo($left); var $right = $('<div>').attr('id', 'wa-right-panel').appendTo($main); var $editorHeader = $('<div>').addClass('wa-editor-header').appendTo($right); var $headerLeft = $('<div>').addClass('wa-header-left').appendTo($editorHeader); var $titleLink = $('<a>').addClass('wa-title-link').text('Page content').attr('target', '_blank').appendTo($headerLeft); $('<span>').addClass('wa-header-sep').appendTo($headerLeft); var $unsavedWrapper = $('<span>').addClass('wa-unsaved-wrapper').appendTo($headerLeft); $('<span>').addClass('wa-unsaved-dot').text('●').appendTo($unsavedWrapper); $('<span>').addClass('wa-unsaved-text').text('Unsaved').appendTo($unsavedWrapper); var $headerCenter = $('<div>').addClass('wa-header-center').appendTo($editorHeader); var $statusLabel = $('<span>').addClass('wa-status-label').text('READY').appendTo($headerCenter); var $headerRight = $('<div>').addClass('wa-header-right').appendTo($editorHeader); var $infoContainer = $('<span>').addClass('wa-info-container').appendTo($headerRight); var $toolsContainer = $('<div>').addClass('wa-tools-container').appendTo($headerRight); var $resizeContainer = $('<div>').addClass('wa-resize-container').appendTo($headerRight); var $adminTools = $('<div>').addClass('wa-admin-tools').hide().appendTo($toolsContainer); // Wide chevron SVGs var svgUp = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="8" viewBox="0 0 24 12"><path d="M2 10 L12 2 L22 10" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>'; var svgDown = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="8" viewBox="0 0 24 12"><path d="M2 2 L12 10 L22 2" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>'; var $btnSizeUp = $('<div>').addClass('wa-resize-btn').html(svgUp).attr('title', 'Decrease view size'); var $btnSizeDown = $('<div>').addClass('wa-resize-btn').html(svgDown).attr('title', 'Increase view size'); $resizeContainer.append($btnSizeUp, $btnSizeDown); function setPanelHeight(modeIndex) { currentHeightMode = modeIndex; if (currentHeightMode < 0) currentHeightMode = 0; if (currentHeightMode > 2) currentHeightMode = 2; $('#wa-visual-output').css('flex-basis', heightValues[currentHeightMode]); $btnSizeUp.toggleClass('wa-resize-disabled', currentHeightMode === 0); $btnSizeDown.toggleClass('wa-resize-disabled', currentHeightMode === 2); } $btnSizeUp.on('click', function() { if (!$(this).hasClass('wa-resize-disabled')) setPanelHeight(currentHeightMode - 1); }); $btnSizeDown.on('click', function() { if (!$(this).hasClass('wa-resize-disabled')) setPanelHeight(currentHeightMode + 1); }); setPanelHeight(1); if (CAN_MOVE) { var btnAdminMove = new OO.ui.ButtonWidget({ icon: 'move', title: 'Move page to $xA', disabled: true, framed: false }); $adminTools.append(btnAdminMove.$element).show(); } if (IS_ADMIN) { var btnAdminDel = new OO.ui.ButtonWidget({ icon: 'trash', title: 'Delete page', disabled: true, framed: false }); var btnAdminProt = new OO.ui.ButtonWidget({ icon: 'lock', title: 'Protect page', disabled: true, framed: false }); $adminTools.append(btnAdminDel.$element, btnAdminProt.$element).show(); } var btnWatch = new OO.ui.ButtonWidget({ icon: 'star', title: 'Watch this page', framed: false, disabled: true, accessKey: 'w' }); $toolsContainer.append(btnWatch.$element); var $visualOut = $('<div>').attr('id', 'wa-visual-output').html('<div style="color:#aaa; text-align:center; margin-top:50px;">Ready to start...</div>').prependTo($right); var $editorArea = $('<div>').attr('id', 'wa-editor-area').appendTo($right); var $textArea = $('<textarea>').attr('id', 'wa-editor-textarea').attr('placeholder', 'Page text will appear here...').appendTo($editorArea); function setStatus(msg, type) { if (!msg) msg = "Ready"; $statusLabel.text(msg).removeClass('wa-status-error wa-status-working'); if (type === 'error') $statusLabel.addClass('wa-status-error'); if (type === 'working') $statusLabel.addClass('wa-status-working'); } // EDITOR OBJECT var Editor = { mode: 'textarea', cmInstance: null, init: function() { var self = this; mw.loader.using(['ext.CodeMirror', 'ext.CodeMirror.mode.mediawiki']).then(function(require) { try { self.cmInstance = new(require('ext.CodeMirror'))($textArea[0], (require('ext.CodeMirror.mode.mediawiki')).mediawiki()); self.cmInstance.initialize(); self.mode = 'codemirror'; } catch (e) { console.error("CM Error", e); } }).catch(function(err) { console.error("CM Load Error:", err); }); $textArea.on('input', updateDirtyState); }, getValue: function() { return (this.mode === 'codemirror' && this.cmInstance) ? this.cmInstance.view.state.doc.toString() : $textArea.val(); }, setValue: function(text) { $textArea.val(text); if (this.mode === 'codemirror' && this.cmInstance) { this.cmInstance.view.dispatch({ changes: { from: 0, to: this.cmInstance.view.state.doc.length, insert: text } }); } else { $textArea[0].dispatchEvent(new Event('input')); } }, setDisabled: function(d) { $textArea.prop('disabled', d); if (this.mode === 'codemirror' && this.cmInstance) { this.cmInstance.view.contentDOM.contentEditable = !d; $($textArea).parent().find('.cm-editor').css('opacity', d ? 0.5 : 1); } }, scrollToLine: function(n) { if (isNaN(n)) return; if (this.mode === 'codemirror' && this.cmInstance) { var v = this.cmInstance.view; var l = v.state.doc.line(n); v.dispatch({ effects: v.constructor.scrollIntoView(l.from, { y: 'center' }), selection: { anchor: l.from } }); v.focus(); } } }; var WorkerEngine = { activeWorker: null, workerURL: null, currentLibCode: null, timeoutTimer: null, initWorker: function(libCode) { this.destroy(); // Clean up existing if any this.currentLibCode = libCode || ""; var scriptContent = this.currentLibCode + "\n\n" + ` self.onmessage = async function(e) { try { var data = e.data; var inputs = data.texts || [data.text]; var vars = data.vars; var outputs = []; // Helper to construct async functions dynamically var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; function inject(str) { if (!str) return ""; return str.replace(/\\$x([A-Z]|x)/g, function(m) { return vars[m] || ""; }); } // Returns a Promise and handles 'await' inside user code async function execUserFunc(code, currentText, currentVars, sharedObj) { if (!code || code.trim() === "") return currentText; try { var func = new AsyncFunction('text', 'vars', 'shared', code); var res = await func(currentText, currentVars, sharedObj); if (res && typeof res === 'object' && res.skip) { return { _skipSignal: true, reason: res.reason || 'Script-requested skip' }; } return (res !== undefined) ? res : currentText; } catch (err) { throw err; // or: return currentText } } for (var i = 0; i < inputs.length; i++) { var text = inputs[i]; var shared = {}; // Shared context for this page // 1. Pre-Process var preRes; if (data.preCode && data.preCode.trim() !== "") { preRes = await execUserFunc(data.preCode, text, vars, shared); } else if (typeof wAwB_Pre === 'function') { try { preRes = await wAwB_Pre(text, vars, shared); if (preRes && typeof preRes === 'object' && preRes.skip) { preRes = { _skipSignal: true, reason: preRes.reason || 'Script-requested skip' }; } } catch (err) { preRes = text; } } else { preRes = text; } if (preRes && preRes._skipSignal) { self.postMessage({ skipped: true, reason: preRes.reason }); return; } text = (preRes !== undefined) ? preRes : text; // 2. Rules Processing if (data.rules && data.rules.length > 0) { data.rules.forEach(function(rule) { var findStr = inject(rule.find); if (!findStr) return; if (rule.isFunc) { try { var userFunc = new Function('match', 'groups', 'vars', 'shared', rule.replace); text = text.replace(new RegExp(findStr, (rule.flags || 'gmu').replace(/[^gimsuvy]/g, '')), function(...args) { var match = args[0]; var groups = args.slice(1, -2); try { var res = userFunc(match, groups, vars, shared); return res !== undefined ? res : match; } catch (err) { return match; } }); } catch (e) {} } else { var repStr = inject(rule.replace).replace(/\\\\n/g, "\\n").replace(/\\\\t/g, "\\t").replace(/\\\\r/g, "\\r"); if (rule.regex) { try { var flags = (rule.flags || 'gmu').replace(/[^gimsuvy]/g, ''); text = text.replace(new RegExp(findStr, flags), repStr); } catch (e) {} } else { var finalFind = findStr.replace(/\\\\n/g, "\\n").replace(/\\\\t/g, "\\t").replace(/\\\\r/g, "\\r"); text = text.split(finalFind).join(repStr); } } }); } // 3. Post-Process var postRes; if (data.postCode && data.postCode.trim() !== "") { postRes = await execUserFunc(data.postCode, text, vars, shared); } else if (typeof wAwB_Post === 'function') { try { postRes = await wAwB_Post(text, vars, shared); if (postRes && typeof postRes === 'object' && postRes.skip) { postRes = { _skipSignal: true, reason: postRes.reason || 'Script-requested skip' }; } } catch (err) { postRes = text; } } else { postRes = text; } if (postRes && postRes._skipSignal) { self.postMessage({ skipped: true, reason: postRes.reason }); return; } text = (postRes !== undefined) ? postRes : text; outputs.push(text); } self.postMessage({ success: true, texts: outputs }); } catch (err) { self.postMessage({ success: false, error: err.toString() }); } }; `; var blob = new Blob([scriptContent], { type: 'application/javascript' }); this.workerURL = URL.createObjectURL(blob); this.activeWorker = new Worker(this.workerURL); }, run: function(payload) { var self = this; return new Promise(function(resolve, reject) { // Re-init if no worker exists, or if the user changed the library code if (!self.activeWorker || self.currentLibCode !== (payload.libraryCode || "")) { self.initWorker(payload.libraryCode); } if (self.timeoutTimer) clearTimeout(self.timeoutTimer); self.timeoutTimer = setTimeout(function() { self.destroy(); // Assassinate the stuck worker reject("Script timed out (" + SCRIPT_TIMEOUT_MS + "ms)."); }, SCRIPT_TIMEOUT_MS); self.activeWorker.onmessage = function(e) { clearTimeout(self.timeoutTimer); if (e.data.skipped) resolve({ skipped: true, reason: e.data.reason }); else if (e.data.success) resolve({ success: true, texts: e.data.texts }); else reject(e.data.error); }; self.activeWorker.postMessage(payload); }); }, destroy: function() { if (this.activeWorker) { this.activeWorker.terminate(); this.activeWorker = null; } if (this.workerURL) { URL.revokeObjectURL(this.workerURL); this.workerURL = null; } if (this.timeoutTimer) { clearTimeout(this.timeoutTimer); this.timeoutTimer = null; } } }; var PageProtector = { store: [], getKey: function() { var id = this.store.length.toString(); var p = ""; for (var i = 0; i < id.length; i++) { p += String.fromCharCode(0xE010 + parseInt(id[i])); } return '\uE000' + p + '\uE001'; }, protect: function(text, mode, config, templateSpecies = null) { this.store = []; var self = this; var safeRep = function(t, r) { return t.replace(r, function(m) { if (!m) return m; var key = self.getKey(); self.store.push(m); return key; }); }; var shouldProcess = function(id) { if (mode === 'target') return config === id; return config[id] === true; }; var matchedBrackets = function(text, op, cl, species = '') { var newText = "", depth = 0, start = 0, cursor = 0; var speciesRegex = species ? new RegExp(species, 'iu') : null; for (var i = 0; i < text.length; i++) { if (text[i] === op[0] && text.slice(i, i + op.length) === op) { if (depth === 0) start = i; depth++; i += op.length - 1; } else if (text[i] === cl[0] && text.slice(i, i + cl.length) === cl) { if (depth > 0) { depth--; if (depth === 0) { var chunk = text.substring(start, i + cl.length); if (!speciesRegex || speciesRegex.test(chunk)) { var key = self.getKey(); self.store.push(chunk); newText += text.substring(cursor, start) + key; } else { newText += text.substring(cursor, i + cl.length); } cursor = i + cl.length; } i += cl.length - 1; } } } newText += text.substring(cursor); return newText; }; PROTECTION_DEFS.forEach(function(def) { if (shouldProcess(def.id)) { if (def.id === 'blocks') { ['math', 'pre', 'source', 'syntaxhighlight', 'code', 'gallery'].forEach(t => text = safeRep(text, new RegExp('<' + t + '[^>]*?>[\\s\\S]*?<\\/' + t + '>|<' + t + '[^>]*?/>', 'gi'))); } else if (['templates', 'tables', 'images'].includes(def.id)) { var activeSpecies = (def.id === 'templates') ? templateSpecies : def.species; text = matchedBrackets(text, def.open, def.close, activeSpecies || ''); } else if (def.regex) { text = safeRep(text, def.regex); } } }); return text; }, restore: function(text) { var self = this; var loop = 100; while (/(\uE000[\uE010-\uE019]+\uE001)/.test(text) && loop > 0) { text = text.replace(/\uE000([\uE010-\uE019]+)\uE001/g, function(m, d) { var id = ""; for (var i = 0; i < d.length; i++) id += (d.charCodeAt(i) - 0xE010).toString(); return self.store[parseInt(id, 10)] || m; }); loop--; } return text; } }; var accordionRegistry = []; function addSection(title, $inner) { var btn = new OO.ui.ButtonWidget({ label: title, indicator: 'down', framed: false, classes: ['wa-section-header'] }); var box = $('<div>').addClass('wa-foldable-content').append($inner); var sectionObj = { btn: btn, box: box, label: title }; accordionRegistry.push(sectionObj); btn.on('click', function() { var isOpening = !box.is(':visible'); if (isOpening) { accordionRegistry.forEach(function(sec) { if (sec !== sectionObj) { sec.box.hide(); sec.btn.setIndicator('down'); } }); } box.toggle(); btn.setIndicator(box.is(':visible') ? 'up' : 'down'); }); $content.append(btn.$element, box); return sectionObj; } // WIDGETS var srcSelect = new OO.ui.DropdownInputWidget({ options: [{ data: 'cat', label: 'Category' }, { data: 'linksto', label: 'Pages linking to...' }, { data: 'linkson', label: 'Links on page...' }, { data: 'prefix', label: 'Pages with prefix...' }, { data: 'watchlist', label: 'Watchlist' }, { data: 'search', label: 'Wiki search' }, { data: 'usercontribs', label: 'User contributions' }, { data: 'pageswithprop', label: 'Pages with property' }] }); var srcInput = new OO.ui.TextInputWidget({ placeholder: 'Category...' }); var now = new Date(); var today = now.toISOString().split('T')[0]; var srcInputUser = new OO.ui.TextInputWidget({ placeholder: 'Username' }); var srcInputStartDate = new OO.ui.TextInputWidget({ value: today + 'T00:00:00', placeholder: 'ISO start date' }); var srcInputEndDate = new OO.ui.TextInputWidget({ value: today + 'T23:59:59', placeholder: 'ISO end date' }); var srcDropProp = new OO.ui.DropdownInputWidget({ options: [] }); var $optContainer = $('<div>').addClass('wa-source-options').hide(); var $optCat = $('<div>').hide(); var $optUser = $('<div>').hide(); var $optProp = $('<div>').hide(); var chkCatPages = new OO.ui.CheckboxInputWidget({ selected: true }); var chkCatSub = new OO.ui.CheckboxInputWidget({ selected: false }); var chkCatFile = new OO.ui.CheckboxInputWidget({ selected: false }); $optCat.append($('<div>').addClass('wa-opt-label').text('Include:'), new OO.ui.FieldLayout(chkCatPages, { label: 'Pages', align: 'inline' }).$element, new OO.ui.FieldLayout(chkCatSub, { label: 'Subcats', align: 'inline' }).$element, new OO.ui.FieldLayout(chkCatFile, { label: 'Files', align: 'inline' }).$element); $optUser.append(new OO.ui.FieldLayout(srcInputUser, { label: 'User', align: 'top' }).$element, new OO.ui.FieldLayout(srcInputStartDate, { label: 'Start (Older)', align: 'top' }).$element, new OO.ui.FieldLayout(srcInputEndDate, { label: 'End (Newer)', align: 'top' }).$element); $optProp.append(new OO.ui.FieldLayout(srcDropProp, { label: 'Property', align: 'top' }).$element); var $optLinks = $('<div>').hide(); var chkLinkWiki = new OO.ui.CheckboxInputWidget({ selected: true }); var chkLinkTrans = new OO.ui.CheckboxInputWidget({ selected: false }); var chkLinkImg = new OO.ui.CheckboxInputWidget({ selected: false }); var dropLinkRedir = new OO.ui.DropdownInputWidget({ options: [{ data: 'nonredirects', label: 'No redirects' }, { data: 'all', label: 'Both' }, { data: 'redirects', label: 'Redirects only' }] }); var chkLinkToRedir = new OO.ui.CheckboxInputWidget({ selected: false }); $optLinks.append($('<div>').addClass('wa-opt-label').text('What to include:'), $('<div>').addClass('wa-opt-row').append(new OO.ui.FieldLayout(chkLinkWiki, { label: 'Wikilinks', align: 'inline' }).$element, new OO.ui.FieldLayout(chkLinkTrans, { label: 'Transclusions', align: 'inline' }).$element, new OO.ui.FieldLayout(chkLinkImg, { label: 'File usage', align: 'inline' }).$element), $('<div>').addClass('wa-opt-label').text('Redirects:'), dropLinkRedir.$element, new OO.ui.FieldLayout(chkLinkToRedir, { label: 'Include links to redirects', align: 'inline' }).$element); $optContainer.append($optCat, $optLinks, $optUser, $optProp); var queryCache = {}; var lastMode = 'cat'; srcSelect.on('change', function(newMode) { if (!isLoadingProject) { if (lastMode !== 'watchlist' && lastMode !== 'usercontribs' && lastMode !== 'pageswithprop') { queryCache[lastMode] = srcInput.getValue(); } } $optContainer.hide(); $optCat.hide(); $optLinks.hide(); $optUser.hide(); $optProp.hide(); srcInput.setDisabled(false).$element.show(); if (newMode === 'cat') { $optContainer.show(); $optCat.show(); } else if (newMode === 'linksto') { $optContainer.show(); $optLinks.show(); } else if (newMode === 'usercontribs') { $optContainer.show(); $optUser.show(); srcInput.setDisabled(true).$element.hide(); } else if (newMode === 'pageswithprop') { $optContainer.show(); $optProp.show(); srcInput.setDisabled(true).$element.hide(); if (!propNamesLoaded) { new mw.Api().get({ action: 'query', list: 'pagepropnames', ppnlimit: 'max' }).then(function(d) { if (d.query && d.query.pagepropnames) { srcDropProp.setOptions(d.query.pagepropnames.map(p => ({ data: p.propname, label: p.propname }))); propNamesLoaded = true; } }); } } if (newMode === 'watchlist') { srcInput.setValue(''); srcInput.setDisabled(true); srcInput.$input.attr('placeholder', '(No query needed)'); } else if (newMode !== 'usercontribs' && newMode !== 'pageswithprop') { srcInput.setValue(queryCache[newMode] || ''); var ph = 'Query...'; if (newMode === 'cat') ph = 'Category name'; if (newMode === 'search') ph = 'Search query...'; if (newMode === 'prefix') ph = 'Page prefix...'; if (newMode === 'linksto') ph = 'Pages linking to this title...'; if (newMode === 'linkson') ph = 'Get links from this page...'; srcInput.$input.attr('placeholder', ph); } lastMode = newMode; }); srcSelect.emit('change', srcSelect.getValue()); var $nsSelect = $('<select>').attr('id', 'wa-ns-selector').attr('multiple', 'multiple').attr('size', '8'); var nsMap = mw.config.get('wgFormattedNamespaces'); for (var id in nsMap) { if (parseInt(id) >= 0) $nsSelect.append($('<option>').val(id).text(id + ': ' + (nsMap[id] || '(Main)'))); } $nsSelect.val(['0']); var btnAdd = new OO.ui.ButtonWidget({ label: 'Add to list', icon: 'add', flags: ['primary', 'progressive'] }); var $btnRow = $('<div>').css({ 'display': 'flex', 'justify-content': 'flex-end', 'margin-top': '10px' }); var $fetchStatus = $('<span>').css({ 'margin-right': '10px', 'color': '#888', 'font-size': '0.85em', 'align-self': 'center' }).hide(); $btnRow.append($fetchStatus, btnAdd.$element); addSection('Source', $('<div>').append(new OO.ui.FieldLayout(srcSelect, { label: 'Mode', align: 'top' }).$element, new OO.ui.FieldLayout(srcInput, { label: 'Query', align: 'top' }).$element, $optContainer, $('<div>').text('Namespaces:').css({ 'font-weight': 'bold', 'margin-top': '5px' }), $nsSelect, $btnRow)); var redirMode = new OO.ui.RadioSelectWidget({ items: [new OO.ui.RadioOptionWidget({ data: 'edit', label: 'Edit the redirect page (Default)' }), new OO.ui.RadioOptionWidget({ data: 'follow', label: 'Follow redirect (Edit target)' }), new OO.ui.RadioOptionWidget({ data: 'skip', label: 'Skip redirects' })] }); redirMode.selectItemByData('edit'); var radSkipExist = new OO.ui.RadioSelectWidget({ items: [new OO.ui.RadioOptionWidget({ data: 'none', label: 'Process all' }), new OO.ui.RadioOptionWidget({ data: 'missing', label: 'Skip if page does not exist' }), new OO.ui.RadioOptionWidget({ data: 'exists', label: 'Skip if page exists' })] }); radSkipExist.selectItemByData('none'); var chkSkipNoChange = new OO.ui.CheckboxInputWidget({ selected: false }); var inpSkipContains = new OO.ui.TextInputWidget({ placeholder: 'Text/Regex for Skip if FOUND' }); var togSkipContainsRegex = new OO.ui.ToggleSwitchWidget({ value: false, title: 'Use regex' }); var inpSkipNotContains = new OO.ui.TextInputWidget({ placeholder: 'Text/Regex for Skip if MISSING' }); var togSkipNotContainsRegex = new OO.ui.ToggleSwitchWidget({ value: false, title: 'Use regex' }); var inpSkipCategories = new OO.ui.TextInputWidget({ placeholder: 'Skip if in: Category1|Category2' }); var inpSkipNotCategories = new OO.ui.TextInputWidget({ placeholder: 'Skip if NOT in: Category1|Category2' }); var $settingsPanel = $('<div>') .append($('<span>').addClass('wa-settings-header').text('Redirects')) .append(redirMode.$element) .append($('<hr>').css('border-top', '1px solid #eee')) .append($('<span>').addClass('wa-settings-header').text('Skip logic')) .append(new OO.ui.FieldLayout(chkSkipNoChange, { label: 'Skip if no changes made', align: 'inline' }).$element.css('margin-bottom', '8px')) .append(radSkipExist.$element) .append($('<hr>').css('border-top', '1px solid #eee')) .append($('<span>').addClass('wa-settings-header').text('Content filters')) .append($('<div>').addClass('wa-setting-row').append(inpSkipContains.$element.css('flex', 1), togSkipContainsRegex.$element.css('margin-left', '5px'))) .append($('<div>').addClass('wa-setting-row').append(inpSkipNotContains.$element.css('flex', 1), togSkipNotContainsRegex.$element.css('margin-left', '5px'))) .append($('<hr>').css('border-top', '1px solid #eee')) .append($('<span>').addClass('wa-settings-header').text('Category filters')) .append(new OO.ui.FieldLayout(inpSkipCategories, { label: 'Blacklist', align: 'top' }).$element) .append(new OO.ui.FieldLayout(inpSkipNotCategories, { label: 'Whitelist', align: 'top' }).$element); addSection('Skip', $settingsPanel); var dropProtMode = new OO.ui.DropdownInputWidget({ options: [{ data: 'protect', label: 'Protect (Exclude)' }, { data: 'target', label: 'Target (Edit Matches Only)' }] }); var inpTemplateFilter = new OO.ui.TextInputWidget({ placeholder: 'Regex: infobox rail line|railway' }); var $templateFilterLayout = new OO.ui.FieldLayout(inpTemplateFilter, { label: 'Template filter', align: 'top' }); var $protList = $('<div>'); var protCheckboxes = {}; PROTECTION_DEFS.forEach(function(def) { var chk = new OO.ui.CheckboxInputWidget({ selected: def.isOn }); protCheckboxes[def.id] = chk; $protList.append(new OO.ui.FieldLayout(chk, { label: def.label, align: 'inline' }).$element); }); var targetRadioItems = PROTECTION_DEFS.map(function(def) { return new OO.ui.RadioOptionWidget({ data: def.id, label: def.label }); }); var radTargetSet = new OO.ui.RadioSelectWidget({ items: targetRadioItems }); var $targetList = $('<div>').hide().append(radTargetSet.$element); dropProtMode.on('change', function(mode) { if (mode === 'protect') { $protList.show(); $targetList.hide(); } else { $protList.hide(); $targetList.show(); } }); addSection('Protection', $('<div>').addClass('wa-source-options') .append(new OO.ui.FieldLayout(dropProtMode, { label: 'Mode', align: 'top' }).$element) .append($('<hr>').css('border-top', '1px solid #eee')) .append($protList).append($targetList) .append($('<div style="margin-top:10px;">').append($templateFilterLayout.$element)) ); var $rulesList = $('<div>'); var btnAddRule = new OO.ui.ButtonWidget({ label: 'Add rule', icon: 'add' }); var rulesRegistry = []; addSection('Rules', $('<div>').append($rulesList, btnAddRule.$element)); var togWikiTypos = new OO.ui.ToggleSwitchWidget({ value: false }); var lblWikiStatus = $('<div>').css({ 'font-size': '0.85em', 'color': '#888', 'margin-top': '2px' }); var btnLoadLocal = new OO.ui.ButtonWidget({ icon: 'upload', label: 'Load file', framed: false }); var btnClearLocal = new OO.ui.ButtonWidget({ icon: 'trash', title: 'Clear local', framed: false, flags: 'destructive', disabled: true }); var lblLocalStatus = $('<div>').text('No local rules').css({ 'font-size': '0.85em', 'color': '#888', 'margin-top': '2px' }); var $typoInput = $('<input type="file">').hide().appendTo('body'); var $extRulesPanel = $('<div>').addClass('wa-source-options'); $extRulesPanel.append( $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between' }).append($('<span>').text('Project:AutoWikiBrowser/Typos').css('font-weight', 'bold'), togWikiTypos.$element), $('<div>').css('margin-bottom', '10px').append(lblWikiStatus), $('<hr>').css('border-top', '1px solid #eee'), $('<div>').append($('<div>').css({ 'display': 'flex', 'align-items': 'center' }).append($('<span>').text('Local rules (session only)').css({ 'font-weight': 'bold' }), $('<div>').css('flex', '1'), btnLoadLocal.$element, btnClearLocal.$element), lblLocalStatus) ); addSection('External rules', $extRulesPanel); var txtPreScript = new OO.ui.MultilineTextInputWidget({ rows: 6, value: '', placeholder: '// Enter JavaScript function body here.\n// Available variables: text, vars, shared\nreturn text;' }); var txtPostScript = new OO.ui.MultilineTextInputWidget({ rows: 6, value: '', placeholder: '// Enter JavaScript function body here.\n// Available variables: text, vars, shared\nreturn text;' }); var btnLoadLib = new OO.ui.ButtonWidget({ icon: 'upload', title: 'Load library (.js)', framed: false }); var btnRemoveLib = new OO.ui.ButtonWidget({ icon: 'trash', title: 'Remove library', framed: false, flags: 'destructive' }); var txtLibStatus = new OO.ui.TextInputWidget({ value: '(No library loaded)', readOnly: true }); var $libInput = $('<input type="file" accept=".js">').hide().appendTo('body'); var btnEditLib = new OO.ui.ButtonWidget({ icon: 'edit', label: 'Edit project library', framed: false }); var $scriptPanel = $('<div>').append( $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'gap': '5px', 'margin-bottom': '10px' }).append($('<span>').text('JS library:').css({ 'font-weight': 'bold', 'white-space': 'nowrap' }), txtLibStatus.$element.css('flex', '1'), btnLoadLib.$element, btnRemoveLib.$element), $('<div>').css({ 'display': 'flex', 'justify-content': 'flex-end', 'margin-bottom': '10px' }).append(btnEditLib.$element), new OO.ui.FieldLayout(txtPreScript, { label: 'Pre-Process', align: 'top' }).$element, new OO.ui.FieldLayout(txtPostScript, { label: 'Post-Process', align: 'top' }).$element ); addSection('Scripts', $scriptPanel); function updateLibUI() { if (currentLibrary.code) { txtLibStatus.setValue(currentLibrary.name); btnRemoveLib.setDisabled(false); } else { txtLibStatus.setValue('(No library loaded)'); btnRemoveLib.setDisabled(true); } } updateLibUI(); function LibraryEditorDialog(config) { LibraryEditorDialog.super.call(this, config); } OO.inheritClass(LibraryEditorDialog, OO.ui.ProcessDialog); LibraryEditorDialog.static.name = 'libraryEditor'; LibraryEditorDialog.static.title = 'Edit project library'; LibraryEditorDialog.static.actions = [{ action: 'save', label: 'Save', flags: ['primary', 'progressive'] }, { label: 'Cancel', flags: 'safe' } ]; LibraryEditorDialog.prototype.initialize = function() { LibraryEditorDialog.super.prototype.initialize.call(this); this.$element.addClass('wa-lib-dialog'); // Attach our custom CSS override class this.panel = new OO.ui.PanelLayout({ padded: true, expanded: true }); this.$editorWrapper = $('<div>').addClass('wa-lib-editorwrapper'); this.panel.$element.append(this.$editorWrapper); this.$body.append(this.panel.$element); }; LibraryEditorDialog.prototype.getSetupProcess = function(data) { data = data || {}; return LibraryEditorDialog.super.prototype.getSetupProcess.call(this, data) .next(function() { var self = this; self.$editorWrapper.empty(); // Create a textarea for the MediaWiki CM wrapper to properly bind to var $libTextArea = $('<textarea>').appendTo(self.$editorWrapper); var initCode = currentLibrary.code || "// All custom library functions defined here will be passed to the worker.\n// Special functions:\n// function wAwB_Pre(text, vars, shared) { return text; }\n// function wAwB_Post(text, vars, shared) { return text; }\n"; return mw.loader.using(['ext.CodeMirror', 'ext.CodeMirror.modes']).then(function(require) { var CM = require('ext.CodeMirror'); var modes = require('ext.CodeMirror.modes'); self.cmInstance = new CM($libTextArea[0], modes.javascript()); self.cmInstance.initialize(); self.cmInstance.view.dispatch({ changes: { from: 0, insert: initCode } }); // Force CodeMirror to fill the wrapper self.$editorWrapper.find('.cm-editor').css({ height: '100%' }); }).catch(function(err) { console.error("wAwB CM Init Error:", err); }); }, this); }; LibraryEditorDialog.prototype.getActionProcess = function(action) { var dialog = this; if (action === 'save') { return new OO.ui.Process(function() { var newCode = ""; if (dialog.cmInstance) { newCode = dialog.cmInstance.view.state.doc.toString(); } if (newCode.trim() === "") { currentLibrary = { name: null, code: null }; } else { currentLibrary.code = newCode; currentLibrary.name = "custom code"; } updateLibUI(); dialog.close({ action: action }); }); } if (action === 'cancel' || !action) { return new OO.ui.Process(function() { dialog.close({ action: action }); }); } return LibraryEditorDialog.super.prototype.getActionProcess.call(this, action); }; LibraryEditorDialog.prototype.getTeardownProcess = function(data) { return LibraryEditorDialog.super.prototype.getTeardownProcess.call(this, data) .next(function() { if (this.cmInstance) { try { this.cmInstance.view.destroy(); } catch (e) {} this.cmInstance = null; } }, this); }; var windowManager = new OO.ui.WindowManager(); $('body').append(windowManager.$element); var libDialog = new LibraryEditorDialog(); windowManager.addWindows([libDialog]); btnEditLib.on('click', function() { windowManager.openWindow(libDialog); }); var togAdminEnable = new OO.ui.ToggleSwitchWidget({ value: false }); var chkMovRedirect = new OO.ui.CheckboxInputWidget({ selected: false }); var chkMovTalk = new OO.ui.CheckboxInputWidget({ selected: true }); var chkMovSub = new OO.ui.CheckboxInputWidget({ selected: false }); var chkDelTalk = new OO.ui.CheckboxInputWidget({ selected: true }); var dropProtEdit = new OO.ui.DropdownInputWidget({ options: [{ data: '', label: '(No Change)' }, { data: 'all', label: 'All' }, { data: 'autoconfirmed', label: 'Autoconfirmed' }, { data: 'sysop', label: 'Sysop' }] }); var dropProtMove = new OO.ui.DropdownInputWidget({ options: [{ data: '', label: '(No Change)' }, { data: 'all', label: 'All' }, { data: 'autoconfirmed', label: 'Autoconfirmed' }, { data: 'sysop', label: 'Sysop' }] }); var inpProtExpiry = new OO.ui.TextInputWidget({ placeholder: 'infinite / 2 days / 12 hours' }); if (CAN_MOVE || IS_ADMIN) { var $adminPanel = $('<div>').append( $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'flex-start', 'gap': '10px' }).append($('<span>').text('Enable page actions').css('font-weight', 'bold'), togAdminEnable.$element), $('<hr>') ); if (CAN_MOVE) { $adminPanel.append( $('<strong>').text('Move options:'), new OO.ui.FieldLayout(chkMovRedirect, { label: 'Do not create redirect', align: 'inline' }).$element, new OO.ui.FieldLayout(chkMovTalk, { label: 'Move talk page', align: 'inline' }).$element, new OO.ui.FieldLayout(chkMovSub, { label: 'Move subpages', align: 'inline' }).$element, $('<br>') ); } if (IS_ADMIN) { $adminPanel.append( $('<strong>').text('Delete options:'), new OO.ui.FieldLayout(chkDelTalk, { label: 'Delete talk page', align: 'inline' }).$element, $('<br>'), $('<strong>').text('Protect options:'), new OO.ui.FieldLayout(dropProtEdit, { label: 'Edit level', align: 'top' }).$element, new OO.ui.FieldLayout(dropProtMove, { label: 'Move level', align: 'top' }).$element, new OO.ui.FieldLayout(inpProtExpiry, { label: 'Expiry', align: 'top' }).$element ); } addSection('Page actions', $adminPanel); } var btnPower = new OO.ui.ButtonWidget({ label: 'Start', icon: 'power', flags: ['primary', 'progressive'], title: 'Start editing', accessKey: 'a' }); var btnDiff = new OO.ui.ButtonWidget({ label: 'Diff', icon: 'update', title: 'Show diff', accessKey: 'd' }); var btnSkip = new OO.ui.ButtonWidget({ label: 'Next', icon: 'next', title: 'Skip to next page', accessKey: 'n', disabled: true }); var btnPreview = new OO.ui.ButtonWidget({ label: 'Preview', icon: 'article', title: 'Preview page', accessKey: 'p' }); var btnSave = new OO.ui.ButtonWidget({ label: 'Save', icon: 'upload', flags: 'progressive', title: 'Save edit', accessKey: 's', disabled: true }); var inputSummary = new OO.ui.TextInputWidget({ placeholder: '', value: '', title: 'Enter edit summary', accessKey: 'b' }); var $sumLayout = new OO.ui.FieldLayout(inputSummary, { label: 'Edit summary', align: 'top' }).$element; $sumLayout.css('margin-bottom', '6px'); var listTextarea = new OO.ui.MultilineTextInputWidget({ rows: 15, classes: ['wa-page-list-raw'] }); var btnSort = new OO.ui.ButtonWidget({ icon: 'sortVertical', framed: false }); var btnDedup = new OO.ui.ButtonWidget({ icon: 'funnel', framed: false }); var btnClear = new OO.ui.ButtonWidget({ icon: 'trash', framed: false }); var btnPreParse = new OO.ui.ButtonWidget({ label: 'Pre-parse', title: 'Process list in background', icon: 'robot', framed: false }); var $listCounter = $('<span>').addClass('wa-list-counter').text('0 pages'); var togAutoSave = new OO.ui.ToggleSwitchWidget({ value: false }); var txtAutoDelay = new OO.ui.TextInputWidget({ value: '10' }); var $botRow = $('<div>').addClass('wa-bot-row').hide(); if (PERMS.allowBot) { $botRow.show().append($('<span>').css('font-weight', 'bold').text('Bot mode: '), togAutoSave.$element, $('<span>').text('Delay (s):'), txtAutoDelay.$element.css('max-width', '40px')); togAutoSave.on('change', function(v) { if (v) txtAutoDelay.setValue('10'); }); } var sortAsc = true; var $procHeader = $('<div>').addClass('wa-section-header').attr('id', 'wa-proc-header').css({ 'display': 'flex', 'justify-content': 'space-between', 'align-items': 'center' }); var $procTitle = $('<span>').attr('id', 'wa-proc-title').text('Processing'); var chkMinor = new OO.ui.CheckboxInputWidget({ selected: true, title: 'Minor edit' }); var $minorLayout = new OO.ui.FieldLayout(chkMinor, { label: 'm', align: 'inline', title: 'Minor edit' }); $minorLayout.$element.css({ 'margin-right': '15px', 'font-weight': 'normal' }); $procHeader.append($procTitle, $minorLayout.$element); var $procContent = $('<div>').attr('id', 'wa-proc-content').append( $sumLayout, $botRow, $('<div>').addClass('wa-grid-container').append( $('<div>').addClass('wa-grid-col').append(btnPower.$element), $('<div>').addClass('wa-grid-col').append(btnDiff.$element, btnSkip.$element), $('<div>').addClass('wa-grid-col').append(btnPreview.$element, btnSave.$element) ), $('<div>').addClass('wa-toolbar').append($listCounter, btnSort.$element, btnDedup.$element, btnClear.$element), listTextarea.$element, $('<div>').css({ 'margin-top': '5px' }).append(btnPreParse.$element) ); $content.append($procHeader, $procContent); var configWidgets = [ srcSelect, srcInput, srcInputUser, srcInputStartDate, srcInputEndDate, srcDropProp, chkCatPages, chkCatSub, chkCatFile, chkLinkWiki, chkLinkTrans, chkLinkImg, dropLinkRedir, chkLinkToRedir, btnAdd, redirMode, chkSkipNoChange, radSkipExist, inpSkipContains, togSkipContainsRegex, inpSkipNotContains, togSkipNotContainsRegex, inpSkipCategories, inpSkipNotCategories, dropProtMode, radTargetSet, inpTemplateFilter, btnAddRule, txtPreScript, txtPostScript, chkMovRedirect, chkMovTalk, chkMovSub, chkDelTalk, dropProtEdit, dropProtMove, inpProtExpiry, togWikiTypos, btnLoadLocal, btnClearLocal, btnPreParse ]; // ===================================================================== // 5. FUNCTION DEFINITIONS (Core Logic) // ===================================================================== function checkSummaryWarning() { var val = inputSummary.getValue(); var isBlank = !val || val.trim() === ""; if (isBlank || hasNewSources) inputSummary.$element.addClass('wa-summary-warning'); else inputSummary.$element.removeClass('wa-summary-warning'); } function renderCurrentView() { if (currentViewMode === 'preview') renderPreview(); else renderDiff(); } function toggleConfig(isLocked) { configWidgets.forEach(function(w) { if (w instanceof OO.ui.TextInputWidget || w instanceof OO.ui.MultilineTextInputWidget) { w.setReadOnly(isLocked); w.$element.css('opacity', isLocked ? 0.8 : 1); } else { w.setDisabled(isLocked); } }); $nsSelect.prop('disabled', isLocked); for (var key in protCheckboxes) protCheckboxes[key].setDisabled(isLocked); rulesRegistry.forEach(function(r) { r.find.setReadOnly(isLocked); r.rep.setReadOnly(isLocked); r.regex.setDisabled(isLocked); r.flags.setReadOnly(isLocked); r.enable.setDisabled(isLocked); r.del.setDisabled(isLocked); r.btnFunc.setDisabled(isLocked || !r.regex.getValue()); r.btnUp.setDisabled(isLocked || rulesRegistry.indexOf(r) === 0); r.btnDown.setDisabled(isLocked || rulesRegistry.indexOf(r) === rulesRegistry.length - 1); }); if (CAN_MOVE || IS_ADMIN) togAdminEnable.setDisabled(isLocked); btnLoadLib.setDisabled(isLocked); btnRemoveLib.setDisabled(isLocked || !currentLibrary.code); btnEditLib.setDisabled(isLocked); btnLoadLocal.setDisabled(isLocked); btnClearLocal.setDisabled(isLocked || localTypos.length === 0); } function updateListCount() { var val = listTextarea.getValue(); var count = val.trim() ? val.split('\n').filter(function(l) { var line = l.trim(); return line !== "" && !line.startsWith("####"); }).length : 0; $listCounter.text(count + ' pages'); } listTextarea.on('change', updateListCount); function updateDirtyState() { if (isRunning && currentTitle && Editor.getValue() !== originalWikitext) $editorHeader.addClass('wa-dirty'); else $editorHeader.removeClass('wa-dirty'); } function removeTopLine() { var l = listTextarea.getValue().split('\n'); l.shift(); listTextarea.setValue(l.join('\n')); updateListCount(); } function updateInterfaceMode() { var isAdminMode = togAdminEnable.getValue(); var pageLoaded = !!currentTitle; btnSave.setDisabled(isAdminMode || !pageLoaded || !PERMS.canSave); btnSkip.setDisabled(!pageLoaded); btnPreview.setDisabled(!pageLoaded); btnDiff.setDisabled(isAdminMode || !pageLoaded); Editor.setDisabled(isAdminMode || !pageLoaded); if (CAN_MOVE) { var allowAdmin = isAdminMode && currentPageExists; btnAdminMove.setDisabled(!(allowAdmin && currentVars['$xA'])); if (currentVars['$xA']) btnAdminMove.setTitle('Move page to ' + currentVars['$xA']); else btnAdminMove.setTitle('Move page to $xA (Variable not set)'); } if (IS_ADMIN) { var allowAdmin = isAdminMode && currentPageExists; btnAdminDel.setDisabled(!allowAdmin); btnAdminProt.setDisabled(!allowAdmin); } } function renderDiff() { $visualOut.html('<div style="color:#888; text-align:center;">Generating Diff...</div>'); var currentText = Editor.getValue(); new mw.Api().post({ 'action': 'compare', fromtitle: currentTitle, toslots: 'main', 'totext-main': currentText, slots: 'main', prop: 'diff', formatversion: 2 }).then(function(data) { var diffBody = data.compare && data.compare.bodies && data.compare.bodies.main; if (diffBody) { $visualOut.html('<h4>Diff: ' + currentTitle + '</h4><table class="diff"><colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup><tbody>' + diffBody + '</tbody></table>'); processDiffTable(); } else { $visualOut.html('<div style="color:green; text-align:center; padding-top:20px;">No Changes detected</div>'); } }); } function processDiffTable() { var rightLineNum = 0; $visualOut.find('table.diff tr').each(function() { var $tr = $(this); var $linenos = $tr.find('td.diff-lineno'); if ($linenos.length > 0) { var txt = $linenos.last().text(); var m = txt.match(/(\d+)/); if (m) rightLineNum = parseInt(m[1]); return; } if ($tr.find('.diff-addedline').length > 0 || $tr.find('.diff-context').length > 0) { $tr.attr('data-line', rightLineNum); $tr.css('cursor', 'pointer').attr('title', 'Jump to line ' + rightLineNum); rightLineNum++; } }); // Attach a single delegated click listener to the table instead of every row $visualOut.find('table.diff').on('click', 'tr[data-line]', function() { Editor.scrollToLine(parseInt($(this).attr('data-line'))); }); } function renderPreview() { $visualOut.html('<div style="color:#888; text-align:center;">Generating Preview...</div>'); new mw.Api().post({ action: 'parse', title: currentTitle, text: Editor.getValue(), prop: 'text|categorieshtml|modules|jsconfigvars', useskin: mw.config.get('skin'), disablelimitreport: true, pst: true, contentmodel: 'wikitext' }).then(function(data) { if (data.parse && data.parse.text) { var $prev = $('<div>').html(data.parse.text['*']); if (data.parse.categorieshtml) $prev.append(data.parse.categorieshtml['*']); $prev.find('a').attr('target', '_blank'); $visualOut.empty().append($prev); mw.loader.using(data.parse.modules.concat(data.parse.modulestyles, data.parse.modulescripts), function() { mw.hook('wikipage.content').fire($('.wa-visual-output .mw-parser-output')); }); } }).catch(function(err) { $visualOut.html('Error generating preview.'); alert("Preview failed: " + err); }); } async function transformPageText(rawText, title, config) { var filters = config.filters; if (filters) { var check = function(text, rule) { if (!rule || !rule.val) return false; if (rule.regex) { try { return new RegExp(rule.val, 'mu').test(text); } catch (e) { return false; } } return text.indexOf(rule.val) !== -1; }; if (filters.skipContains && filters.skipContains.val && check(rawText, filters.skipContains)) { return { skipped: true, reason: 'Contains: ' + filters.skipContains.val }; } if (filters.skipNotContains && filters.skipNotContains.val && !check(rawText, filters.skipNotContains)) { return { skipped: true, reason: 'Missing: ' + filters.skipNotContains.val }; } } var mode = config.mode; var inputs = []; var compiledSpecies = null; if (config.templateFilter) { var tFilter = config.templateFilter; if (tFilter[0] === "^") tFilter = "^\\{\\{\\s*" + tFilter.slice(1); else tFilter = "\\{\\{\\s*" + tFilter; compiledSpecies = tFilter + "(?=\\s*[|}\\n])"; } var skeleton = PageProtector.protect(rawText, mode, config.excludes, compiledSpecies); if (mode === 'target') inputs = PageProtector.store; else inputs = [skeleton]; var combinedRules = rulesRegistry.filter(r => r.isActive()).map(r => ({ find: r.find.getValue(), replace: r.rep.getValue(), regex: r.regex.getValue(), flags: r.flags.getValue(), enabled: r.isActive(), isFunc: r.isFunc() })); if (togWikiTypos.getValue()) combinedRules = combinedRules.concat(wikiTypos); if (localTypos.length > 0) combinedRules = combinedRules.concat(localTypos); var payload = { texts: inputs, vars: config.vars, preCode: getUserCode(txtPreScript, 'wAwB_Pre'), libraryCode: currentLibrary.code, rules: combinedRules, postCode: getUserCode(txtPostScript, 'wAwB_Post') }; var result = await WorkerEngine.run(payload); if (result.skipped) return { skipped: true, reason: result.reason }; var finalText = ""; if (mode === 'target') { PageProtector.store = result.texts; finalText = PageProtector.restore(skeleton); } else { finalText = PageProtector.restore(result.texts[0]); } return { skipped: false, text: finalText }; } async function processPageContent() { try { setStatus('Processing...', 'working'); var mode = dropProtMode.getValue(); var activeConfig = { mode: mode, excludes: {}, templateFilter: inpTemplateFilter.getValue().trim(), vars: currentVars, filters: { skipContains: { val: inpSkipContains.getValue(), regex: togSkipContainsRegex.getValue() }, skipNotContains: { val: inpSkipNotContains.getValue(), regex: togSkipNotContainsRegex.getValue() } } }; if (mode === 'protect') { for (var k in protCheckboxes) activeConfig.excludes[k] = protCheckboxes[k].isSelected(); } else { var sel = radTargetSet.findSelectedItem(); activeConfig.excludes = sel ? sel.getData() : null; } var res = await transformPageText(originalWikitext, currentTitle, activeConfig); if (res.skipped) { removeTopLine(); loadNextPage(); return; } if (chkSkipNoChange.isSelected() && res.text === originalWikitext) { removeTopLine(); loadNextPage(); return; } setStatus('Ready'); Editor.setValue(res.text); if (CAN_MOVE || IS_ADMIN) updateInterfaceMode(); else { Editor.setDisabled(false); btnSave.setDisabled(!PERMS.canSave); btnSkip.setDisabled(false); btnPreview.setDisabled(false); btnDiff.setDisabled(false); } updateDirtyState(); renderCurrentView(); if (PERMS.allowBot && togAutoSave.getValue()) { var delay = Math.max(0, parseInt(txtAutoDelay.getValue(), 10) || 0) * 1000; setStatus('Auto-save in ' + (delay / 1000) + 's...', 'working'); if (autoSaveTimer) clearTimeout(autoSaveTimer); autoSaveTimer = setTimeout(function() { if (isRunning && PERMS.canSave) { btnSave.emit('click'); } }, delay); } } catch (e) { setStatus('Error', 'error'); alert(e); btnPower.emit('click'); } } async function runPreParseBatch() { // 1. Toggle / Stop Logic if (isRunning) { isRunning = false; setStatus('Stopping...', 'working'); btnPreParse.setLabel('Pre-parse'); return; } // 2. Start & Deduplicate var currentVal = listTextarea.getValue(); var cleanVal = getDeduplicatedList(currentVal).join('\n'); listTextarea.setValue(cleanVal); updateListCount(); isRunning = true; toggleUI(true); // 3. Lock UI toggleUI(true); btnSkip.setDisabled(true); btnDiff.setDisabled(true); btnPreview.setDisabled(true); btnSave.setDisabled(true); Editor.setDisabled(true); btnPreParse.setLabel('Stop pre-parse'); // Inject STOP marker if not present var currentList = listTextarea.getValue().split('\n'); if (!currentList.includes('####STOP')) { currentList.push('####STOP'); listTextarea.setValue(currentList.join('\n')); } // Gather Config var activeConfig = { mode: dropProtMode.getValue(), excludes: {}, templateFilter: inpTemplateFilter.getValue().trim(), vars: {}, filters: { skipContains: { val: inpSkipContains.getValue(), regex: togSkipContainsRegex.getValue() }, skipNotContains: { val: inpSkipNotContains.getValue(), regex: togSkipNotContainsRegex.getValue() } } }; if (activeConfig.mode === 'protect') { for (var k in protCheckboxes) activeConfig.excludes[k] = protCheckboxes[k].isSelected(); } else { var sel = radTargetSet.findSelectedItem(); activeConfig.excludes = sel ? sel.getData() : null; } setStatus('Pre-parsing...', 'working'); while (isRunning) { var lines = listTextarea.getValue().split('\n'); var batchTitles = []; var stopFound = false; for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (line === '####STOP') { stopFound = true; break; } if (line && !line.startsWith('####')) { var parts = line.split('|'); batchTitles.push({ fullLine: line, title: parts[0], vars: parts.slice(1) }); } if (batchTitles.length >= 50) break; } if (batchTitles.length === 0) { if (stopFound) setStatus('Pre-parse complete'); else setStatus('List empty'); break; } $listCounter.text('Fetching ' + batchTitles.length + '...'); var badCats = inpSkipCategories.getValue().split('|').map(s => s.trim()).filter(s => s); var reqCats = inpSkipNotCategories.getValue().split('|').map(s => s.trim()).filter(s => s); var api = new mw.Api(); try { var data = await api.get({ action: 'query', prop: 'revisions' + (badCats.length + reqCats.length > 0 ? '|categories' : ''), titles: batchTitles.map(t => t.title).join('|'), rvprop: 'content', rvslots: 'main', redirects: 1, cllimit: 'max' }); var pageMap = {}; if (data.query && data.query.pages) Object.values(data.query.pages).forEach(p => pageMap[p.title] = p); var redirMap = {}; if (data.query && data.query.redirects) data.query.redirects.forEach(r => redirMap[r.from] = r.to); var keptLines = []; for (var k = 0; k < batchTitles.length; k++) { var item = batchTitles[k]; var lookupTitle = redirMap[item.title] || item.title; var page = pageMap[lookupTitle]; if (!page || page.missing || page.invalid || !page.revisions || !page.revisions[0]) { console.warn("Skipping invalid/missing page:", item.title); continue; } var pageCats = new Set((page.categories || []).map(c => c.title.replace(/^[^:]+:/, '').trim())); if (badCats.some(c => pageCats.has(c))) continue; // Skip if (reqCats.length > 0 && !reqCats.some(c => pageCats.has(c))) continue; // Skip var rawText = page.revisions[0].slots.main['*']; activeConfig.vars = { '$xx': item.title }; item.vars.forEach((v, idx) => activeConfig.vars['$x' + String.fromCharCode(65 + idx)] = v); var res = await transformPageText(rawText, item.title, activeConfig); // UPDATED LOGIC: Respect "Skip if no change" checkbox if (!res.skipped && (!chkSkipNoChange.isSelected() || res.text !== rawText)) { keptLines.push(item.fullLine); } } var freshLines = listTextarea.getValue().split('\n'); var stopIndex = -1; for (var x = 0; x < freshLines.length; x++) { if (freshLines[x] === '####STOP') { stopIndex = x; break; } } if (stopIndex > -1) { var topChunk = freshLines.slice(0, stopIndex); var botChunk = freshLines.slice(stopIndex + 1); var processedSet = new Set(batchTitles.map(t => t.fullLine)); var newTop = topChunk.filter(l => !processedSet.has(l)); var newList = newTop.concat(['####STOP']).concat(botChunk).concat(keptLines); listTextarea.setValue(newList.join('\n')); updateListCount(); } } catch (e) { console.error(e); setStatus('Batch error: ' + e, 'error'); break; } } isRunning = false; toggleUI(false); WorkerEngine.destroy(); btnPreParse.setLabel('Pre-parse'); if (listTextarea.getValue().startsWith('####STOP')) setStatus('Pre-parse done!'); else setStatus('Stopped'); } btnPreParse.on('click', runPreParseBatch); function loadNextPage() { if (!isRunning) return; var allLines = listTextarea.getValue().split('\n'); var listChanged = false; var stopCommand = false; while (allLines.length > 0) { var line = allLines[0]; if (line === '####STOP') { stopCommand = true; break; } if (line.startsWith('####') || line === "") { allLines.shift(); listChanged = true; } else { break; } } if (listChanged) { listTextarea.setValue(allLines.join('\n')); updateListCount(); } if (stopCommand) { btnPower.emit('click'); setStatus("Stopped by ####STOP"); return; } if (allLines.length === 0) { btnPower.emit('click'); setStatus("Done!"); return; } var raw = allLines[0]; var parts = raw.split('|'); currentTitle = parts[0].trim(); baseRevId = 0; originalWikitext = ""; if (!currentTitle) { removeTopLine(); loadNextPage(); return; } currentVars = {}; currentVars['$xx'] = currentTitle; for (var i = 1; i < parts.length; i++) currentVars['$x' + String.fromCharCode(64 + i)] = parts[i]; setStatus('Loading...', 'working'); btnSave.setDisabled(true); btnPreview.setDisabled(true); btnDiff.setDisabled(true); btnSkip.setDisabled(true); Editor.setDisabled(true); $titleLink.attr('href', mw.util.getUrl(currentTitle)).text(currentTitle); $editorHeader.removeClass('wa-dirty'); $visualOut.empty(); Editor.setValue('Loading...'); $infoContainer.empty(); currentPageExists = false; btnWatch.setDisabled(true); if (CAN_MOVE || IS_ADMIN) updateInterfaceMode(); var badCats = inpSkipCategories.getValue().split('|').map(s => s.trim()).filter(s => s); var reqCats = inpSkipNotCategories.getValue().split('|').map(s => s.trim()).filter(s => s); var api = new mw.Api(); var params = { action: 'query', prop: 'revisions|info' + (badCats.length + reqCats.length > 0 ? '|categories' : ''), titles: currentTitle, rvprop: 'content|timestamp|ids', rvslots: 'main', inprop: 'watched', cllimit: 'max' }; var rMode = redirMode.findSelectedItem().getData(); if (rMode === 'follow') params.redirects = 1; return api.get(params).then(async function(data) { var pid = Object.keys(data.query.pages)[0]; var page = data.query.pages[pid]; currentPageExists = !page.missing && !page.invalid; var pageCats = new Set((page.categories || []).map(c => c.title.replace(/^[^:]+:/, '').trim())); if (badCats.some(c => pageCats.has(c))) { setStatus('Skip: cat blacklist'); removeTopLine(); loadNextPage(); return; } if (reqCats.length > 0 && !reqCats.some(c => pageCats.has(c))) { setStatus('Skip: cat whitelist'); removeTopLine(); loadNextPage(); return; } if (rMode === 'follow' && data.query.redirects) { currentTitle = page.title; $titleLink.attr('href', mw.util.getUrl(currentTitle)).text(currentTitle); mw.notify('Redirect followed to: ' + currentTitle); } if (rMode === 'skip' && page.redirect !== undefined) { removeTopLine(); loadNextPage(); return; } var skipMode = radSkipExist.findSelectedItem().getData(); if (pid === "-1") { if (skipMode === 'missing') { removeTopLine(); loadNextPage(); return; } originalWikitext = ""; baseRevId = 0; } else { if (skipMode === 'exists') { removeTopLine(); loadNextPage(); return; } originalWikitext = page.revisions[0].slots.main['*']; baseRevId = page.revisions[0].revid; } if (page.revisions && page.revisions.length > 0) { var rev = page.revisions[0]; var ts = new Date(rev.timestamp).toISOString().replace('T', ' ').substring(0, 16); $infoContainer.empty().append('Last edit: ' + ts + ' | ', $('<a>').attr('href', mw.util.getUrl(currentTitle, { action: 'history' })).attr('target', '_blank').text('history')); } btnWatch.setDisabled(!currentPageExists); if (page.watched !== undefined) btnWatch.setIcon('unStar'); else btnWatch.setIcon('star'); if (CAN_MOVE || IS_ADMIN) { updateInterfaceMode(); if (togAdminEnable.getValue()) { Editor.setValue(originalWikitext); renderCurrentView(); setStatus('Ready (Page actions)'); return; } } processPageContent(); }).catch(function(e) { setStatus('API error', 'error'); alert('Load error: ' + e); btnPower.emit('click'); }); } async function fetchWithContinue(api, params) { var allTitles = new Set(); var continueToken = {}; var safetyLimit = FETCH_SAFETY_LIMIT; var count = 0; isFetching = true; btnAdd.setLabel('Cancel fetch'); $fetchStatus.text('Fetching...').show(); try { while (isFetching && count < safetyLimit) { var merged = Object.assign({}, params, continueToken); var data = await api.get(merged); var batch = []; if (data.watchlistraw) batch = data.watchlistraw; else if (data.query) { if (data.query.pages) batch = Object.values(data.query.pages); else if (data.query.categorymembers) batch = data.query.categorymembers; else if (data.query.backlinks) batch = data.query.backlinks; else if (data.query.embeddedin) batch = data.query.embeddedin; else if (data.query.imageusage) batch = data.query.imageusage; else if (data.query.search) batch = data.query.search; else if (data.query.allpages) batch = data.query.allpages; else if (data.query.usercontribs) batch = data.query.usercontribs; else if (data.query.pageswithprop) batch = data.query.pageswithprop; } if (batch.length > 0) { batch.forEach(item => { if (item.title) allTitles.add(item.title); }); count = allTitles.size; $fetchStatus.text('Fetched ' + count + '...'); } if (data.continue) continueToken = data.continue; else break; } } catch (e) { alert("Fetch interrupted: " + e); } isFetching = false; btnAdd.setLabel('Add to list').setDisabled(false); $fetchStatus.text('Added ' + allTitles.size + ' pages').delay(3000).fadeOut(); if (allTitles.size > 0) { hasNewSources = true; checkSummaryWarning(); } return Array.from(allTitles); } function toggleUI(d) { if (d) { btnPower.setLabel('Stop').setIcon('power').setFlags(['destructive']); } else { btnPower.setLabel('Start').setIcon('power').clearFlags().setFlags(['primary', 'progressive']); if (PERMS.allowBot) togAutoSave.setValue(false); } toggleConfig(d); btnSort.setDisabled(d); btnDedup.setDisabled(d); btnClear.setDisabled(d); btnSaveProj.setDisabled(d); btnLoadProj.setDisabled(d); btnSkip.setDisabled(!d); btnSave.setDisabled(true); listTextarea.setReadOnly(d); if (d) listTextarea.$element.addClass('wa-list-running'); else listTextarea.$element.removeClass('wa-list-running'); } function resetPanels() { Editor.setValue(''); $titleLink.text('Page content').removeAttr('href'); $editorHeader.removeClass('wa-dirty'); setStatus('Ready'); currentTitle = null; $visualOut.html('<div style="color:#aaa; text-align:center; margin-top:50px;">Ready...</div>'); $infoContainer.empty(); btnWatch.setDisabled(true); Editor.setDisabled(true); currentPageExists = false; if (CAN_MOVE || IS_ADMIN) updateInterfaceMode(); toggleUI(false); updateListCount(); if (autoSaveTimer) clearTimeout(autoSaveTimer); } function arrayMove(arr, old_index, new_index) { if (new_index >= arr.length) { var k = new_index - arr.length + 1; while (k--) arr.push(undefined); } arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); } function updateRuleButtons() { rulesRegistry.forEach(function(item, idx) { item.btnUp.setDisabled(idx === 0); item.btnDown.setDisabled(idx === rulesRegistry.length - 1); }); } function addRule() { var row = $('<div>').addClass('wa-rule-row'); var controls = $('<div>').addClass('wa-rule-controls'); var btnUp = new OO.ui.ButtonWidget({ icon: 'collapse', framed: false, title: 'Move up', classes: ['wa-rule-btn'] }); var btnDown = new OO.ui.ButtonWidget({ icon: 'expand', framed: false, title: 'Move down', classes: ['wa-rule-btn'] }); controls.append(btnUp.$element, btnDown.$element); var contentDiv = $('<div>').addClass('wa-rule-content'); var f = new OO.ui.TextInputWidget({ placeholder: 'Find' }); var r = new OO.ui.TextInputWidget({ placeholder: 'Replace' }); var reg = new OO.ui.ToggleSwitchWidget(); var fl = new OO.ui.TextInputWidget({ value: 'gmu', disabled: true }).toggle(false); var btnEnable = new OO.ui.ButtonWidget({ icon: 'power', framed: false, title: 'Toggle rule', flags: ['progressive'] }); var isRuleActive = true; var btnFunc = new OO.ui.ButtonWidget({ icon: 'code', framed: false, title: 'Toggle JS mode', disabled: true }); var isRuleFunc = false; var toggleRule = function(forceVal) { var val = (forceVal !== undefined) ? forceVal : !isRuleActive; isRuleActive = val; row.css('opacity', isRuleActive ? 1 : 0.5); if (isRuleActive) btnEnable.setFlags(['progressive']); else btnEnable.clearFlags(); }; btnEnable.on('click', function() { toggleRule(); }); var toggleFunc = function(forceVal) { var val = (forceVal !== undefined) ? forceVal : !isRuleFunc; isRuleFunc = val; if (isRuleFunc) { btnFunc.setFlags(['progressive']); r.$input.attr('placeholder', 'return match.toUpperCase();'); } else { btnFunc.clearFlags(); r.$input.attr('placeholder', 'Replace'); } }; btnFunc.on('click', function() { toggleFunc(); }); btnFunc.toggle(false); reg.on('change', function(v) { fl.setDisabled(!v); fl.toggle(v); btnFunc.setDisabled(!v); if (!v) { btnFunc.toggle(false); if (isRuleFunc) toggleFunc(false); } else btnFunc.toggle(true); }); var del = new OO.ui.ButtonWidget({ icon: 'trash', flags: 'destructive', framed: false }); del.on('click', function() { row.fadeOut(200, function() { row.remove(); rulesRegistry = rulesRegistry.filter(x => x.row !== row); updateRuleButtons(); }); }); contentDiv.append(f.$element, $('<div>').css('margin-top', '3px').append(r.$element), $('<div>').addClass('wa-rule-opt-row').append($('<div>').css({ 'display': 'flex', 'align-items': 'center' }).append($('<div>').css({ 'display': 'flex', 'align-items': 'center' }).append($('<span>').text('Regex: ').css({ 'font-size': '0.8em', 'margin-right': '4px' }), reg.$element, fl.$element.css({ 'width': '50px', 'margin-left': '5px' })), btnFunc.$element.css('margin-left', '10px')), $('<div>').css('display', 'flex').append(btnEnable.$element, del.$element))); row.append(controls, contentDiv); $rulesList.append(row); var ruleItem = { row: row, find: f, rep: r, regex: reg, flags: fl, btnUp: btnUp, btnDown: btnDown, enable: btnEnable, del: del, btnFunc: btnFunc, isActive: function() { return isRuleActive; }, setActive: toggleRule, isFunc: function() { return isRuleFunc; }, setFunc: toggleFunc }; rulesRegistry.push(ruleItem); btnUp.on('click', function() { var idx = rulesRegistry.indexOf(ruleItem); if (idx > 0) { var prevRow = rulesRegistry[idx - 1].row; row.fadeOut(150, function() { row.insertBefore(prevRow).fadeIn(150).addClass('wa-highlight'); setTimeout(function() { row.removeClass('wa-highlight'); }, 500); }); arrayMove(rulesRegistry, idx, idx - 1); updateRuleButtons(); } }); btnDown.on('click', function() { var idx = rulesRegistry.indexOf(ruleItem); if (idx < rulesRegistry.length - 1) { var nextRow = rulesRegistry[idx + 1].row; row.fadeOut(150, function() { row.insertAfter(nextRow).fadeIn(150).addClass('wa-highlight'); setTimeout(function() { row.removeClass('wa-highlight'); }, 500); }); arrayMove(rulesRegistry, idx, idx + 1); updateRuleButtons(); } }); updateRuleButtons(); } btnAddRule.on('click', addRule); addRule(); togWikiTypos.on('change', function(v) { if (v) { if (wikiTypos.length > 0) lblWikiStatus.text(wikiTypos.length + ' rules loaded (Cached)'); else { lblWikiStatus.text('Fetching...'); togWikiTypos.setDisabled(true); new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgFormattedNamespaces')[4] + ':AutoWikiBrowser/Typos', rvprop: 'content', rvslots: 'main', formatversion: 2 }).then(function(d) { var page = d.query.pages[0]; if (!page.missing) { wikiTypos = parseTypoContent(page.revisions[0].slots.main.content); lblWikiStatus.text(wikiTypos.length + ' rules loaded'); } else { lblWikiStatus.text('Page not found'); togWikiTypos.setValue(false); } }).catch(function() { lblWikiStatus.text('Error'); togWikiTypos.setValue(false); }).always(function() { togWikiTypos.setDisabled(false); }); } } else lblWikiStatus.text('Rules inactive'); }); btnLoadLocal.on('click', function() { $typoInput.click(); }); $typoInput.on('change', function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(evt) { localTypos = parseTypoContent(evt.target.result); lblLocalStatus.text(localTypos.length + ' local rules loaded'); btnClearLocal.setDisabled(false); }; reader.readAsText(file); $typoInput.val(''); }); btnClearLocal.on('click', function() { localTypos = []; lblLocalStatus.text('No local rules'); btnClearLocal.setDisabled(true); }); btnLoadLib.on('click', function() { $libInput.click(); }); btnRemoveLib.on('click', function() { currentLibrary = { name: null, code: null }; updateLibUI(); }); $libInput.on('change', function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(evt) { currentLibrary = { name: file.name, code: evt.target.result }; updateLibUI(); }; reader.readAsText(file); $libInput.val(''); }); btnPower.on('click', function() { hasNewSources = false; checkSummaryWarning(); if (!isRunning) { if (SAVED_SESSION === 0) mw.track('stats.mediawiki_gadget_wAwB_total'); isRunning = true; toggleUI(true); loadNextPage(); } else { if (SAVED_RUN > 0) { mw.track('stats.mediawiki_gadget_wAwB_saved_total', SAVED_RUN, { wiki: WIKI }); SAVED_SESSION += SAVED_RUN; SAVED_RUN = 0; } isRunning = false; toggleUI(false); WorkerEngine.destroy(); resetPanels(); } }); inputSummary.on('change', function() { checkSummaryWarning(); }); btnSkip.on('click', function() { if (Editor.getValue() === 'Loading...') return; removeTopLine(); loadNextPage(); }); btnDiff.on('click', function() { currentViewMode = 'diff'; if (currentTitle) renderDiff(); }); btnPreview.on('click', function() { currentViewMode = 'preview'; if (currentTitle) renderPreview(); }); btnSave.on('click', function() { if (Editor.getValue() === 'Loading...' || !currentTitle) return; if (autoSaveTimer) clearTimeout(autoSaveTimer); btnSave.setDisabled(true); setStatus('Saving...', 'working'); var effectiveDelay = PERMS.saveDelay || 0; if (effectiveDelay > 0) setStatus('Throttling (' + (effectiveDelay / 1000) + 's)...', 'working'); setTimeout(function() { if (effectiveDelay > 0) setStatus('Saving...', 'working'); var summary = inputSummary.getValue() + SUMMARY_SUFFIX; new mw.Api().postWithToken('csrf', { action: 'edit', title: currentTitle, text: Editor.getValue(), summary: summary, minor: chkMinor.isSelected(), baserevid: baseRevId, bot: PERMS.allowBot, tags: DO_TAG ? APP_NAME : undefined }).then(function() { SAVED_RUN += 1; removeTopLine(); loadNextPage(); }).catch(function(c) { btnSave.setDisabled(false); setStatus('Save error', 'error'); alert('Save failed: ' + c); }); }, effectiveDelay); }); btnWatch.on('click', function() { var isWatched = btnWatch.getIcon() === 'unStar'; new mw.Api()[isWatched ? 'unwatch' : 'watch'](currentTitle).then(function() { btnWatch.setIcon(isWatched ? 'star' : 'unStar'); mw.notify(isWatched ? 'Unwatched' : 'Watched'); }); }); btnAdd.on('click', function() { if (isFetching) { isFetching = false; btnAdd.setDisabled(true).setLabel('Cancelling...'); return; } try { var mode = srcSelect.getValue(), q = srcInput.getValue().trim(); if (mode !== 'watchlist' && mode !== 'usercontribs' && mode !== 'pageswithprop' && !q) { alert('Query empty'); return; } var nsIds = ($nsSelect.val() || []).map(v => parseInt(v)); var nsStr = nsIds.join('|'); var api = new mw.Api(), promises = []; if (mode === 'cat') promises.push(fetchWithContinue(api, { action: 'query', list: 'categorymembers', cmtitle: mw.Title.newFromText(q, 14) ? mw.Title.newFromText(q, 14).getPrefixedText() : 'Category:' + q, cmnamespace: nsStr, cmtype: (chkCatPages.isSelected() ? 'page|' : '') + (chkCatSub.isSelected() ? 'subcat|' : '') + (chkCatFile.isSelected() ? 'file' : ''), cmlimit: 'max' })); else if (mode === 'linksto') { if (chkLinkWiki.isSelected()) promises.push(fetchWithContinue(api, { action: 'query', list: 'backlinks', bltitle: q, blnamespace: nsStr, bllimit: 'max', blfilterredir: dropLinkRedir.getValue(), blredirect: chkLinkToRedir.isSelected() })); if (chkLinkTrans.isSelected()) promises.push(fetchWithContinue(api, { action: 'query', list: 'embeddedin', eititle: q, einamespace: nsStr, eilimit: 'max', eifilterredir: dropLinkRedir.getValue() })); if (chkLinkImg.isSelected()) promises.push(fetchWithContinue(api, { action: 'query', list: 'imageusage', iutitle: q, iunamespace: nsStr, iulimit: 'max', iufilterredir: dropLinkRedir.getValue() })); } else if (mode === 'linkson') promises.push(fetchWithContinue(api, { action: 'query', generator: 'links', titles: q, gplnamespace: nsStr, gpllimit: 'max', prop: 'info' })); else if (mode === 'prefix') promises.push(fetchWithContinue(api, { action: 'query', list: 'allpages', apprefix: q, apnamespace: nsIds[0] || 0, aplimit: 'max' })); else if (mode === 'watchlist') promises.push(fetchWithContinue(api, { action: 'query', list: 'watchlistraw', wrnamespace: nsStr, wrlimit: 'max' })); else if (mode === 'search') promises.push(fetchWithContinue(api, { action: 'query', list: 'search', srsearch: q, srnamespace: nsStr, srlimit: 'max' })); else if (mode === 'usercontribs') promises.push(fetchWithContinue(api, { action: 'query', list: 'usercontribs', ucuser: srcInputUser.getValue(), ucstart: srcInputStartDate.getValue(), ucend: srcInputEndDate.getValue(), ucdir: 'newer', uclimit: 'max', ucnamespace: nsStr, ucprop: 'title' })); else if (mode === 'pageswithprop') promises.push(fetchWithContinue(api, { action: 'query', list: 'pageswithprop', pwppropname: srcDropProp.getValue(), pwplimit: 'max' })); Promise.all(promises).then(function(res) { var list = new Set(); res.forEach(titles => titles.forEach(t => list.add(t))); var currentVal = listTextarea.getValue(); var newVal = Array.from(list).join('\n'); listTextarea.setValue(currentVal ? currentVal + '\n' + newVal : newVal); mw.notify('Added ' + list.size + ' pages'); }).catch(e => alert('Error: ' + e)); } catch (e) { alert("Fetch error: " + e); } }); btnSort.on('click', function() { var v = listTextarea.getValue(); if (v) { var lines = getNormalizedList(v); lines.sort((a, b) => sortAsc ? a.localeCompare(b) : b.localeCompare(a)); listTextarea.setValue(lines.join('\n')); sortAsc = !sortAsc; } }); btnDedup.on('click', function() { var v = listTextarea.getValue(); if (v) listTextarea.setValue(getDeduplicatedList(v).join('\n')); }); btnClear.on('click', function() { listTextarea.setValue(''); }); btnSaveProj.on('click', function() { try { var currentMode = srcSelect.getValue(); if (['watchlist', 'usercontribs', 'pageswithprop'].indexOf(currentMode) === -1) queryCache[currentMode] = srcInput.getValue(); var saveExcludes = {}; for (var k in protCheckboxes) saveExcludes[k] = protCheckboxes[k].isSelected(); var data = { version: APP_VERSION, library: currentLibrary, source: { activeMode: currentMode, namespaces: ($nsSelect.val() || []).map(v => parseInt(v)), modes: { cat: { query: queryCache['cat'] || '', options: { pages: chkCatPages.isSelected(), sub: chkCatSub.isSelected(), file: chkCatFile.isSelected() } }, linksto: { query: queryCache['linksto'] || '', options: { wiki: chkLinkWiki.isSelected(), trans: chkLinkTrans.isSelected(), img: chkLinkImg.isSelected(), redir: dropLinkRedir.getValue(), toRedir: chkLinkToRedir.isSelected() } }, linkson: { query: queryCache['linkson'] || '' }, prefix: { query: queryCache['prefix'] || '' }, watchlist: { query: '' }, search: { query: queryCache['search'] || '' }, usercontribs: { options: { user: srcInputUser.getValue(), start: srcInputStartDate.getValue(), end: srcInputEndDate.getValue() } }, pageswithprop: { options: { prop: srcDropProp.getValue() } } } }, settings: { redir: redirMode.findSelectedItem().getData(), skipLogic: radSkipExist.findSelectedItem().getData(), skipNoChange: chkSkipNoChange.isSelected(), minor: chkMinor.isSelected() }, filters: { contains: { val: inpSkipContains.getValue(), regex: togSkipContainsRegex.getValue() }, notContains: { val: inpSkipNotContains.getValue(), regex: togSkipNotContainsRegex.getValue() }, categories: { skip: inpSkipCategories.getValue(), require: inpSkipNotCategories.getValue() } }, rules: rulesRegistry.map(r => ({ find: r.find.getValue(), replace: r.rep.getValue(), regex: r.regex.getValue(), flags: r.flags.getValue(), enabled: r.isActive(), isFunc: r.isFunc() })), scripts: { pre: txtPreScript.getValue(), post: txtPostScript.getValue() }, processing: { summary: inputSummary.getValue(), list: listTextarea.getValue() }, protection: { mode: dropProtMode.getValue(), excludes: saveExcludes, target: (radTargetSet.findSelectedItem() || { getData: () => null }).getData(), templateFilter: inpTemplateFilter.getValue() } }; var a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 1)], { type: "application/json" })); a.download = "wawb_project.json"; a.click(); } catch (e) { alert("Save error: " + e); } }); btnLoadProj.on('click', function() { $fileInput.click(); }); $fileInput.on('change', function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(evt) { try { var data = JSON.parse(evt.target.result); isLoadingProject = true; if (data.source) { if (data.source.namespaces) $nsSelect.val(data.source.namespaces.map(String)); if (data.source.modes) { var m = data.source.modes; queryCache = {}; for (var key in m) if (m[key].query !== undefined) queryCache[key] = m[key].query; if (m.cat && m.cat.options) { chkCatPages.setSelected(m.cat.options.pages); chkCatSub.setSelected(m.cat.options.sub); chkCatFile.setSelected(m.cat.options.file); } if (m.linksto && m.linksto.options) { chkLinkWiki.setSelected(m.linksto.options.wiki); chkLinkTrans.setSelected(m.linksto.options.trans); chkLinkImg.setSelected(m.linksto.options.img); dropLinkRedir.setValue(m.linksto.options.redir); chkLinkToRedir.setSelected(m.linksto.options.toRedir); } if (m.usercontribs && m.usercontribs.options) { srcInputUser.setValue(m.usercontribs.options.user); srcInputStartDate.setValue(m.usercontribs.options.start); srcInputEndDate.setValue(m.usercontribs.options.end); } if (m.pageswithprop && m.pageswithprop.options) srcDropProp.setValue(m.pageswithprop.options.prop); } if (data.source.activeMode) { srcSelect.setValue(data.source.activeMode); isLoadingProject = false; srcSelect.emit('change', data.source.activeMode); isLoadingProject = true; } } if (data.settings) { redirMode.selectItemByData(data.settings.redir); chkSkipNoChange.setSelected(data.settings.skipNoChange); radSkipExist.selectItemByData(data.settings.skipLogic); if (data.settings.minor !== undefined) chkMinor.setSelected(data.settings.minor); } if (data.protection) { dropProtMode.setValue(data.protection.mode); for (var k in data.protection.excludes) if (protCheckboxes[k]) protCheckboxes[k].setSelected(data.protection.excludes[k]); if (data.protection.target) radTargetSet.selectItemByData(data.protection.target); if (data.protection.templateFilter) inpTemplateFilter.setValue(data.protection.templateFilter); } if (data.library) { currentLibrary = data.library; updateLibUI(); } if (data.filters) { inpSkipContains.setValue(data.filters.contains.val); togSkipContainsRegex.setValue(data.filters.contains.regex); inpSkipNotContains.setValue(data.filters.notContains.val); togSkipNotContainsRegex.setValue(data.filters.notContains.regex); } if (data.filters.categories) { inpSkipCategories.setValue(data.filters.categories.skip); inpSkipNotCategories.setValue(data.filters.categories.require); } rulesRegistry.forEach(r => r.row.remove()); rulesRegistry = []; $rulesList.empty(); if (data.rules) data.rules.forEach(r => { addRule(); var last = rulesRegistry[rulesRegistry.length - 1]; last.find.setValue(r.find); last.rep.setValue(r.replace); last.regex.setValue(r.regex); last.flags.setValue(r.flags); last.flags.setDisabled(!r.regex); last.setActive(r.enabled); if (r.isFunc) last.setFunc(true); }); if (rulesRegistry.length === 0) addRule(); if (data.scripts) { txtPreScript.setValue(data.scripts.pre); txtPostScript.setValue(data.scripts.post); } if (data.processing) { inputSummary.setValue(data.processing.summary); listTextarea.setValue(data.processing.list); } isLoadingProject = false; setStatus('Project loaded'); } catch (err) { alert("Error: " + err); } $fileInput.val(''); }; reader.readAsText(file); }); if (CAN_MOVE || IS_ADMIN) { togAdminEnable.on('change', function(val) { if (!currentTitle) { updateInterfaceMode(); return; } if (val) { Editor.setValue(originalWikitext); updateInterfaceMode(); renderDiff(); setStatus('Ready (Page actions)'); } else processPageContent(); }); } if (CAN_MOVE) { btnAdminMove.on('click', function() { if (!currentVars['$xA']) { mw.notify('Variable $xA not set', { type: 'error' }); return; } new mw.Api().postWithToken('csrf', { action: 'move', from: currentTitle, to: currentVars['$xA'], reason: inputSummary.getValue() + SUMMARY_SUFFIX, movetalk: chkMovTalk.isSelected(), movesubpages: chkMovSub.isSelected(), noredirect: chkMovRedirect.isSelected() }).then(function() { removeTopLine(); loadNextPage(); }).catch(e => alert('Move failed: ' + e)); }); } if (IS_ADMIN) { btnAdminDel.on('click', function() { new mw.Api().postWithToken('csrf', { action: 'delete', title: currentTitle, reason: inputSummary.getValue() + SUMMARY_SUFFIX }).then(function() { if (chkDelTalk.isSelected()) new mw.Api().postWithToken('csrf', { action: 'delete', title: mw.Title.newFromText(currentTitle).getTalkPage().getPrefixedText(), reason: 'Talk page of deleted page' }); removeTopLine(); loadNextPage(); }).catch(e => alert('Delete failed: ' + e)); }); btnAdminProt.on('click', function() { var protections = []; if (dropProtEdit.getValue()) protections.push('edit=' + dropProtEdit.getValue()); if (dropProtMove.getValue()) protections.push('move=' + dropProtMove.getValue()); new mw.Api().postWithToken('csrf', { action: 'protect', title: currentTitle, protections: protections.join('|'), expiry: inpProtExpiry.getValue() || 'infinite', reason: inputSummary.getValue() + SUMMARY_SUFFIX }).then(function() { removeTopLine(); loadNextPage(); }).catch(e => alert('Protect failed: ' + e)); }); } Editor.init(); resetPanels(); }); $(window).on('beforeunload', function() { return "You have unsaved work."; }); }).catch(e => console.error("wAwB Loader Error:", e)); //</nowiki> k97y7ukggj94tywlzrkicd84935nc0x User:Laerdon/ConvoWizard.js 2 172222 740018 732256 2026-05-01T15:29:29Z Laerdon 70887 740018 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); try { return JSON.parse(text); } catch { return {}; } } catch { return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> 7okx55pztcah4ihmthez32j4psjj61s 740022 740018 2026-05-01T15:57:09Z Laerdon 70887 740022 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); console.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); console.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> ovsz2p21jixld5h3jzz7wk6fn7z9zvd 740023 740022 2026-05-01T15:57:42Z Laerdon 70887 740023 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); console.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); console.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> s03a6wee73z3gj9jhq65fizq9y11u3i 740024 740023 2026-05-01T15:58:59Z Laerdon 70887 740024 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); nativeConsole.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); nativeConsole.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } catch (err) { console.warn(`[${NAME}] request failed for ${route}:`, err); return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> gqs3nqhf6blhg26t1docydtkqnp9kif 740051 740024 2026-05-01T17:49:44Z Laerdon 70887 740051 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://craft.infosci.cornell.edu/extension'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); nativeConsole.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); nativeConsole.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } catch (err) { console.warn(`[${NAME}] request failed for ${route}:`, err); return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> kja8tstbas61fu3su10xu48a30ozl6v 740052 740051 2026-05-01T17:51:17Z Laerdon 70887 740052 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); nativeConsole.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); nativeConsole.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } catch (err) { console.warn(`[${NAME}] request failed for ${route}:`, err); return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> ftcww4kvrmxid40orbuts40gnt5co50 740053 740052 2026-05-01T17:51:39Z Laerdon 70887 740053 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); nativeConsole.log(`[${NAME}] response from ${route}: status=${res.status}`, text); try { const data = JSON.parse(text); nativeConsole.log(`[${NAME}] json from ${route}:`, data); return data; } catch (err) { console.warn(`[${NAME}] non-json response from ${route}:`, text); return {}; } } catch (err) { console.warn(`[${NAME}] request failed for ${route}:`, err); return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> gqs3nqhf6blhg26t1docydtkqnp9kif User:Sam Sailor/test.js/comrade-lib.js 2 175022 739981 739969 2026-05-01T12:49:15Z Sam Sailor 26820 Test 739981 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log(oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 9bwsi3mvqb7gjd1qkwh408mx79v2hl3 739982 739981 2026-05-01T12:51:31Z Sam Sailor 26820 Test 739982 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> npw5s2vr04tsmn21yn2wz8kct4m8ap8 740021 739982 2026-05-01T15:43:34Z Sam Sailor 26820 Test 740021 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> diqlcff3qrwn5i09b6ktywp2zq4wi8o 740030 740021 2026-05-01T16:17:33Z Sam Sailor 26820 Test 740030 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 500) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> er70utjkrunwonr9pmkr3mrkpdkf6b6 740032 740030 2026-05-01T16:20:53Z Sam Sailor 26820 Test 740032 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 450) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 0lxei0uak0vyf0xdrq5plcko5kbd3eb 740033 740032 2026-05-01T16:22:29Z Sam Sailor 26820 Test 740033 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 3l2bffhb1seg4g4y5d2gxh41k2kk4zc 740035 740033 2026-05-01T16:28:04Z Sam Sailor 26820 Test 740035 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 300) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 5uyb83670z41i9agvegoi1wv7zpez4h 740036 740035 2026-05-01T16:30:24Z Sam Sailor 26820 Test 740036 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 350) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 7wc0x3br7w9311g4q0t1zuih4242vxu 740049 740036 2026-05-01T17:32:26Z Sam Sailor 26820 Test 740049 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 350) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').fadeOut(2000); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append( $('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?' ); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 3mxmvytrad4usnqw0trx3u4g1s2stqk 740133 740049 2026-05-02T08:45:14Z Sam Sailor 26820 Test 740133 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 4px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> ngur8wq6f3wqr378e1fwsxiaxeh044j 740134 740133 2026-05-02T08:53:45Z Sam Sailor 26820 Test 740134 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:') && !t.startsWith('Template:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 4px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 5ujbw6cze2v95tzy3nq0gvzjl7mh5lc 740135 740134 2026-05-02T08:59:13Z Sam Sailor 26820 Test 740135 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a').filter(function() { const title = $(this).attr('title'); return title && !title.startsWith('Talk:') && !title.startsWith('Template:'); }); const titles = $links.map((i, el) => $(el).attr('title')).get(); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 4px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 0z3nuolh0q6ekmgtq11y3xkyw9v5nzk 740136 740135 2026-05-02T09:04:01Z Sam Sailor 26820 Test 740136 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a').filter(function() { const title = $(this).attr('title') || ''; const href = $(this).attr('href') || ''; if (title.startsWith('Talk:') || title.startsWith('Template:')) return false; if (href.includes('Template:') || href.includes('Template_talk:')) return false; return true; }); const titles = $links.map((i, el) => $(el).attr('title')).get(); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 3px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> gxjttxqkuivabfgfesf7v8ymfxczrva 740137 740136 2026-05-02T09:11:18Z Sam Sailor 26820 Test 740137 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a').filter(function() { const title = $(this).attr('title') || ''; if (title.startsWith('Talk:') || title.startsWith('Template:') || title.startsWith('Category:')) { return false; } return true; }); const titles = $links.map((i, el) => $(el).attr('title')).get(); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 3px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> aze4qhrlhzh5qdy78ybyl1jmwkhc07v 740138 740137 2026-05-02T09:23:46Z Sam Sailor 26820 Test 740138 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a').filter(function() { const title = $(this).attr('title') || ''; if (title.startsWith('Talk:') || title.startsWith('Template:')) { return false; } return true; }); const titles = $links.map((i, el) => $(el).attr('title')).get(); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 3px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); if (oresClass.toLowerCase() === talkClass?.toLowerCase()) return; const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 2nrh40130va6x0zpjbc3668ibx6vp8k 740139 740138 2026-05-02T10:48:56Z Sam Sailor 26820 Test 740139 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ mw.util.addCSS(` .ambox-Orphan { display: block !important; } .comrade-container { box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 2px; padding: 10px 15px; margin: 10px 0; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; border: 1px solid #a2a9b1; background: #fcfcfc; line-height: 1.5; } .comrade-header { display: flex; align-items: center; justify-content: space-between; width: 100%; } .comrade-content { display: flex; flex-direction: column; gap: 5px; width: 100%; } .comrade-success { background-color: #d5f5e3 !important; border-left: 5px solid #27ae60 !important; color: #1b5e20 !important; } .comrade-nudge { background: #fff9ea; border-left: 5px solid #f0ad4e; } .comrade-info, .comrade-status { background: #eaf3ff; border-left: 5px solid #36c; } .comrade-container button:not(.comrade-dismiss) { background: #f8f9fa; border: 1px solid #a2a9b1; border-radius: 2px; padding: 2px 10px; cursor: pointer; font-weight: bold; font-size: 0.95em; margin-left: 10px; } .comrade-container button:not(.comrade-dismiss):hover { border-color: #36c; color: #36c; background: #fff; } .comrade-dismiss { background: transparent; border: none; color: #d33; font-weight: bold; cursor: pointer; font-size: 0.9em; } .comrade-dismiss:hover { text-decoration: underline; } .comrade-code-block { background: #f1f1f1; padding: 4px 8px; border-radius: 3px; font-family: monospace; display: inline-block; margin-top: 4px; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { let timerInterval; const api = new mw.Api(); const $links = $('#mw-pages li a').filter(function() { const title = $(this).attr('title') || ''; if (title.startsWith('Talk:') || title.startsWith('Template:')) { return false; } return true; }); const titles = $links.map((i, el) => $(el).attr('title')).get(); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); const totalPages = titles.length; let remainingTime = Math.ceil((totalPages * 400) / 1000); $btn.prop('disabled', true).text(`Auditing (${remainingTime}s)...`); timerInterval = setInterval(() => { remainingTime -= 1; if (remainingTime > 0) { $btn.text(`Auditing (${remainingTime}s)...`); } else { clearInterval(timerInterval); } }, 1000); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); clearInterval(timerInterval); $btn.text('Audit complete').prop('disabled', true); } catch (error) { clearInterval(timerInterval); console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`).first(); if (!$link.length) { return; } let label = ""; let style = "font-size: 0.85em; margin-left: 3px; font-weight: normal;"; if (talkClass !== "Stub" && talkClass !== "Unrated" && hasStub) { label = `${talkClass} (stub tag present) : ${predicted || "?"}`; style += " color: #f0ad4e;"; } else if (talkClass === "Stub" && predicted && predicted !== "Stub") { label = `Stub : ${predicted}`; style += " color: #27ae60;"; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += " color: #36c;"; } if (label) { const $parent = $link.parent(); $parent.find(".comrade-audit-label").remove(); const $span = $("<span>").addClass("comrade-audit-label").attr("style", style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Lead capitalization window.ComradeLib.checkLeadCapitalization = function(currentTitle) { if (mw.config.get("wgNamespaceNumber") !== 0) return; const titleElement = document.querySelector("#mw-content-text > div > p:not([class]) b"); if (!titleElement) return; const leadTitle = titleElement.textContent.trim(); const articleTitle = currentTitle.trim(); if (leadTitle !== articleTitle && leadTitle.toLowerCase() === articleTitle.toLowerCase()) { ComradeLib.renderLeadCapitalizationNudge(titleElement, leadTitle, articleTitle); } }; window.ComradeLib.renderLeadCapitalizationNudge = function(element, leadTitle, articleTitle) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Lead Capitalization:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text(`Lead title uses incorrect casing (${leadTitle}). Correct to `), $('<code>').text(articleTitle), '?'); const $btn = $('<button>').text('Fix lead casing').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; const escaped = leadTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`'''${escaped}'''`, 'g'); content = content.replace(searchRegex, `'''${articleTitle}'''`); await api.postWithToken('csrf', { action: 'edit', title: title, text: content, summary: `Fix bold lead title capitalization per [[MOS:LEADTITLE]]`, nocreate: true }); mw.notify('Lead capitalization corrected!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update lead title capitalization.', { type: 'error' }); console.error(e); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = async function() { const pageName = mw.config.get('wgPageName'); try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'ids', formatversion: 2 }); const page = pageData.query.pages[0]; const revId = page?.revisions?.[0]?.revid; if (!revId) return null; const scores = await ComradeLib.fetchOresData([String(revId)]); const pred = scores[String(revId)]; if (!pred) return null; return pred.charAt(0).toUpperCase() + pred.slice(1); } catch (e) { console.warn("Comrade: getOresPrediction failed to fetch via LiftWing", e); return null; } }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = await ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkRes.query.pages[0].revisions[0].content.match(/\|\s*\n?\s*class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].trim().toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } console.log("Comrade Quality Check ORES vs Talk:", oresClass, talkClass); const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (oresClass.toLowerCase() === talkClass?.toLowerCase() && !lingeringStub) return; if (lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); const message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } else if (talkPage.missing || !talkClass) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); const message = talkPage.missing ? `Talk page missing; ORES suggests <b>${oresClass}</b>. ` : `Talk page exists but is unassessed; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } else if (needsPromotionNudge) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); const message = `ORES suggests <b>${oresClass}</b>. `; const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } else if (needsHighQualNudge) { const $container = $('<div>').addClass('comrade-container comrade-info'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); const message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> cj7281t7na53i76oavhh2k0ch8k0vhd Odetari 0 175070 739995 739897 2026-05-01T13:34:18Z Cryptocurrency777 73698 739995 wikitext text/x-wiki #REDIRECT [[Narcistic Personality Disorder (song)]] [[File:Odetari picture.webp]] 0dhed0dte83viohtiomhi1sw9182ayu 739996 739995 2026-05-01T13:43:13Z Cryptocurrency777 73698 739996 wikitext text/x-wiki #REDIRECT [[Narcistic Personality Disorder (song)]] [[File:Odetari picture.webp|150px]] hyvaec8j7x0ao5x4sfr3sln8c0hmr6p DaniDV 0 175071 739980 739899 2026-05-01T12:42:43Z Cryptocurrency777 73698 739980 wikitext text/x-wiki ''{{InfoboxTest|DaniDV|image=TestDaniDV.jpg|alias=Darkphobia|years active=2024+|agent=-}}'' '''DaniDV''' (also known as Darkphobia), is a producer who made ''Decay'', duh {{Did you mean/box}} {{Welcome-copyright}} di85xyjcjq6eri4dx76mdj7kh4s80a8 739997 739980 2026-05-01T13:44:24Z Cryptocurrency777 73698 739997 wikitext text/x-wiki ''{{InfoboxTest|DaniDV|image=TestDaniDV.jpg|alias=Darkphobia|years active=2024+|agent=-}}'' '''DaniDV''' (also known as Darkphobia), is a producer who made a song called ''Decay''. He is known for EDM music. huh {{Did you mean/box}} {{Welcome-copyright}} s1pstqexji4ixvqna5j1cd92iyj7ln9 740000 739997 2026-05-01T13:56:07Z Cryptocurrency777 73698 740000 wikitext text/x-wiki ''{{InfoboxTest|DaniDV|image=TestDaniDV.jpg|alias=Darkphobia|years active=2024+|agent=-}}'' '''DaniDV''' (also known as Darkphobia), is a producer who made a song called ''[[Decay (song)|Decay]]''. He is known for EDM music. huh {{Did you mean/box}} {{Welcome-copyright}} tkmxvnkf1tt03mrotpn433q6bqhximt Anna Kournikova 0 175078 739974 2026-05-01T12:24:07Z Cryptocurrency777 73698 Created page with "{{Infobox person |name = Anna Kournikova |image = Anna Kournikova-Bagram Airfield 2009.jpg |caption = Kournikova at [[Bagram Air Base]] during a 2009 [[United Service Organization|USO]] tour |native_name = {{nobold|Анна Курникова}} |native_name_lang = ru |birth_date = {{birth date and age|df=y|1981|06|07}} |birth_place = Moscow, [[Russian Soviet Federative Socialist Republic|Russian SFSR]], Soviet Union |height = {{convert|1.73|m|ftin|abbr=on}} |partner = ..." 739974 wikitext text/x-wiki {{Infobox person |name = Anna Kournikova |image = Anna Kournikova-Bagram Airfield 2009.jpg |caption = Kournikova at [[Bagram Air Base]] during a 2009 [[United Service Organization|USO]] tour |native_name = {{nobold|Анна Курникова}} |native_name_lang = ru |birth_date = {{birth date and age|df=y|1981|06|07}} |birth_place = Moscow, [[Russian Soviet Federative Socialist Republic|Russian SFSR]], Soviet Union |height = {{convert|1.73|m|ftin|abbr=on}} |partner = [[Enrique Iglesias]] (2001–present) |module = {{Infobox tennis biography | embed=yes |fullname = |country = Russia |turnedpro = October 1995 |retired = May 2003 |plays = Right-handed (two-handed backhand) |careerprizemoney = US$3,584,662 |singlesrecord = {{tennis record|won=209|lost=129}} |singlestitles = 0 |highestsinglesranking = No. 8 (20 November 2000) |AustralianOpenresult = QF ([[2001 Australian Open – Women's singles|2001]]) |FrenchOpenresult = 4R ([[1998 French Open – Women's singles|1998]], [[1999 French Open – Women's singles|1999]]) |Wimbledonresult = SF ([[1997 Wimbledon Championships – Women's singles|1997]]) |USOpenresult = 4R ([[1996 US Open – Women's singles|1996]], [[1998 US Open – Women's singles|1998]]) |Othertournaments = yes |WTAChampionshipsresult = SF ([[2000 WTA Tour Championships|2000]]) |Olympicsresult = 1R ([[Tennis at the 1996 Summer Olympics – Women's singles|1996]]) |doublesrecord = 200–71 |doublestitles = 16 |highestdoublesranking = [[List of WTA number 1 ranked doubles tennis players|No. '''1''']] (22 November 1999) |AustralianOpenDoublesresult = '''W''' ([[1999 Australian Open – Women's doubles|1999]], [[2002 Australian Open – Women's doubles|2002]]) |FrenchOpenDoublesresult = F ([[1999 French Open – Women's doubles|1999]]) |WimbledonDoublesresult = SF ([[2000 Wimbledon Championships – Women's doubles|2000]], [[2002 Wimbledon Championships – Women's doubles|2002]]) |USOpenDoublesresult = QF ([[1996 US Open – Women's doubles|1996]], [[2002 US Open – Women's doubles|2002]]) |OthertournamentsDoubles = yes |WTAChampionshipsDoublesresult = '''W''' ([[1999 WTA Tour Championships – Doubles|1999]], [[2000 WTA Tour Championships – Doubles|2000]]) |Mixed = yes |mixedrecord = 24–14 |mixedtitles = |AustralianOpenMixedresult = SF ([[1997 Australian Open – Mixed doubles|1997]], [[2000 Australian Open – Mixed doubles|2000]]) |FrenchOpenMixedresult = QF ([[1997 French Open – Mixed doubles|1997]]) |WimbledonMixedresult = F ([[1999 Wimbledon Championships – Mixed doubles|1999]]) |USOpenMixedresult = F ([[2000 US Open – Mixed doubles|2000]]) }} }} '''Anna Sergeyevna Kournikova Iglesias''' ({{nee|'''Kournikova'''}}; {{langx|ru|Анна Сергеевна Курникова}}; {{IPA|ru|ˈanːə sʲɪrˈɡʲejɪvnə ˈkurnʲɪkəvə|lang|Anna_kournikova.ogg}}; born 7 June 1981) is a Russian model and television personality, and former professional tennis player. Her appearance <!-- Per WP:NPOV, do not label her appearance as "beauty" or anything else --> and celebrity status made her one of the best known tennis stars worldwide. At the peak of her fame, fans looking for images of Kournikova made her name one of the most common search strings on [[Google Search]].<ref name=2001EOY>{{cite web|url=http://www.google.com/press/zeitgeist2001.html|title=2001 Year-End Google Zeitgeist: Search patterns, trends, and surprises|access-date=8 July 2009}}</ref><ref name=2002EOY>{{cite web|url=http://www.google.com/press/zeitgeist2002.html|title=2002 Year-End Google Zeitgeist: Search patterns, trends, and surprises|access-date=8 July 2009}}</ref><ref name=2003EOY>{{cite web|url=http://www.google.com/intl/en/press/zeitgeist2003.html|title=2003 Year-End Google Zeitgeist: Search patterns, trends, and surprises|access-date=9 July 2009}}</ref> 3vhqqm3p1zolnwt4elakzsmvmzsgx65 Jumpstyle 0 175079 739976 2026-05-01T12:29:09Z Cryptocurrency777 73698 Created page with "'''Jumpstyle''' is a genre of music and dance that comes from Belgium and the Netherlands. It is considered a subcategory of EDM (Electronic Dance Music) and Eurodance. It came from the 90s and 200s but got revived in the 2020s. A good example would be Scooter’s "Jumping All Over the World,". [[Category:Music]]" 739976 wikitext text/x-wiki '''Jumpstyle''' is a genre of music and dance that comes from Belgium and the Netherlands. It is considered a subcategory of EDM (Electronic Dance Music) and Eurodance. It came from the 90s and 200s but got revived in the 2020s. A good example would be Scooter’s "Jumping All Over the World,". [[Category:Music]] rrg134b9c1nibxfvsjv0qnqq1kjz4dq 739977 739976 2026-05-01T12:30:19Z Cryptocurrency777 73698 739977 wikitext text/x-wiki '''Jumpstyle''' is a genre of music and dance that comes from Belgium and the Netherlands. It is considered a subcategory of EDM (Electronic Dance Music) and Eurodance. It came from the 90s and 200s but got revived in the 2020s. A good example would be Scooter’s "Jumping All Over the World,". JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP [[Category:Music]] ogat1az9na5rljw2rjnnnz99oj08cx0 739978 739977 2026-05-01T12:35:34Z Cryptocurrency777 73698 739978 wikitext text/x-wiki '''Jumpstyle''' is a genre of music and dance that comes from Belgium and the Netherlands. It is considered a subcategory of EDM (Electronic Dance Music) and Eurodance. It came from the 90s and 200s but got revived in the 2020s. A good example would be Scooter’s "Jumping All Over the World,". <span style="color:#000;font-family:Comic Sans MS;font-size:80%;border-radius:30px;background-color:#FCCCCF;padding:6px 7px 5px 7px">JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP JUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMPJUMP</span> [[Category:Music]] sjz0ew04w9h2vsfmjcmon0g9d0bn0nw File:TestDaniDV.jpg 6 175080 739979 2026-05-01T12:40:11Z Cryptocurrency777 73698 wu.weee drew it 739979 wikitext text/x-wiki == Summary == wu.weee drew it == Licensing == {{Fair use}} e91s2dnfl8a6mtvmzlzrnx4on1ct77o User:ToprakM/test.css 2 175081 739985 2026-05-01T13:13:32Z ToprakM 35316 Created page with "background-color: red;" 739985 css text/css background-color: red; 9z9tdr0fszlqar9tkt102o4s3fvygh8 739987 739985 2026-05-01T13:15:18Z ToprakM 35316 739987 css text/css body, #mw-page-base, #content { background-color: red !important; } ei59pbcqxhprp1yq58avwa1olv3cgn4 740025 739987 2026-05-01T16:08:19Z ToprakM 35316 740025 css text/css body, #mw-page-base, #content { background-color: red !important; } /* dark mode */ @media screen { html.skin-theme-clientpref-os, html.skin-theme-clientpref-night { background-color: purple !important; } 3otzvfrt0vaswxo7jh6qpbdtwn6vjs5 740026 740025 2026-05-01T16:08:56Z ToprakM 35316 740026 css text/css body, #mw-page-base, #content { background-color: red !important; } /* dark mode */ @media screen { html.skin-theme-clientpref-os, html.skin-theme-clientpref-night { background-color: purple !important; } } nvabzcvvkvityceetlstvxg2i04xfhp 740027 740026 2026-05-01T16:09:12Z ToprakM 35316 740027 css text/css /* dark mode */ @media screen { html.skin-theme-clientpref-os, html.skin-theme-clientpref-night { background-color: purple !important; } } pl0ew2hjyiqspuytot1ukyi6f5zwafa 740028 740027 2026-05-01T16:09:42Z ToprakM 35316 740028 css text/css @media screen { html.skin-theme-clientpref-night { background-color: purple !important; } html.skin-theme-clientpref-night { background-color: purple !important; } } ed96o9bfg5he234qj8wgrk9g4z0mlu6 740029 740028 2026-05-01T16:12:03Z ToprakM 35316 740029 css text/css body, #mw-page-base, #content { background-color: red !important; } @media screen { html.skin-theme-clientpref-night, html.skin-theme-clientpref-os { body, #mw-page-base, #content { background-color: purple !important; } } } jj76tmplib65a21873iit4ay7kd3g3a 740031 740029 2026-05-01T16:20:52Z ToprakM 35316 740031 css text/css /* 1. Varsayılan Durum (Light Mode) */ body, #mw-page-base, #content { background-color: red !important; } /* 2. Kullanıcı MANUEL olarak Gece Modunu (Night Mode) seçtiğinde */ html.skin-theme-clientpref-night body, html.skin-theme-clientpref-night #mw-page-base, html.skin-theme-clientpref-night #content { background-color: purple !important; } /* 3. Kullanıcı "OS Tercihi"ni seçtiğinde VE işletim sistemi gerçekten Gece Modunda olduğunda */ @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os body, html.skin-theme-clientpref-os #mw-page-base, html.skin-theme-clientpref-os #content { background-color: purple !important; } } cqr95imdga3m2ub39lw69zaexiggpne 740034 740031 2026-05-01T16:24:26Z ToprakM 35316 740034 css text/css body, #mw-page-base, #content { background-color: red !important; } html.skin-theme-clientpref-night body, html.skin-theme-clientpref-night #mw-page-base, html.skin-theme-clientpref-night #content { background-color: purple !important; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os body, html.skin-theme-clientpref-os #mw-page-base, html.skin-theme-clientpref-os #content { background-color: purple !important; } } i1iejd96ku3f5vvgoti19zaqvz28lzs File:TestDecayCover.jpeg 6 175082 739998 2026-05-01T13:49:06Z Cryptocurrency777 73698 fair use test 739998 wikitext text/x-wiki == Summary == fair use test == Licensing == {{Fair use}} sg92p7hcmrtlxviqbtwt49d20ckqsgt Decay (song) 0 175083 739999 2026-05-01T13:55:23Z Cryptocurrency777 73698 Created page with "{{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a song by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-X..." 739999 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a song by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref> 8bepcccrmtuo59bu57iitm75l979vt8 740006 739999 2026-05-01T14:20:43Z Cryptocurrency777 73698 740006 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a song by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref>. It contains elements of EDM and discusses topics like [[relationships]] and self harm. 5l3bknl7miatjp195qmyd4r9g5gyxri 740039 740006 2026-05-01T17:07:32Z Cryptocurrency777 73698 740039 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a song by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref>. It contains elements of EDM and discusses topics like [[relationships]] and self harm. Rox3s was meant to be a feature on this song, but he never showed up for the open. ioz98seke45b892utsxiqdy84vwooux 740042 740039 2026-05-01T17:14:38Z Cryptocurrency777 73698 740042 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a song by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref>. It contains elements of EDM and discusses topics like [[relationships]] and self harm. Rox3s was meant to be a feature on this song, but he never showed up for the open. The song has at least 150,000 plays on Spotify. gkbn3vtwzbrhgea7mvzroloeoitebm0 740043 740042 2026-05-01T17:15:30Z Cryptocurrency777 73698 740043 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=DaniDV, AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a single by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 7, 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref>. It contains elements of EDM and discusses topics like [[relationships]] and self harm. Rox3s was meant to be a feature on this song, but he never showed up for the open. The song has at least 150,000 plays on Spotify. s756943b06xnnyknjzyf4m3412qc21i 740056 740043 2026-05-01T18:37:11Z Cryptocurrency777 73698 740056 wikitext text/x-wiki {{Infobox song|Name=Decay|cover=TestDecayCover.jpeg|cover size=250px|Artist=[[DaniDV]], AfterDRK, Xaduma, Xyle, Anti-Light|Released=March 2025|Length=2:36|Writer=AfterDRK, Xaduma, Xyle, Anti-Light}} '''Decay''' is a single by AfterDRK, Xaduma, Xyle, and Anti-Light released in March 7, 2025. It was produced by [[DaniDV]]. It has a BPM of 145<ref>{{Citation | title=DECAY - AfterDRK, Anti-Light, DaniDV, Xyle, xaduma | url=https://tunebat.com/Info/DECAY-AfterDRK-Anti-Light-DaniDV-Xyle-xaduma/6bBm0o4a7bkTtvohndLh6A}}</ref>. It contains elements of EDM and discusses topics like [[relationships]] and self harm. Rox3s was meant to be a feature on this song, but he never showed up for the open. The song has at least 150,000 plays on Spotify. guig8u2f0fetgjk2nlihv13cbgpvx5d We Can't Be Friends (Wait for Your Love) 0 175084 740001 2026-05-01T14:01:04Z Cryptocurrency777 73698 Created page with "{{Infobox song|Name=We Can't Be Friends (Wait For Your Love)|Artist=Ariana Grande|Released=2024|Writer=* Ariana Grande * [[Max Martin]] * [[Ilya Salmanzadeh]]}} "'''We Can't Be Friends (Wait for Your Love)'''"<!-- do not add stylization here, refer to [[MOS: MUSICCAPS]] --> is a song by American singer-songwriter [[Ariana Grande]]. It was released through [[Republic Records]] on March 8, 2024, as the second single from her seventh studio album, ''Eternal Sunshine (a..." 740001 wikitext text/x-wiki {{Infobox song|Name=We Can't Be Friends (Wait For Your Love)|Artist=Ariana Grande|Released=2024|Writer=* Ariana Grande * [[Max Martin]] * [[Ilya Salmanzadeh]]}} "'''We Can't Be Friends (Wait for Your Love)'''"<!-- do not add stylization here, refer to [[MOS: MUSICCAPS]] --> is a song by American singer-songwriter [[Ariana Grande]]. It was released through [[Republic Records]] on March 8, 2024, as the second single from her seventh studio album, ''[[Eternal Sunshine (album)|Eternal Sunshine]]'' (2024). It is written and produced by Grande, [[Max Martin]] and [[Ilya Salmanzadeh]]. Built on an [[electropop]] beat, "We Can't Be Friends (Wait for Your Love)" is a [[Europop]], [[power pop]], [[synth-pop]], and [[synthwave]] song. The song's lyrics dually refer to the demise of a romantic relationship as well as Grande's relationship with the press. i2tig22buolyxhu95n5j6mo7gnzyuw1 740002 740001 2026-05-01T14:01:20Z Cryptocurrency777 73698 740002 wikitext text/x-wiki {{Infobox song|Name=We Can't Be Friends (Wait For Your Love)|Artist=Ariana Grande|Released=2024|Writer=* Ariana Grande * [[Max Martin]] * [[Ilya Salmanzadeh]]}} "'''We Can't Be Friends (Wait for Your Love)'''"<!-- do not add stylization here, refer to [[MOS: MUSICCAPS]] --> is a song by American singer-songwriter [[Ariana Grande]]. It was released through [[Republic Records]] on March 8, 2024, as the second single from her seventh studio album, ''[[Eternal Sunshine (album)|Eternal Sunshine]]'' (2024). It is written and produced by Grande, [[Max Martin]] and [[Ilya Salmanzadeh]]. Built on an [[electropop]] beat, "We Can't Be Friends (Wait for Your Love)" is a [[Europop]], [[power pop]], [[synth-pop]], and [[synthwave]] song. The song's lyrics dually refer to the demise of a romantic relationship as well as Grande's relationship with the press. [[Category:Music]] ibdoheknudx3uquwdz5nwuqgcs40j3p File:DiggyTest.jpg 6 175085 740003 2026-05-01T14:05:46Z Cryptocurrency777 73698 fair use 740003 wikitext text/x-wiki == Summary == fair use == Licensing == {{Fair use}} 1wgt6bicqvnlasoz382mqruylvek2pc Diggy 0 175086 740004 2026-05-01T14:18:22Z Cryptocurrency777 73698 Created page with "{{Infobox song|Name=Diggy|cover=DiggyTest.jpg|cover size=250px|Artist=Spencer Ludwig|Released=2016|Recorded=2016|Genre=Alternative|Writer=}} '''Diggy''' is a single released by Spencer Ludwig of Capital Cities. It was written by Spencer Ludwig. It was recorded and released by Ludwig in 2016 and contains trumpet elements. Its lyrics often reference dancing and showing off moves. The song was later featured in the video game ''Just Dance 2017''." 740004 wikitext text/x-wiki {{Infobox song|Name=Diggy|cover=DiggyTest.jpg|cover size=250px|Artist=Spencer Ludwig|Released=2016|Recorded=2016|Genre=Alternative|Writer=}} '''Diggy''' is a single released by Spencer Ludwig of Capital Cities. It was written by Spencer Ludwig. It was recorded and released by Ludwig in 2016 and contains trumpet elements. Its lyrics often reference dancing and showing off moves. The song was later featured in the video game ''Just Dance 2017''. fy9pptjok1es9xz7hmtr17511ubk9nh 740005 740004 2026-05-01T14:18:39Z Cryptocurrency777 73698 740005 wikitext text/x-wiki {{Infobox song|Name=Diggy|cover=DiggyTest.jpg|cover size=250px|Artist=Spencer Ludwig|Released=2016|Recorded=2016|Genre=Alternative|Writer=}} '''Diggy''' is a single released by [[Spencer Ludwig]] of Capital Cities. It was written by Spencer Ludwig. It was recorded and released by Ludwig in 2016 and contains trumpet elements. Its lyrics often reference dancing and showing off moves. The song was later featured in the video game ''Just Dance 2017''. s8s74te01pjam9h5br8y5zqals9bztr 740017 740005 2026-05-01T15:17:32Z Cryptocurrency777 73698 740017 wikitext text/x-wiki {{Infobox song|Name=Diggy|cover=DiggyTest.jpg|cover size=280px|Artist=Spencer Ludwig|Released=2016|Recorded=2016|Genre=Alternative|Writer=}} '''Diggy''' is a single released by [[Spencer Ludwig]] of Capital Cities. It was written by Spencer Ludwig. It was recorded and released by Ludwig in 2016 and contains trumpet elements. Its lyrics often reference dancing and showing off moves. The song was later featured in the video game ''Just Dance 2017''. 8u6edfamfp2bo884rdlgrjioosgbivc Kirby 0 175087 740009 2026-05-01T14:43:01Z Cryptocurrency777 73698 Created page with "{{Infobox video game|release=1992+|image=Kirby-Logo-New.svg|image size=150px|developer=Nintendo}} Kirby is a video game platformer series made by Nintendo in 1992. The main character is Kirby, and he is a pink, round creature who can copy abilities" 740009 wikitext text/x-wiki {{Infobox video game|release=1992+|image=Kirby-Logo-New.svg|image size=150px|developer=Nintendo}} Kirby is a video game platformer series made by Nintendo in 1992. The main character is Kirby, and he is a pink, round creature who can copy abilities jbzjisol6wut8b7r0pg3ks6yr7jc2cw Yak 0 175088 740010 2026-05-01T14:47:19Z Cryptocurrency777 73698 Created page with "A domestic bovine animal ...................... [[File:Yak in Khövsgöl 01.jpg|thumb]]" 740010 wikitext text/x-wiki A domestic bovine animal ...................... [[File:Yak in Khövsgöl 01.jpg|thumb]] q9calygvtm8lg1x58nczhjb6mt8nj2l Oregon 0 175089 740011 2026-05-01T14:49:47Z Cryptocurrency777 73698 Created page with "Like [[Alabama]], Oregon is one of the 50 states in [[USA]]" 740011 wikitext text/x-wiki Like [[Alabama]], Oregon is one of the 50 states in [[USA]] 1oujf6xl93ye171u17xb79zjnslwum5 Tesseract 0 175090 740012 2026-05-01T14:58:06Z Cryptocurrency777 73698 Created page with "[[File:8-cell-simple.gif]] a Tesseract is a 4D cube that has 8 cubic cells, 24 square faces, 32 edges, and 16 vertices {{Did you know}}" 740012 wikitext text/x-wiki [[File:8-cell-simple.gif]] a Tesseract is a 4D cube that has 8 cubic cells, 24 square faces, 32 edges, and 16 vertices {{Did you know}} 2rvz3pxmdxo5y2vmdjjimn2fa9lyw5g 67 0 175091 740013 2026-05-01T15:10:04Z Cryptocurrency777 73698 Created page with "67 ''67'' '''67''' == 67 == === 67 === = 67 = <span style="font-family:Comic Sans MS;>67</span> <span style="font-family:Tahoma;>67</span> <span style="font-family:Roboto;>67</span> is aa 2025 meme" 740013 wikitext text/x-wiki 67 ''67'' '''67''' == 67 == === 67 === = 67 = <span style="font-family:Comic Sans MS;>67</span> <span style="font-family:Tahoma;>67</span> <span style="font-family:Roboto;>67</span> is aa 2025 meme oknm71rlahryrbnfd9lb4lhqe8uuo18 740014 740013 2026-05-01T15:10:12Z Cryptocurrency777 73698 740014 wikitext text/x-wiki 67 ''67'' '''67''' == 67 == === 67 === = 67 = <span style="font-family:Comic Sans MS;>67</span> <span style="font-family:Tahoma;>67</span> <span style="font-family:Roboto;>67</span> is a 2025 meme 518otr9p9xdp0wpudt5nxvgei9hp057 All Night Radio 0 175092 740015 2026-05-01T15:13:28Z Cryptocurrency777 73698 Created page with "{{nihongo|"'''All Night Radio'''"|オールナイトレディオ|Ōru Naito Redio}} is a song recorded by Japanese singer [[Ado (singer)|Ado]], released on October 14, 2023 by [[Virgin Records|Virgin Music]]. The song was written and produced by [[Vocaloid]] producer Mitchie M.<ref name=":1">{{Cite web |trans-title=Ado's New Song "All Night Radio" Theme Song for Live-Streamed Stage Play Drama "Ano Yoru de Aetara" |title=Adoの新曲「オールナイトレディオ」生..." 740015 wikitext text/x-wiki {{nihongo|"'''All Night Radio'''"|オールナイトレディオ|Ōru Naito Redio}} is a song recorded by Japanese singer [[Ado (singer)|Ado]], released on October 14, 2023 by [[Virgin Records|Virgin Music]]. The song was written and produced by [[Vocaloid]] producer Mitchie M.<ref name=":1">{{Cite web |trans-title=Ado's New Song "All Night Radio" Theme Song for Live-Streamed Stage Play Drama "Ano Yoru de Aetara" |title=Adoの新曲「オールナイトレディオ」生配信舞台演劇ドラマ「あの夜であえたら」主題歌に |url=https://www.fashion-press.net/news/110057 |date=October 10, 2023 |access-date=March 22, 2024 |website=Fashion Press |language=ja |archive-date=March 22, 2024 |archive-url=https://web.archive.org/web/20240322143701/https://www.fashion-press.net/news/110057 |url-status=live }}</ref> 43i44aahre6nzjdx2yoqflf18x5a9gh Ado (musician) 0 175093 740016 2026-05-01T15:15:35Z Cryptocurrency777 73698 Redirected page to [[All Night Radio]] 740016 wikitext text/x-wiki #REDIRECT [[All Night Radio]] olkafuhrhs3u6s24v435opxghcswk02 User:Sam Sailor/test.js/comrade-options.js 2 175094 740019 2026-05-01T15:32:00Z Sam Sailor 26820 Test 740019 javascript text/javascript //<nowiki> window.ComradeOptions = Object.assign({ // Default Excluded Templates excludedTemplates: [ "Template:Dmbox", "Template:Set index article", "Template:Animal common name", "Template:Chemistry index", "Template:Enzyme index", "Template:Fungus common name", "Template:Given name", "Template:Greek mythology index", "Template:Lake index", "Template:Locomotive index", "Template:Media set index", "Template:Molecular formula index", "Template:Mountain index", "Template:Nickname", "Template:Painting index", "Template:Plant common name", "Template:River index", "Template:Road index", "Template:Ship index", "Template:Sport index", "Template:Storm index", "Template:Surname" ], // UI Behaviors and Timers nudgeTimeout: 10000, debugMode: false }, window.ComradeOptions || {}); //</nowiki> 06159b470wa47m5we1ycsahktlbt2wx Wikipedia:Requests/Bot status/Alachuckthebuck 4 175095 740045 2026-05-01T17:18:07Z Alachuckthebuck 49745 requesting bot acess for chuckbot. 740045 wikitext text/x-wiki <!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.--> === [[User:Chuckbot|Chuckbot]] === * {{User3|Alachuckthebuck}}, [[Special:CentralAuth/Alachuckthebuck|global contribs]] 17:17, 1 May 2026 (UTC) *{{user3|Chuckbot}},[[Special:CentralAuth/Chuckbot|global contribs]] * '''Motive for request:''' Testing code for chuckbot's 4-award helper module [https://github.com/chuckthebuck/module4awardhelper] before going live. * '''Requested rights:'''bot * '''Comments:''' <!-- Comments of other users --> <!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! --> [[Category:!Requests]] [[Category:Really big category]] __NOINDEX__ f4af0ytjfo1pmz874mqui1x8zbg8whe 740058 740045 2026-05-01T19:09:22Z Alachuckthebuck 49745 /* Chuckbot */ adding mainpage 740058 wikitext text/x-wiki <!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.--> === [[User:Chuckbot|Chuckbot]] === * {{User3|Alachuckthebuck}}, [[Special:CentralAuth/Alachuckthebuck|global contribs]] 17:17, 1 May 2026 (UTC) *{{user3|Chuckbot}},[[Special:CentralAuth/Chuckbot|global contribs]] * '''Motive for request:''' Testing code for chuckbot's 4-award helper module [https://github.com/chuckthebuck/module4awardhelper] before going live. my main page is [[c:user:Alachuckthebuck|user:Alachuckthebuck]] * '''Requested rights:'''bot * '''Comments:''' <!-- Comments of other users --> <!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! --> [[Category:!Requests]] [[Category:Really big category]] __NOINDEX__ 71oc5xlvmqp7sewd95xggnn9bki30id Wikipedia:Requests/Permissions/Namoroka 4 175096 740050 2026-05-01T17:47:29Z Namoroka 19627 Created page with "<!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.--> === [[User:{{subst:REVISIONUSER}}|{{subst:REVISIONUSER}}]] === * {{User3|{{subst:REVISIONUSER}}}}, [[Special:CentralAuth/{{subst:REVISIONUSER}}|global contribs]] ~~~~~ * '''Motive for request:''' <!-- Important! Plea..." 740050 wikitext text/x-wiki <!--@Bureaucrats: If you close this request as {{Done}} add {{Request-done|1=~~~~|2=Closing rationale}} to the top of the page. If you close it as {{Not done}}, add {{Request-not done|1=~~~~|2=Closing rationale}} there, then close with {{Request closed}}.--> === [[User:Namoroka|Namoroka]] === * {{User3|Namoroka}}, [[Special:CentralAuth/Namoroka|global contribs]] 17:47, 1 May 2026 (UTC) * '''Motive for request:''' <!-- Important! Please include a good motive for your request here. --> To test changes to gadgets * '''Requested rights:''' <!-- Please include the rights requested (example: Administrator, Bureaucrat, etc.) --> Interface administrator * '''Comments:''' <!-- Comments of other users -->Hi, I'm an admin and interface administrator on kowikisource, and I want to test some gadgets before they go live. <!-- DO NOT FORGET to transclude your request at THE TOP of [[Wikipedia:Requests/Permissions]] or nobody will see it! --> [[Category:!Requests]] [[Category:Really big category]] __NOINDEX__ cnuuza95ftnzswfffkm31f41eun2sk2 Halsey 0 175097 740120 2026-05-01T21:43:46Z Cryptocurrency777 73698 Created page with "[[G-Eazy]] ex" 740120 wikitext text/x-wiki [[G-Eazy]] ex sv7qrmjjsqy8yewt2yjq2ottwdrd6ut User talk:TheAuroraBorealis 3 175098 740121 2026-05-01T21:48:08Z TheAuroraBorealis 73590 Bot: Adding Hello world from PyWikiBot! ~~~~ 740121 wikitext text/x-wiki Hello world from PyWikiBot! [[User:TheAuroraBorealis|TheAuroraBorealis]] ([[User talk:TheAuroraBorealis|talk]]) 21:48, 1 May 2026 (UTC) kv77p5s7fez22an7m80krpqncfhugte 740122 740121 2026-05-01T21:49:17Z TheAuroraBorealis 73590 Bot: Adding PyWikiBot has taken over the world! /j 740122 wikitext text/x-wiki PyWikiBot has taken over the world! /j Hello world from PyWikiBot! [[User:TheAuroraBorealis|TheAuroraBorealis]] ([[User talk:TheAuroraBorealis|talk]]) 21:48, 1 May 2026 (UTC) pdr4r16e2rwi9jv5z1qwoa0nw5wh9pm User:Namoroka/ohi.css 2 175099 740124 2026-05-02T02:17:56Z Namoroka 19627 Created page with "/* CSS */ #ohi_wrap { font-size: 13px; font-family: sans-serif; } #keyboardLayout { clear: both; text-align: center; } #keyboardLayoutInfo { font-size: 12px; margin-bottom: 5px; text-align: left; } #keyboardLayoutInfo a { display: none; } #keyboardLayoutTable { display: inline-block; } #keyboardLayoutTable .row { margin: 0; white-space: nowrap; } #ohi_wrap div.e1, #ohi_wrap div.e2, #ohi_wrap div.e3, #ohi_wrap div.h1, #ohi_wrap div.h2, #ohi_wrap div.h3 {..." 740124 css text/css /* CSS */ #ohi_wrap { font-size: 13px; font-family: sans-serif; } #keyboardLayout { clear: both; text-align: center; } #keyboardLayoutInfo { font-size: 12px; margin-bottom: 5px; text-align: left; } #keyboardLayoutInfo a { display: none; } #keyboardLayoutTable { display: inline-block; } #keyboardLayoutTable .row { margin: 0; white-space: nowrap; } #ohi_wrap div.e1, #ohi_wrap div.e2, #ohi_wrap div.e3, #ohi_wrap div.h1, #ohi_wrap div.h2, #ohi_wrap div.h3 { display: inline-block; font-size: 12px; vertical-align: bottom; height: 38px; cursor: pointer; border: 1px solid #c8ccd1; background-color: #fff; box-sizing: border-box; border-radius: 2px; margin: 1px; } #ohi_wrap div.e1 { background-color: #fff; } #ohi_wrap div.e2 { background-color: #eaecf0; } #ohi_wrap div.e3 { background-color: #f8f9fa; } #ohi_wrap div.h1 { background-color: #eaf3ff; } /* 초성 */ #ohi_wrap div.h2 { background-color: #fce7e7; } /* 중성 */ #ohi_wrap div.h3 { background-color: #e3f5e1; } /* 종성 */ #ohi_wrap div.gin-hol { background-color: #f1e1ff; } /* 타자기 장모음 */ #ohi_wrap div.e1:hover, #ohi_wrap div.e2:hover, #ohi_wrap div.e3:hover, #ohi_wrap div.h1:hover, #ohi_wrap div.h2:hover, #ohi_wrap div.h3:hover { background-color: #c8ccd1; } #ohi_wrap div.pressed { background-color: #fc3; } /* 자판 내부 텍스트 */ #ohi_wrap div.up, #ohi_wrap div.down { height: 18px; line-height: 18px; } #ohi_wrap div.up { margin: 0 2px; } #ohi_wrap div.down { margin: 0 2px; } #ohi_wrap div.ue, #ohi_wrap div.de { float: left; color: #72777d; font-family: monospace; } #ohi_wrap div.e2 div.ue { color: #202122; } #ohi_wrap div.dh, #ohi_wrap div.uh { float: right; color: #202122; font-weight: bold; } /* 체크박스 옵션 영역 */ #top_options .option, #middle_options .option { display: inline-block; margin-right: 15px; float: none; } #ohi_wrap input.checkbox { margin-left: 0; margin-right: 3px; vertical-align: middle; } #ohi_wrap label { font-size: 12px; color: #202122; cursor: pointer; } kbd.status { font-family: monospace; color: #fff; background-color: #36c; padding: 2px 4px; border-radius: 2px; } mgunsxaud16ybcgfbt4whhxfgslz8yz User:Namoroka/additional-layouts.js 2 175100 740125 2026-05-02T02:20:00Z Namoroka 19627 https://github.com/pat-al/Online-HanGeul-IME/blob/master/additional_layouts.js / GPLv3 740125 javascript text/javascript /* ※ additional_layouts.js : 이제는 쓰이지 않거나 개선판이 나왔거나 연구 중인 자판 배열들을 모음 ※ keyboard_layouts.js : 널리 쓰이거나 대표성이 있거나 기능·배열에 주목할 면이 있는 자판 배열들을 모음 ※ 첫가끝 방식으로 옛한글을 조합하는 자판은 type_name 끝에 '-y'를 붙인다. ※ 신세벌식 자판은 type_name 앞에 'Sin3-'를 붙인다. ※ 갈마들이 방식을 쓰는 공병우식 자판은 type_name 끝에 '_gm'을 붙인다. ※ 모아치기 방식으로 쓰는 세벌식 자판은 type_name 앞에 '3m-'를 붙인다. ※ 타자기 자판은 벌 수 다음에 't-'를 붙인다. ('3t-', 4t-') ※ 모아치기 자판의 보조 배열(sublayout)은 입력에 반영되지 않고 배열표에만 나타남 */ input_additional_keyboard_layout_info(); input_additional_combination_table_info(); var additional_layouts = []; additional_layouts[0] = new keyboard_layout_info(); additional_layouts.push({KE: 'Ko', type_name: '2-GJS', full_name: '김준성 풀어쓰기 수동 타자기 (1940년대)', layout: K2_GimJunseong_typewriter_layout, link: 'https://pat.im/1024'}); additional_layouts.push({KE: 'Ko', type_name: '2-HGS_1952', full_name: '한당욱·김철수·신한종 풀어쓰기 전신 타자기 (1952)', layout: K2_HGS_1952_teletypewriter_layout, link: 'https://pat.im/1025'}); additional_layouts.push({KE: 'Ko', type_name: '2-Jang_1953', full_name: '장봉선 풀어쓰기 전신 타자기 (1953) (체신부 통신용)', layout: K2_Jang_1953_teletypewriter_layout, link: 'https://pat.im/1025'}); additional_layouts.push({KE: 'Ko', type_name: '2-Bag_Song_1968', full_name: '박영효·송계범 전신 타자기 자판 설계안 (1968)', layout: K2_Bag_Song_1968_layout, link: 'https://pat.im/1025'}); additional_layouts.push({KE: 'Ko', type_name: '2-Gaon26KM', full_name: '가온한글26KM', layout: K2_Gaon_KSX5002_layout, link: 'http://cafe.daum.net/kbd-p/8OTK/10'}); additional_layouts.push({KE: 'Ko', type_name: '4t-1969', full_name: '1969 네벌식 타자기 표준 (과학기술처)', layout: K4_1969_Typewriter_layout, hangeul_combination_table: K4_1969_Typewriter_combination_table, link: 'https://pat.im/965'}); additional_layouts.push({KE: 'Ko', type_name: '3t-Oesol', full_name: '외솔 타자기 (1981, 최동식·김광성)', layout: K3_Oesol_Typewriter_layout, hangeul_combination_table: K3_Oesol_Typewriter_combination_table, link: 'https://pat.im/1026'}); additional_layouts.push({KE: 'Ko', type_name: '4t-1985', full_name: '1985 두벌식 배열 호환형 타자기 (과학기술처)', layout: K4_1985_Typewriter_layout, link: 'https://pat.im/1026'}); additional_layouts.push({KE: 'Ko', type_name: '3t-Gong_Munjang', full_name: '공병우 문장용 타자기', layout: K3_Gong_Munjang_layout, link: 'https://pat.im/960#5'}); additional_layouts.push({KE: 'Ko', type_name: '3-87', full_name: '3-87', layout: K3_87_layout, extended_sign_layout: K3_87_extended_layout, link: 'https://pat.im/1145'}); additional_layouts.push({KE: 'Ko', type_name: '3-89', full_name: '3-89', layout: K3_89_layout, link: ''}); additional_layouts.push({KE: 'Ko', type_name: '3-891', full_name: '3-891', layout: K3_891_layout, extended_sign_layout: K3_891_extended_layout, link: 'https://pat.im/1188'}); additional_layouts.push({KE: 'Ko', type_name: '3-95', full_name: '3-95 자판안 (김창용)', layout: K3_95_proposal_layout, extended_sign_layout: K3_95_extended_layout, link: 'https://bbs.pat.im/viewtopic.php?f=15&t=931'}); additional_layouts.push({KE: 'Ko', type_name: '3-sun1990', full_name: '안종혁 순아래 1990 (한 손 자판, no-shift) (3-90 응용)', layout: K3_sun1990_layout, link: ''}); additional_layouts.push({KE: 'Ko', type_name: '3-Gaon38A', full_name: '김국 38A (가온세벌38A)', layout: K3_Gaon38A_layout, hangeul_combination_table: Gaon38A_combination_table, link: 'http://cafe.daum.net/kbd-p/8k2B/1'}); additional_layouts.push({KE: 'Ko', type_name: '3-2011', full_name: '3-2011 (문장 입력용)', layout: K3_2011_layout, extended_sign_layout: K3_2011_extended_sign_layout, old_hangeul_layout_type_name: '3-2011-y', link: 'https://pat.im/855'}); additional_layouts.push({KE: 'Ko', type_name: '3-2011-y', full_name: '3-2011 옛한글', layout: K3_2011_layout, extended_sign_layout: K3_2011y_extended_sign_layout, extended_hangeul_layout: K3_2012y_extended_hangeul_layout, link: 'https://pat.im/908'}); additional_layouts.push({KE: 'Ko', type_name: '3-2012', full_name: '3-2012', layout: K3_2012_layout, extended_sign_layout: K3_2012_extended_sign_layout, old_hangeul_layout_type_name: '3-2012-y', link: 'https://pat.im/938'}); additional_layouts.push({KE: 'Ko', type_name: '3-2012-y', full_name: '3-2012 옛한글', layout: K3_2012_layout, extended_sign_layout: K3_2012y_extended_sign_layout, extended_hangeul_layout: K3_2012y_extended_hangeul_layout, link: 'https://pat.im/938#4-2'}); additional_layouts.push({KE: 'Ko', type_name: '314', full_name: '한글문화원 314 자판안', layout: K3_14_proposal_layout, link: 'http://cafe.daum.net/3bulsik/JMKX/4'}); additional_layouts.push({KE: 'Ko', type_name: '314_gm', full_name: '한글문화원 314 자판안 (+ 갈마들이)', layout: K3_14_proposal_layout, link: 'http://cafe.daum.net/3bulsik/JMKX/4'}); additional_layouts.push({KE: 'Ko', type_name: '3-2014', full_name: '3-2014', layout: K3_2014_layout, extended_sign_layout: K3_2012y_extended_sign_layout, hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, old_hangeul_layout_type_name: '3-2014-y', link: 'https://pat.im/1088'}); additional_layouts.push({KE: 'Ko', type_name: '3-2014-y', full_name: '3-2014 옛한글', layout: K3_2014_layout, extended_sign_layout: K3_2012y_extended_sign_layout, extended_hangeul_layout: K3_2012y_extended_hangeul_layout, link: 'https://pat.im/1090'}); additional_layouts.push({KE: 'Ko', type_name: '3-2015', full_name: '3-2015', layout: K3_2015_layout, hangeul_combination_table: K3_2015_combination_table, hangeul_convenience_combination_table: K3_2015_additional_combination_table, old_hangeul_layout_type_name: '3-2015-y', link: 'http://cafe.daum.net/3bulsik/JMKX/34'}); additional_layouts.push({KE: 'Ko', type_name: '3-2015-y', full_name: '3-2015 옛한글', layout: K3_2015y_layout, hangeul_combination_table: K3_2015y_combination_table, link: 'http://cafe.daum.net/3bulsik/JMKX/36'}); additional_layouts.push({KE: 'Ko', type_name: '3-2015M', full_name: '3-2015M', layout: K3_2015M_layout, hangeul_combination_table: K3_2015M_combination_table, hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, link: 'http://cafe.daum.net/3bulsik/JMKX/46'}); additional_layouts.push({KE: 'Ko', type_name: '3-2015P', full_name: '3-2015P', layout: K3_2015P_layout, extended_sign_layout: K3_2012y_extended_sign_layout, hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, old_hangeul_layout_type_name: '3-2015P-y', link: 'https://pat.im/1090'}); additional_layouts.push({KE: 'Ko', type_name: '3-2015P-y', full_name: '3-2015P 옛한글', layout: K3_2015P_layout, extended_sign_layout: K3_2012y_extended_sign_layout, extended_hangeul_layout: K3_2012y_extended_hangeul_layout, link: 'https://pat.im/1090'}); additional_layouts.push({KE: 'Ko', type_name: '3-P3', full_name: '3-P3', layout: K3_P3_layout, extended_sign_layout: K3_P3_extended_sign_layout, hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, link: 'https://pat.im/1128'}); additional_layouts.push({KE: 'Ko', type_name: '3-P2', full_name: '3-P2', layout: K3_P2_layout, extended_sign_layout: K3_2012y_extended_sign_layout, hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, link: 'https://pat.im/1128'}); additional_layouts.push({KE: 'Ko', type_name: '3-D1', full_name: '3-D1', layout: K3_D1_layout, capslock_layout: K3_D1_capslock_layout, old_hangeul_layout_type_name: '3-D1-y', hangeul_combination_table: K3_D1_combination_table, hangeul_convenience_combination_table: K3_D1_additional_combination_table, link: 'https://cafe.daum.net/3bulsik/JMKX/180', link: 'https://ds1tpt.tistory.com/10'}); additional_layouts.push({KE: 'Ko', type_name: '3-D1-y', full_name: '3-D1 옛한글', layout: K3_D1_y_layout, hangeul_combination_table: K3_D1_y_combination_table, link: 'https://cafe.daum.net/3bulsik/JMKX/180', link: 'https://ds1tpt.tistory.com/10'}); additional_layouts.push({KE: 'Ko', type_name: '3-D2', full_name: '3-D2', layout: K3_D2_layout, capslock_layout: K3_D2_capslock_layout, old_hangeul_layout_type_name: '3-D2-y', hangeul_combination_table: K3_Galmadeuli_Gong3_combination_table, link: 'https://cafe.daum.net/3bulsik/JMKX/201', link: 'https://ds1tpt.tistory.com/16'}); additional_layouts.push({KE: 'Ko', type_name: '3-D2-y', full_name: '3-D2 옛한글', layout: K3_D2_y_layout, capslock_layout: K3_D2_capslock_layout, link: 'https://cafe.daum.net/3bulsik/JMKX/201', link: 'https://ds1tpt.tistory.com/16'}); additional_layouts.push({KE: 'Ko', type_name: '3-18Na', full_name: '3-18Na', layout: K3_18Na_layout, hangeul_combination_table: K3_18Na_combination_table, link: 'https://kldp.org/node/160815'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-1995', full_name: '신세벌식 1995 (신광조 원안)', layout: K3_Sin3_1995_layout, link:'https://pat.im/1104'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-BGN', full_name: '박경남 신세벌식', layout: K3_Sin3_BGN_layout}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-2003', full_name: '박경남 수정 신세벌식 (2003)', layout: K3_Sin3_2003_layout}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-2012', full_name: '신세벌식 2012', layout: K3_Sin3_2012_layout, link: 'https://pat.im/978'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-2015', full_name: '신세벌식 2015', layout: K3_Sin3_2015_layout, hangeul_combination_table: K3_Sin3_2015_combination_table, link: 'http://sebeol.org/gnuboard/bbs/board.php?bo_table=lab&wr_id=28'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-M', full_name: '신세벌식 M', layout: K3_Sin3_M_layout, link: 'http://cafe.daum.net/3bulsik/JMKX/77'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-P', full_name: '신세벌식 P', layout: K3_Sin3_P_layout, extended_hangeul_layout: K3_Sin3_P_y_layout, extended_sign_layout: K3_Sin3_extended_sign_layout, /*extended_hangeul_combination_table: K3_Sin3_P_extended_combination_table,*/ old_hangeul_layout_type_name: 'Sin3-P-y', link: 'https://pat.im/1110'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-P-y', full_name: '신세벌식 P 옛한글 조합', layout: K3_Sin3_P_y_layout, capslock_layout: K3_Sin3_P_y_capslock_layout, extended_sign_layout: K3_Sin3_extended_sign_layout, capslock_extended_sign_layout: null, hangeul_combination_table: K3_Sin3_P2_yeshangeul_combination_table, link: 'https://pat.im/1136#2-4'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-Gongdong', full_name: '신세벌식 공동개발안 (연구)', layout: K3_Sin3_Gongdong_layout, ieochigi_hangeul_abbreviation_table: K3_Sin3_Gongdong_abbreviation_table, hangeul_convenience_combination_table: K3_Sin3_Gongdong_additional_combination_table, link: 'http://cafe.daum.net/3bulsikmini0A0/JYgd/31'}); additional_layouts.push({KE: 'Ko', type_name: 'Sin3-Cham', full_name: '참신세벌식 v18', layout: K3_Sin3_Cham_layout, hangeul_combination_table: K3_Sin3_Cham_combination_table, hangeul_convenience_combination_table: K3_Sin3_Cham_additional_combination_table, link: 'https://doc9107.tistory.com/67'}); additional_layouts.push({KE: 'Ko', type_name: 'LGG3-OH-R', full_name: '이건구 한 손 세벌식 오른손', layout: K3_LGG_OH_r, capslock_layout: K3_LGG_OH_r_capslock_layout, hangeul_combination_table: K3_LGG_OH_combination_table, link: 'https://ds1tpt.tistory.com/32'}); additional_layouts.push({KE: 'Ko', type_name: 'LGG3-OH-L', full_name: '이건구 한 손 세벌식 왼손', layout: K3_LGG_OH_l, capslock_layout: K3_LGG_OH_l_capslock_layout, hangeul_combination_table: K3_LGG_OH_combination_table, link: 'https://ds1tpt.tistory.com/32'}); additional_layouts.push({KE: 'Ko', type_name: 'LGG3-OH-OHK', full_name: '이건구 한 손 세벌식 한손글쇠판용', layout: K3_LGG_OH_ohk, capslock_layout: K3_LGG_OH_ohk_capslock_layout, hangeul_combination_table: K3_LGG_OH_combination_table, link: 'https://ds1tpt.tistory.com/32'}); additional_layouts.push({KE: 'Ko', type_name: '3m-Anmatae', full_name: '안마태 소리 글판', layout: K3_Anmatae_layout, moachigi_hangeul_combination_table: K3_Anmatae_combination_table, link: ''}); additional_layouts.push({KE: 'Ko', type_name: '3m-Semoe2014', full_name: '세모이 2014 (옛 배열)', layout: K3_Semoe_2014_layout, sublayout: K3_Semoe_2014_sublayout, moachigi_hangeul_combination_table: K3_Semoe_2014_combination_table}); additional_layouts.push({KE: 'Ko', type_name: '3m-Semoe2015', full_name: '세모이 2015 (옛 배열)', layout: K3_Semoe_2015_layout, sublayout: K3_Semoe_2015_sublayout, moachigi_hangeul_combination_table: K3_Semoe_2015_combination_table}); additional_layouts.push({KE: 'Ko', type_name: '3m-Semoe2016', full_name: '세모이 2016 (옛 배열)', layout: K3_Semoe_2016_layout, sublayout: K3_Semoe_2016_sublayout, moachigi_hangeul_combination_table: K3_Semoe_2016_combination_table, extended_sign_layout: K3_Semoe_extended_sign_layout}); additional_layouts.push({KE: 'Ko', type_name: '3m-Semoe2017', full_name: '세모이 2017 (옛 배열)', layout: K3_Semoe_2017_layout, sublayout: K3_Semoe_2017_sublayout, extended_sign_layout: K3_Semoe_extended_sign_layout, moachigi_hangeul_combination_table: K3_Semoe_2017_combination_table, moachigi_multikey_abbreviation_table: K3_Semoe_2017_moachigi_multikey_abbreviation_table, link: 'http://ssg.wo.tc/220526834927'}); additional_layouts.push({KE: 'Ko', type_name: '3m-test', full_name: '모아치기 시험', layout: K3_Semoe_2017_layout, sublayout: K3_Semoe_2017_sublayout, extended_sign_layout: K3_Semoe_extended_sign_layout, moachigi_hangeul_combination_table: K3_Semoe_2017_combination_table, moachigi_multikey_abbreviation_table: K3_test_multikey_abbreviation_table, link: ''}); function input_additional_keyboard_layout_info() { var i,j; // 김준성 두벌식 타자기 (1940년대 미군정 때) K2_GimJunseong_typewriter_layout = [ 0x0000, /* 0x21 exclam: exclamation mark */ 0x003A, /* 0x22 quotedbl: colon */ 0x0033, /* 0x23 numbersign: 3 */ 0x0034, /* 0x24 dollar: 4 */ 0x0035, /* 0x25 percent: 5 */ 0x0037, /* 0x26 ampersand: 7 */ 0x116C, /* 0x27 apostrophe: jungseong oe */ 0x0039, /* 0x28 parenleft: 9 */ 0x0028, /* 0x29 parenright: left parenthesis */ 0x0038, /* 0x2A asterisk: 8 */ 0x002B, /* 0x2B plus: plus sign */ 0x116E, /* 0x2C comma: jungseong u */ 0x1170, /* 0x2D minus: jungseong we */ 0x1172, /* 0x2E period: jungseong yu */ 0x1171, /* 0x2F slash: jungseong wi */ 0x116F, /* 0x30 0: jungseong weo */ 0x0000, /* 0x31 1: */ 0x0022, /* 0x32 2: quotation mark */ 0x0040, /* 0x33 3: commertial at */ 0x0027, /* 0x34 4: apostrophe */ 0x0025, /* 0x35 5: percent */ 0x002E, /* 0x36 6: period */ 0x002C, /* 0x37 7: comma */ 0x116A, /* 0x38 8: jungseong wa */ 0x116B, /* 0x39 9: jungseong wae */ 0x003F, /* 0x3A colon: question mark */ 0x1168, /* 0x3B semicolon: jungseong ye */ 0x007E, /* 0x3C less: tilde */ 0x003D, /* 0x3D equal: equals sign */ 0x002F, /* 0x3E greater: slash */ 0x005F, /* 0x3F question: underscore */ 0x0032, /* 0x40 at: 2 */ 0x0041, /* 0x41 A */ 0x0042, /* 0x42 B */ 0x0043, /* 0x43 C */ 0x0044, /* 0x44 D */ 0x0045, /* 0x45 E */ 0x0046, /* 0x46 F */ 0x0047, /* 0x47 G */ 0x0048, /* 0x48 H */ 0x0049, /* 0x49 I */ 0x004A, /* 0x4A J */ 0x004B, /* 0x4B K */ 0x004C, /* 0x4C L */ 0x004D, /* 0x4D M */ 0x004E, /* 0x4E N */ 0x004F, /* 0x4F O */ 0x0050, /* 0x50 P */ 0x0051, /* 0x51 Q */ 0x0052, /* 0x52 R */ 0x0053, /* 0x53 S */ 0x0054, /* 0x54 T */ 0x0055, /* 0x55 U */ 0x0056, /* 0x56 V */ 0x0057, /* 0x57 W */ 0x0058, /* 0x58 X */ 0x0059, /* 0x59 Y */ 0x005A, /* 0x5A Z */ 0x1164, /* 0x5B bracketleft: jungseong yae */ 0x005C, /* 0x5C backslash: backslash */ 0x0000, /* 0x5D bracketright: */ 0x0036, /* 0x5E asciicircum: 6 */ 0x0029, /* 0x5F underscore: right parenthesis */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1105, /* 0x61 a: choseong lieul */ 0x110C, /* 0x62 b: choseong jieuj */ 0x110F, /* 0x63 c: choseong kieuk */ 0x1102, /* 0x64 d: choseong nieun */ 0x1107, /* 0x65 e: choseong bieub */ 0x1100, /* 0x66 f: choseong gieug */ 0x1112, /* 0x67 g: choseong hieuh */ 0x1163, /* 0x68 h: jungseong ya */ 0x1175, /* 0x69 i: jungseong i */ 0x1161, /* 0x6A j: jungseong a */ 0x1165, /* 0x6B k: jungseong eo */ 0x1167, /* 0x6C l: jungseong yeo */ 0x1169, /* 0x6D m: jungseong o */ 0x116D, /* 0x6E n: jungseong yo */ 0x1166, /* 0x6F o: jungseong e */ 0x1162, /* 0x70 p: jungseong ae */ 0x00B0, /* 0x71 q: degree sign */ 0x1109, /* 0x72 r: choseong sieus */ 0x1103, /* 0x73 s: choseong dieud */ 0x110B, /* 0x74 t: choseong ieung */ 0x1173, /* 0x75 u: jungseong eu */ 0x110E, /* 0x76 v: choseong chieuch */ 0x1106, /* 0x77 w: choseong mieum */ 0x1110, /* 0x78 x: choseong tieut */ 0x1174, /* 0x79 y: jungseong eui */ 0x1111, /* 0x7A z: choseong pieup */ 0x002D, /* 0x7B braceleft: minus sign */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x0000, /* 0x7D braceright: */ 0x0000 /* 0x7E asciitilde: */ ]; // 한당욱·김철수·신한종 전신 타자기 (1952년) K2_HGS_1952_teletypewriter_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x0027, /* 0x27 apostrophe: apostrophe */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x003B, /* 0x3B semicolon: semicolon */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: equals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x002D, /* 0x41 A: minus sign */ 0x003F, /* 0x42 B: question mark */ 0x003A, /* 0x43 C: colon */ 0x0024, /* 0x44 D: dollar sign */ 0x0033, /* 0x45 E: 3 */ 0x0021, /* 0x46 F: exclamation mark */ 0x0026, /* 0x47 G: ampersand */ 0x23F9, /* 0x48 H: stop */ 0x0038, /* 0x49 I: 8 */ 0x0022, /* 0x4A J: quotatioin mark */ 0x0028, /* 0x4B K: left parenthesis */ 0x0029, /* 0x4C L: right parenthesis */ 0x002E, /* 0x4D M: period */ 0x002C, /* 0x4E N: comma */ 0x0039, /* 0x4F O: 9 */ 0x0030, /* 0x50 P: 0 */ 0x0031, /* 0x51 Q: 1 */ 0x0034, /* 0x52 R: 4 */ 0x237E, /* 0x53 S: bell */ 0x0035, /* 0x54 T: 5 */ 0x0037, /* 0x55 U: 7 */ 0x003B, /* 0x56 V: semicolon */ 0x0032, /* 0x57 W: 2 */ 0x002F, /* 0x58 X: slash */ 0x0036, /* 0x59 Y: 6 */ 0x3003, /* 0x5A Z: ditto mark */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1100, /* 0x61 a: choseong gieug */ 0x116D, /* 0x62 b: jungseong yo */ 0x1111, /* 0x63 c: choseong pieup */ 0x1109, /* 0x64 d: choseong sieus */ 0x1106, /* 0x65 e: choseong mieum */ 0x1102, /* 0x66 f: choseong nieun */ 0x110B, /* 0x67 g: choseong ieung */ 0x1169, /* 0x68 h: jungseong o */ 0x116E, /* 0x69 i: jungseong u */ 0x1161, /* 0x6A j: jungseong a */ 0x1165, /* 0x6B k: jungseong eo */ 0x1175, /* 0x6C l: jungseong i */ 0x1173, /* 0x6D m: jungseong yu */ 0x1172, /* 0x6E n: jungseong eu */ 0x1162, /* 0x6F o: jungseong ae */ 0x1166, /* 0x70 p: jungseong e */ 0x1107, /* 0x71 q: choseong bieub */ 0x110C, /* 0x72 r: choseong jieuj */ 0x1105, /* 0x73 s: choseong lieul */ 0x1112, /* 0x74 t: choseong hieuh */ 0x1167, /* 0x75 u: jungseong yeo */ 0x1110, /* 0x76 v: choseong tieut */ 0x1103, /* 0x77 w: choseong dieud */ 0x110E, /* 0x78 x: choseong chieuch */ 0x1163, /* 0x79 y: jungseong ya */ 0x110F, /* 0x7A z: choseong kieuk */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 장봉선 두벌식 (1953년 체신부 전신용) K2_Jang_1953_teletypewriter_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x0027, /* 0x27 apostrophe: apostrophe */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x003B, /* 0x3B semicolon: semicolon */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: equals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x002D, /* 0x41 A: minus sign */ 0x003F, /* 0x42 B: question mark */ 0x003A, /* 0x43 C: colon */ 0x271C, /* 0x44 D: Who are you? (ENQ, WRU) */ 0x0033, /* 0x45 E: 3 */ 0x0025, /* 0x46 F: percent sign */ 0x0022, /* 0x47 G: quotatioin mark */ 0x0021, /* 0x48 H: exclamation mark */ 0x0038, /* 0x49 I: 8 */ 0x237E, /* 0x4A J: bell */ 0x0028, /* 0x4B K: left parenthesis */ 0x0029, /* 0x4C L: right parenthesis */ 0x002E, /* 0x4D M: period */ 0x002C, /* 0x4E N: comma */ 0x0039, /* 0x4F O: 9 */ 0x0030, /* 0x50 P: 0 */ 0x0031, /* 0x51 Q: 1 */ 0x0034, /* 0x52 R: 4 */ 0x0027, /* 0x53 S: apostrophe */ 0x0035, /* 0x54 T: 5 */ 0x0037, /* 0x55 U: 7 */ 0x003D, /* 0x56 V: equals sign */ 0x0032, /* 0x57 W: 2 */ 0x002F, /* 0x58 X: slash */ 0x0036, /* 0x59 Y: 6 */ 0x002B, /* 0x5A Z: plus sign */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1112, /* 0x61 a: choseong hieuh */ 0x1111, /* 0x62 b: choseong pieup */ 0x110E, /* 0x63 c: choseong chieuch */ 0x1100, /* 0x64 d: choseong gieug */ 0x1109, /* 0x65 e: choseong sieus */ 0x1102, /* 0x66 f: choseong nieun */ 0x110B, /* 0x67 g: choseong ieung */ 0x1173, /* 0x68 h: jungseong eu */ 0x1169, /* 0x69 i: jungseong o */ 0x1161, /* 0x6A j: jungseong a */ 0x1175, /* 0x6B k: jungseong i */ 0x1165, /* 0x6C l: jungseong eo */ 0x116D, /* 0x6D m: jungseong yo */ 0x1172, /* 0x6E n: jungseong yu */ 0x1163, /* 0x6F o: jungseong ya */ 0x1166, /* 0x70 p: jungseong e */ 0x1110, /* 0x71 q: choseong tieut */ 0x1103, /* 0x72 r: choseong dieud */ 0x1105, /* 0x73 s: choseong lieul */ 0x1106, /* 0x74 t: choseong mieum */ 0x116E, /* 0x75 u: jungseong u */ 0x1107, /* 0x76 v: choseong bieub */ 0x110C, /* 0x77 w: choseong jieuj */ 0x110F, /* 0x78 x: choseong kieuk */ 0x1167, /* 0x79 y: jungseong yeo */ 0x1162, /* 0x7A z: jungseong ae */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 박영효·송계범 두벌식 (1968년 전신 타자기용) K2_Bag_Song_1968_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x0027, /* 0x27 apostrophe: apostrophe */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x003B, /* 0x3B semicolon: semicolon */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: equals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x1104, /* 0x41 A: choseong ssang_dieud */ 0x1163, /* 0x42 B: jungseong ya*/ 0x1112, /* 0x43 C: choseong hieuh*/ 0x110B, /* 0x44 D: choseong ieung*/ 0x1108, /* 0x45 E: choseong ssang_bieub */ 0x1102, /* 0x46 F: choseong nieun */ 0x1105, /* 0x47 G: choseong lieul */ 0x1169, /* 0x48 H: jungseong o */ 0x1168, /* 0x49 I: jungseong ye */ 0x1173, /* 0x4A J: jungseong eu */ 0x1161, /* 0x4B K: jungseong a */ 0x1175, /* 0x4C L: jungseong i */ 0x1172, /* 0x4D M: jungseong yu */ 0x116D, /* 0x4E N: jungseong yo */ 0x1165, /* 0x4F O: jungseong eo */ 0x1164, /* 0x50 P: jungseong yae */ 0x110E, /* 0x51 Q: choseong chieuch */ 0x110A, /* 0x52 R: choseong ssang_sieus */ 0x1101, /* 0x53 S: choseong ssang_gieug */ 0x1106, /* 0x54 T: choseong mieum*/ 0x116E, /* 0x55 U: jungseong u */ 0x1110, /* 0x56 V: choseong tieut */ 0x110D, /* 0x57 W: choseong ssang_jieuj */ 0x1111, /* 0x58 X: choseong pieup */ 0x1167, /* 0x59 Y: jungseong yeo */ 0x110F, /* 0x5A Z: choseong kieuk */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1103, /* 0x61 a: choseong dieud */ 0x1163, /* 0x62 b: jungseong ya */ 0x1112, /* 0x63 c: choseong hieuh */ 0x110B, /* 0x64 d: choseong ieung */ 0x1107, /* 0x65 e: choseong bieub */ 0x1102, /* 0x66 f: choseong nieun */ 0x1105, /* 0x67 g: choseong lieul */ 0x1169, /* 0x68 h: jungseong o */ 0x1166, /* 0x69 i: jungseong e */ 0x1173, /* 0x6A j: jungseong eu */ 0x1161, /* 0x6B k: jungseong a */ 0x1175, /* 0x6C l: jungseong i */ 0x1172, /* 0x6D m: jungseong yu */ 0x116D, /* 0x6E n: jungseong yo */ 0x1165, /* 0x6F o: jungseong eo */ 0x1162, /* 0x70 p: jungseong ae */ 0x110E, /* 0x71 q: choseong chieuch */ 0x1109, /* 0x72 r: choseong sieus */ 0x1100, /* 0x73 s: choseong gieug */ 0x1106, /* 0x74 t: choseong mieum */ 0x116E, /* 0x75 u: jungseong u */ 0x1110, /* 0x76 v: choseong tieut */ 0x110C, /* 0x77 w: choseong jieuj */ 0x1111, /* 0x78 x: choseong pieup */ 0x1167, /* 0x79 y: jungseong yeo */ 0x110F, /* 0x7A z: choseong kieuk */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 가온한글 두벌식 K2_Gaon_KSX5002_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x0027, /* 0x27 apostrophe: apostrophe */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x003B, /* 0x3B semicolon: semicolon */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: equals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x0041, /* 0x41 A */ 0x0042, /* 0x42 B */ 0x0043, /* 0x43 C */ 0x0044, /* 0x44 D */ 0x0045, /* 0x45 E */ 0x0046, /* 0x46 F */ 0x0047, /* 0x47 G */ 0x0048, /* 0x48 H */ 0x0049, /* 0x49 I */ 0x004A, /* 0x4A J */ 0x004B, /* 0x4B K */ 0x004C, /* 0x4C L */ 0x004D, /* 0x4D M */ 0x004E, /* 0x4E N */ 0x004F, /* 0x4F O */ 0x0050, /* 0x50 P */ 0x0051, /* 0x51 Q */ 0x0052, /* 0x52 R */ 0x0053, /* 0x53 S */ 0x0054, /* 0x54 T */ 0x0055, /* 0x55 U */ 0x0056, /* 0x56 V */ 0x0057, /* 0x57 W */ 0x0058, /* 0x58 X */ 0x0059, /* 0x59 Y */ 0x005A, /* 0x5A Z */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1106, /* 0x61 a: choseong mieum */ 0x1172, /* 0x62 b: jungseong yu */ 0x110E, /* 0x63 c: choseong cieuc */ 0x110B, /* 0x64 d: choseong ieung */ 0x1103, /* 0x65 e: choseong dieud */ 0x1105, /* 0x66 f: choseong lieul */ 0x1112, /* 0x67 g: choseong hieuh */ 0x1169, /* 0x68 h: jungseong o */ 0x1163, /* 0x69 i: jungseong ya */ 0x1165, /* 0x6A j: jungseong eo */ 0x1161, /* 0x6B k: jungseong a */ 0x1175, /* 0x6C l: jungseong i */ 0x1173, /* 0x6D m: jungseong eu */ 0x116E, /* 0x6E n: jungseong u */ 0x1162, /* 0x6F o: jungseong ae */ 0x1166, /* 0x70 p: jungseong e */ 0x1107, /* 0x71 q: choseong bieub */ 0x1100, /* 0x72 r: choseong gieug */ 0x1102, /* 0x73 s: choseong nieun */ 0x1109, /* 0x74 t: choseong sieus */ 0x1167, /* 0x75 u: jungseong yeo */ 0x1111, /* 0x76 v: choseong pieup */ 0x110C, /* 0x77 w: choseong jieuj */ 0x1110, /* 0x78 x: choseong tieut */ 0x116D, /* 0x79 y: jungseong yo */ 0x110F, /* 0x7A z: choseong kieuk */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 1969 옛 표준 네벌식 타자기 K4_1969_Typewriter_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x11AE, /* 0x22 quotedbl: jongseong dieud */ 0xFFE6, /* 0x23 numbersign: won sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x003A, /* 0x26 ampersand: colon */ 0x11B8, /* 0x27 apostrophe: jongseong bieub */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x0027, /* 0x2A asterisk: apostrophe */ 0x002B, /* 0x2B plus: plus sign */ 0x3161, /* 0x2C comma: hangeul letter eu */ 0x002D, /* 0x2D minus: minus sign */ 0x11BA, /* 0x2E period: jongseong sieus */ 0x11BB, /* 0x2F slash: jongseong ssang_sieus */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x3156, /* 0x3A colon: hangeul letter ye */ 0x3154, /* 0x3B semicolon: hangeul letter e */ 0x002C, /* 0x3C less: comma */ 0x003D, /* 0x3D equal: equals sign */ 0x11B9, /* 0x3E greater: jongseong bieubsieus */ 0x11AD, /* 0x3F question: jongseong nieunhieuh */ 0x0022, /* 0x40 at: quotation mark */ 0x110F, /* 0x41 A: choseong kieuk */ 0x3160, /* 0x42 B: hangeul letter yu */ 0x11BE, /* 0x43 C: jongseong chieuch */ 0x110E, /* 0x44 D: choseong chieuch */ 0x1104, /* 0x45 E: choseong ssang_dieud */ 0x1111, /* 0x46 F: choseong pieup */ 0x002F, /* 0x47 G: slash */ 0x315B, /* 0x48 H: hangeul letter yo */ 0x1163, /* 0x49 I: jungseong ya */ 0x3155, /* 0x4A J: hangeul letter yeo */ 0x3151, /* 0x4B K: hangeul letter ya */ 0x3150, /* 0x4C L: hangeul lteer ae */ 0x002E, /* 0x4D M: period */ 0x1172, /* 0x4E N: jungseong yu */ 0x1162, /* 0x4F O: jungseong ae */ 0x003F, /* 0x50 P: question mark */ 0x1108, /* 0x51 Q: choseong ssang_bieub */ 0x1101, /* 0x52 R: choseong ssang_gieug */ 0x1110, /* 0x53 S: choseong tieut */ 0x110A, /* 0x54 T: choseong ssang_sieus*/ 0x1167, /* 0x55 U: jungseong yeo */ 0x11C1, /* 0x56 V: jongseong pieup */ 0x110D, /* 0x57 W: choseong ssang_jieuj */ 0x11C0, /* 0x58 X: jongseong tieut */ 0x116D, /* 0x59 Y: jungseong yo */ 0x11C2, /* 0x5A Z: jongseong hieuh */ 0x11A8, /* 0x5B bracketleft: jongseong gieug */ 0x0000, /* 0x5C backslash: */ 0x0000, /* 0x5D bracketright: */ 0x005F, /* 0x5E asciicircum: underscore */ 0x00D7, /* 0x5F underscore: multiplication sign */ 0x0000, /* 0x60 quoteleft: */ 0x1106, /* 0x61 a: choseong mieum */ 0x315C, /* 0x62 b: hangeul letter u */ 0x11BC, /* 0x63 c: jongseong ieung */ 0x110B, /* 0x64 d: choseong ieung */ 0x1103, /* 0x65 e: choseong dieud */ 0x1105, /* 0x66 f: choseong lieul*/ 0x1112, /* 0x67 g: choseong hieuh */ 0x3157, /* 0x68 h: hangeul letter o */ 0x1161, /* 0x69 i: jungseong a */ 0x3153, /* 0x6A j: hangeul letter eo */ 0x314F, /* 0x6B k: hangeul letter a */ 0x3163, /* 0x6C l: hangeul letter i */ 0x1173, /* 0x6D m: jungseong eu */ 0x116E, /* 0x6E n: jungseong u */ 0x1175, /* 0x6F o: jungseong i */ 0x1166, /* 0x70 p: jungseong e */ 0x1107, /* 0x71 q: choseong bieub */ 0x1100, /* 0x72 r: choseong gieug */ 0x1102, /* 0x73 s: choseong nieun */ 0x1109, /* 0x74 t: choseong sieus */ 0x1165, /* 0x75 u: jungseong u */ 0x11AF, /* 0x76 v: jongseong lieul */ 0x110C, /* 0x77 w: choseong jieuj */ 0x11AB, /* 0x78 x: jongseong nieun */ 0x1169, /* 0x79 y: jungseong o */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x11BD, /* 0x7B braceleft: jongseong jieuj */ 0x0000, /* 0x7C bar: */ 0x0000, /* 0x7D braceright: */ 0x0000, /* 0x7E asciitilde: */ ]; // 외솔 타자기 (두벌식 호환형 세벌식) K3_Oesol_Typewriter_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x00B0, /* 0x22 quotedbl: degree sign */ 0xFFE6, /* 0x23 numbersign: won sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x003A, /* 0x26 ampersand: colon */ 0x116E, /* 0x27 apostrophe: jungseong u */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x0027, /* 0x2A asterisk: apostrophe */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x1169, /* 0x2F slash: jungseong o */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x11AC, /* 0x3A colon: jongseong nieun-jieuj */ 0x3162, /* 0x3B semicolon: hangeul letter eui */ 0x002F, /* 0x3C less: slash */ 0x003D, /* 0x3D equal: equals sign */ 0x003F, /* 0x3E greater: question mark */ 0x0040, /* 0x3F question: commertial at */ 0x0022, /* 0x40 at: quotatioin mark */ 0x11B7, /* 0x41 A: jongseong mieum */ 0x11AA, /* 0x42 B: jongseong gieug-sieus */ 0x11BE, /* 0x43 C: jongseong chieuch */ 0x11BC, /* 0x44 D: jongseong ieung */ 0x11AE, /* 0x45 E: jongseong dieud */ 0x11AF, /* 0x46 F: jongseong lieul */ 0x11C2, /* 0x47 G: jongseong hieuh */ 0x11AD, /* 0x48 H: jongseong nieun-hieuh */ 0x11B0, /* 0x49 I: jongseong lieul-gieug */ 0x11B9, /* 0x4A J: jongseong bieub-sieus */ 0x11BB, /* 0x4B K: jongseong ssang_sieus */ 0x11A9, /* 0x4C L: jongseong ssang_gieug */ 0x11B2, /* 0x4D M: jongseong lieul-bieub */ 0x11B3, /* 0x4E N: jongseong lieul-sieus */ 0x11B4, /* 0x4F O: jongseong lieul-tieut */ 0x11B5, /* 0x50 P: jongseong lieul-pieup */ 0x11B8, /* 0x51 Q: jongseong bieub */ 0x11A8, /* 0x52 R: jongseong gieug */ 0x11AB, /* 0x53 S: jongseong nieun */ 0x11BA, /* 0x54 T: jongseong sieus */ 0x11B1, /* 0x55 U: jongseong lieul-mieum */ 0x11C1, /* 0x56 V: jongseong pieup */ 0x11BD, /* 0x57 W: jongseong jieuj */ 0x11C0, /* 0x58 X: jongseong tieut */ 0x11B6, /* 0x59 Y: jongseong lieul-hieuh */ 0x110F, /* 0x5A Z: choseong kieuk */ 0x3156, /* 0x5B bracketleft: hangeul letter ye */ 0x0000, /* 0x5C backslash: */ 0x0000, /* 0x5D bracketright: */ 0x005F, /* 0x5E asciicircum: underscore */ 0x00D7, /* 0x5F underscore: multiplication sign */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1106, /* 0x61 a: choseong mieum */ 0x3160, /* 0x62 b: hangeul letter yu */ 0x110E, /* 0x63 c: choseong chieu */ 0x110B, /* 0x64 d: choseong ieung */ 0x1103, /* 0x65 e: choseong dieud */ 0x1105, /* 0x66 f: choseong lieul */ 0x1112, /* 0x67 g: chchoseong hieuh */ 0x3157, /* 0x68 h: hangeul letter o */ 0x3151, /* 0x69 i: hangeul letter ya */ 0x3153, /* 0x6A j: hangeul letter eo */ 0x314F, /* 0x6B k: hangeul letter a */ 0x3163, /* 0x6C l: hangeul letter i */ 0x3161, /* 0x6D m: hangeul letter eu */ 0x315C, /* 0x6E n: hangeul letter u */ 0x3150, /* 0x6F o: hangeul lteer ae */ 0x3154, /* 0x70 p: hangeul letter e */ 0x1107, /* 0x71 q: choseong bieub */ 0x1100, /* 0x72 r: choseong gieug */ 0x1102, /* 0x73 s: choseong nieun */ 0x1109, /* 0x74 t: choseong sieus */ 0x3155, /* 0x75 u: hangeul letter yeo */ 0x1111, /* 0x76 v: choseong pieup */ 0x110C, /* 0x77 w: choseong jieuj */ 0x1110, /* 0x78 x: choseong tieut */ 0x315B, /* 0x79 y: hangeul letter yo */ 0x110F, /* 0x7A z: choseong kieuk */ 0x3152, /* 0x7B braceleft: hangeul letter yae */ 0x0000, /* 0x7C bar: */ 0x0000, /* 0x7D braceright: */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 1985 표준 타자기 (2벌식 호환형 4벌식) K4_1985_Typewriter_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x11BB, /* 0x22 quotedbl: jongseong ssang_sieus */ 0xFFE6, /* 0x23 numbersign: won sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x003A, /* 0x26 ampersand: colon */ 0x1174, /* 0x27 apostrophe: jungseong eui */ 0x0029, /* 0x28 parenleft: right parenthesis */ 0x002D, /* 0x29 parenright: minus sign */ 0x0028, /* 0x2A asterisk: left parenthesis *///0x0027 apostrophe 0x11B5, /* 0x2B plus: jongseong lieul-pieup */ 0x002C, /* 0x2C comma: comma */ 0x00D7, /* 0x2D minus: multiplication sign */ 0x116E, /* 0x2E period: jungseong u */ 0x1169, /* 0x2F slash: jungseong o */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x11AD, /* 0x3A colon: jongseong nieun-jieuj */ 0x1168, /* 0x3B semicolon: jungseong ye */ 0x11B9, /* 0x3C less: jongseong bieub-sieus */ 0x002B, /* 0x3D equal: plus sign */ 0x002E, /* 0x3E greater: period */ 0x003F, /* 0x3F question: question mark */ 0x0022, /* 0x40 at: quotatioin mark */ 0x11B7, /* 0x41 A: jongseong mieum */ 0x1172, /* 0x42 B: jungseong yu */ 0x11BE, /* 0x43 C: jongseong chieuch */ 0x11BC, /* 0x44 D: jongseong ieung */ 0x11AE, /* 0x45 E: jongseong dieud */ 0x11AF, /* 0x46 F: jongseong lieul */ 0x11C2, /* 0x47 G: jongseong hieuh */ 0x1169, /* 0x48 H: jungseong o */ 0x1163, /* 0x49 I: jungseong ya */ 0x1165, /* 0x4A J: jungseong eo */ 0x1161, /* 0x4B K: jungseong a */ 0x1175, /* 0x4C L: jungseong i */ 0x1173, /* 0x4D M: jungseong eu */ 0x116E, /* 0x4E N: jungseong u */ 0x1162, /* 0x4F O: jungseong ae */ 0x1166, /* 0x50 P: jungseong e */ 0x11B8, /* 0x51 Q: jongseong bieub */ 0x11A8, /* 0x52 R: jongseong gieug */ 0x11AB, /* 0x53 S: jongseong nieun */ 0x11BA, /* 0x54 T: jongseong sieus */ 0x1167, /* 0x55 U: jungseong yeo */ 0x11C1, /* 0x56 V: jongseong pieup */ 0x11BD, /* 0x57 W: jongseong jieuj */ 0x11C0, /* 0x58 X: jongseong tieut */ 0x116D, /* 0x59 Y: jungseong yo */ 0x110F, /* 0x5A Z: choseong kieuk */ 0x1164, /* 0x5B bracketleft: jungseong yae */ 0x0000, /* 0x5C backslash: */ 0x0000, /* 0x5D bracketright: */ 0x002F, /* 0x5E asciicircum: slash */ 0x11A9, /* 0x5F underscore: jongseong ssang_gieug */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1106, /* 0x61 a: choseong mieum */ 0x3160, /* 0x62 b: hangeul letter yu */ 0x110E, /* 0x63 c: choseong chieu */ 0x110B, /* 0x64 d: choseong ieung */ 0x1103, /* 0x65 e: choseong dieud */ 0x1105, /* 0x66 f: choseong lieul */ 0x1112, /* 0x67 g: chchoseong hieuh */ 0x3157, /* 0x68 h: hangeul letter o */ 0x3151, /* 0x69 i: hangeul letter ya */ 0x3153, /* 0x6A j: hangeul letter eo */ 0x314F, /* 0x6B k: hangeul letter a */ 0x3163, /* 0x6C l: hangeul letter i */ 0x3161, /* 0x6D m: hangeul letter eu */ 0x315C, /* 0x6E n: hangeul letter u */ 0x3150, /* 0x6F o: hangeul lteer ae */ 0x3154, /* 0x70 p: hangeul letter e */ 0x1107, /* 0x71 q: choseong bieub */ 0x1100, /* 0x72 r: choseong gieug */ 0x1102, /* 0x73 s: choseong nieun */ 0x1109, /* 0x74 t: choseong sieus */ 0x3155, /* 0x75 u: hangeul letter yeo */ 0x1111, /* 0x76 v: choseong pieup */ 0x110C, /* 0x77 w: choseong jieuj */ 0x1110, /* 0x78 x: choseong tieut */ 0x315B, /* 0x79 y: hangeul letter yo */ 0x110F, /* 0x7A z: choseong kieuk */ 0x11B0, /* 0x7B braceleft: jongseong lieul-gieug */ 0x0000, /* 0x7C bar: */ 0x0000, /* 0x7D braceright: */ 0x007E /* 0x7E asciitilde: tilde */ ]; // 공병우 문장용 타자기 K3_Gong_Munjang_layout = [ 0x11BE, /* 0x21 exclam: jongseong chieuch */ 0x0021, /* 0x22 quotedbl: exclam */ 0x11C1, /* 0x23 numbersign: jongseong pieup */ 0x11BD, /* 0x24 dollar: jongseong jieuj */ 0x3152, /* 0x25 percent: hangeul letter yae */ 0x00B7, /* 0x26 ampersand: middle dot */ 0x3162, /* 0x27 apostrophe: hangeul letter eui */ 0x004B, /* 0x28 parenleft: K */ 0x004D, /* 0x29 parenright: M */ 0x002F, /* 0x2A asterisk: slash */ 0x004C, /* 0x2B plus: L */ 0x002C, /* 0x2C comma */ 0x3156, /* 0x2D minus: hangeul letter ye */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x3151, /* 0x30 0: hangeul letter ya */ 0x11AD, /* 0x31 1: jongseong nieun-hieuh */ 0x11C2, /* 0x32 2: jongseong hieuh */ 0x11BB, /* 0x33 3: jongseong ssang_sieus */ 0x11B9, /* 0x34 4: jongseong bieub-sieus */ 0x3150, /* 0x35 5: hangeul lteer ae */ 0x3155, /* 0x36 6: hangeul letter yeo */ 0x1111, /* 0x37 7: choseong pieup */ 0x1110, /* 0x38 8: choseong tieuh */ 0x110F, /* 0x39 9: choseong kieuk */ 0x0034, /* 0x3A colon: 4 */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x003C, /* 0x3C less: less-than sign */ 0x3160, /* 0x3D equal: hangeul letter yu */ 0x003E, /* 0x3E greater: greater-than sign */ 0x0023, /* 0x3F question: number sign */ 0x11AE, /* 0x40 at: jongseong dieud */ 0x11B2, /* 0x41 A: jongseong lieul-bieub */ 0x300D, /* 0x42 B: right corner bracket 」 */ 0x002A, /* 0x43 C: aterisk */ 0x11B0, /* 0x44 D: jongseong lieul-gieug */ 0x11A9, /* 0x45 E: jongseong ssang_gieug */ 0x0022, /* 0x46 F: quotatioin mark */ 0x003D, /* 0x47 G: equals sign */ 0x0030, /* 0x48 H: 0 */ 0x0037, /* 0x49 I: 7 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0029, /* 0x4D M: parenright */ 0x0028, /* 0x4E N: parenleft */ 0x0038, /* 0x4F O: 8 */ 0x0039, /* 0x50 P: 9 */ 0x11AA, /* 0x51 Q: jongseong gieug-sieus */ 0x003F, /* 0x52 R: question mark */ 0x11B6, /* 0x53 S: jongseong lieul-hieuh */ 0x00D7, /* 0x54 T: multiplication sign × */ 0x0036, /* 0x55 U: 6 */ 0x300C, /* 0x56 V: left corner bracket 「 */ 0x11B1, /* 0x57 W: jongseong lieul-mieum */ 0x11AC, /* 0x58 X: jongseong nieun-jieuj */ 0x0035, /* 0x59 Y: 5 */ 0x11BF, /* 0x5A Z: jongseong kieuk */ 0x116E, /* 0x5B bracketleft: jungseong u */ 0x0000, /* 0x5C backslash: */ 0x0000, /* 0x5D bracketright: */ 0x003A, /* 0x5E asciicircum: middle colon */ 0x0047, /* 0x5F underscore: G */ 0x0000, /* 0x60 quoteleft: */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x315C, /* 0x62 b: hangeul letter u */ 0x3154, /* 0x63 c: hangeul letter e */ 0x11AB, /* 0x64 d: jongseong nieun */ 0x11A8, /* 0x65 e: jongseong gieug */ 0x314F, /* 0x66 f: hangeul letter a */ 0x3161, /* 0x67 g: hangeul letter eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1103, /* 0x6B k: choseong dieud */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x315B, /* 0x70 p: hangeul letter yo */ 0x11C0, /* 0x71 q: jongseong tieut */ 0x3163, /* 0x72 r: hangeul letter i */ 0x11AF, /* 0x73 s: jongseong lieul */ 0x3153, /* 0x74 t: hangeul letter eo */ 0x1100, /* 0x75 u: choseong gieug */ 0x3157, /* 0x76 v: hangeul letter o */ 0x11B7, /* 0x77 w: jongseong mieum */ 0x11BA, /* 0x78 x: jongseong sieus */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B8, /* 0x7A z: jongseong bieub */ 0x002D, /* 0x7B braceleft: minus sign */ 0x0000, /* 0x7C bar: */ 0x0000, /* 0x7D braceright: */ 0x0000 /* 0x7E asciitilde: */ ]; // 3-87 자판 K3_87_layout = [ 0x11BF, /* 0x21 exclam: jongseong kieuk */ 0x201D, /* 0x22 quotedbl: right double quotation mark ” */ 0x11AC, /* 0x23 numbersign: jongseong nieun-jieuj */ 0x11B1, /* 0x24 dollar: jongseong lieul-mieum */ 0x11B4, /* 0x25 percent: jongseong lieul-tieut */ 0x0037, /* 0x26 ampersand: 7 */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x0039, /* 0x28 parenleft: 9 */ 0x002F, /* 0x29 parenright: slash */ 0x0038, /* 0x2A asterisk: 8 */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x300C, /* 0x2D minus: left corner bracket 「 */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x0029, /* 0x30 0: parenright */ 0x11B8, /* 0x31 1: jongseong bieub */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x116D, /* 0x33 3: jungseong yo */ 0x1168, /* 0x34 4: jungseong ye */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x0028, /* 0x37 7: parenleft */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x201C, /* 0x3A colon : left double quotation mark “ */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x002C, /* 0x3C less: comma */ 0x300D, /* 0x3D equal: right corner bracket 」 */ 0x002E, /* 0x3E greater: period */ 0x00B7, /* 0x3F question: middle dot */ 0x11BD, /* 0x40 at: jongseong jieuj */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x11B2, /* 0x42 B: jongseong lieul-bieub */ 0x203B, /* 0x43 C: reference mark */ 0x1164, /* 0x44 D: jungseong yae */ 0x11B8, /* 0x45 E: jongseong bieub */ 0x11A9, /* 0x46 F: jongseong ssang_gieug */ 0x11B0, /* 0x47 G: jongseong lieul-gieug */ 0x0022, /* 0x48 H: quotatioin mark */ 0x0035, /* 0x49 I: 5 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0030, /* 0x4D M: 0 */ 0x002D, /* 0x4E N: minus sign */ 0x0036, /* 0x4F O: 6 */ 0x003C, /* 0x50 P: less-than sign */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11C2, /* 0x52 R: jongseong hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x11B6, /* 0x54 T: jongseong lieul-hieuh */ 0x0034, /* 0x55 U: 4 */ 0x0021, /* 0x56 V: exclam */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x0027, /* 0x59 Y: apostrophe */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x110F, /* 0x5B bracketleft: choseong kieuk */ 0x003A, /* 0x5C backslash: colon */ 0x003F, /* 0x5D bracketright: question mark */ 0x11B5, /* 0x5E asciicircum: jongseong lieul-pieup */ 0x0025, /* 0x5F underscore: percent */ 0x11B3, /* 0x60 quoteleft: jongseong lieul-sieus */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1162, /* 0x64 d: jungseong ae */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1103, /* 0x6B k: choseong dieud */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1175, /* 0x72 r: jungseong i */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1100, /* 0x75 u: choseong gieug */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x003E, /* 0x7B braceleft: greater-than sign */ 0x003B, /* 0x7C bar: semicolon */ 0x005C, /* 0x7D braceright : backslash */ 0x11AA /* 0x7E asciitilde: jongseong gieug-sieus */ ]; // 3-87 자판 확장 배열 K3_87_extended_layout = [ [[0x11B5],[0x0000]], /* 0x21 exclam: jongseong lieul-pieup */ [[0x2193],[0x0000]], /* 0x22 quotedbl: downwards arrow ↓ */ [[0x0023],[0x0000]], /* 0x23 numbersign: number sign */ [[0x0024],[0x0000]], /* 0x24 dollar: dollar */ [[0x00A9],[0x0000]], /* 0x25 percent: copyright sign © */ [[0x2122],[0x0000]], /* 0x26 ampersand: trademark ™ */ [[0x2019],[0x0000]], /* 0x27 apostrophe: right single quotation mark ’ */ [[0x25B2],[0x0000]], /* 0x28 parenleft: black up-pointing ▲ */ [[0x00B0],[0x0000]], /* 0x29 parenright: degree sign ° */ [[0x002A],[0x0000]], /* 0x2A asterisk: aterisk */ [[0x003D],[0x0000]], /* 0x2B plus: equal sign */ [[0x2192],[0x0000]], /* 0x2C comma: rightwards arrow → */ [[0x2610],[0x0000]], /* 0x2D minus: ballot box ☐ */ [[0x2190],[0x0000]], /* 0x2E period: leftwards arrow ← */ [[0x25FE],[0x0000]], /* 0x2F slash: black medium small square ◾ */ [[0x2070],[0x0000]], /* 0x30 0: superscript zero ⁰ */ [[0x00B9],[0x0000]], /* 0x31 1: superscript one ¹ */ [[0x00B2],[0x0000]], /* 0x32 2: superscript two ² */ [[0x00B3],[0x0000]], /* 0x33 3: superscript three ³ */ [[0x2074],[0x0000]], /* 0x34 4: superscript four ⁴ */ [[0x2075],[0x0000]], /* 0x35 5: superscript five ⁵ */ [[0x2076],[0x0000]], /* 0x36 6: superscript six ⁶ */ [[0x2077],[0x0000]], /* 0x37 7: superscript seven ⁷ */ [[0x2078],[0x0000]], /* 0x38 8: superscript eight ⁸ */ [[0x2079],[0x0000]], /* 0x39 9: superscript nine ⁹ */ [[0x2191],[0x0000]], /* 0x3A colon: upwards arrow ↑ */ [[0x2018],[0x0000]], /* 0x3B semicolon: left single quotation mark ‘ */ [[0x2198],[0x0000]], /* 0x3C less: south-east arrow ↘ */ [[0x2713],[0x0000]], /* 0x3D equal: check mark ✓ */ [[0x2015],[0x0000]], /* 0x3E greater: horizontal bar ― */ [[0x00B1],[0x0000]], /* 0x3F question: plus minus sign ± */ [[0x0040],[0x0000]], /* 0x40 at: at */ [[0x0041],[0x0000]], /* 0x41 A: */ [[0x0042],[0x0000]], /* 0x42 B: */ [[0x0043],[0x0000]], /* 0x43 C: */ [[0x0044],[0x0000]], /* 0x44 D: */ [[0x0045],[0x0000]], /* 0x45 E: */ [[0x0046],[0x0000]], /* 0x46 F: */ [[0x0047],[0x0000]], /* 0x47 G: */ [[0x0048],[0x0000]], /* 0x48 H: */ [[0x0049],[0x0000]], /* 0x49 I: */ [[0x004A],[0x0000]], /* 0x4A J: */ [[0x004B],[0x0000]], /* 0x4B K: */ [[0x004C],[0x0000]], /* 0x4C L: */ [[0x004D],[0x0000]], /* 0x4D M: */ [[0x004E],[0x0000]], /* 0x4E N: */ [[0x004F],[0x0000]], /* 0x4F O: */ [[0x0050],[0x0000]], /* 0x50 P: */ [[0x0051],[0x0000]], /* 0x51 Q: */ [[0x0052],[0x0000]], /* 0x52 R: */ [[0x0053],[0x0000]], /* 0x53 S: */ [[0x0054],[0x0000]], /* 0x54 T: */ [[0x0055],[0x0000]], /* 0x55 U: */ [[0x0056],[0x0000]], /* 0x56 V: */ [[0x0057],[0x0000]], /* 0x57 W: */ [[0x0058],[0x0000]], /* 0x58 X: */ [[0x0059],[0x0000]], /* 0x59 Y: */ [[0x005A],[0x0000]], /* 0x5A Z: */ [[0x005B],[0x0000]], /* 0x5B bracketleft: bracketleft */ [[0x005C],[0x0000]], /* 0x5C backslash: backslash */ [[0x005D],[0x0000]], /* 0x5D bracketright: bracketright */ [[0x00AE],[0x0000]], /* 0x5E asciicircum: registerd sign ® */ [[0x007E],[0x0000]], /* 0x5F underscore: tilde */ [[0x11B3],[0x0000]], /* 0x60 quoteleft: jongseong lieul-sieus */ [[0x0061],[0x0000]], /* 0x61 a: */ [[0x0062],[0x0000]], /* 0x62 b: */ [[0x0063],[0x0000]], /* 0x63 c: */ [[0x0064],[0x0000]], /* 0x64 d: */ [[0x0065],[0x0000]], /* 0x65 e: */ [[0x0066],[0x0000]], /* 0x66 f: */ [[0x0067],[0x0000]], /* 0x67 g: */ [[0x0068],[0x0000]], /* 0x68 h: */ [[0x0069],[0x0000]], /* 0x69 i: */ [[0x006A],[0x0000]], /* 0x6A j: */ [[0x006B],[0x0000]], /* 0x6B k: */ [[0x006C],[0x0000]], /* 0x6C l: */ [[0x006D],[0x0000]], /* 0x6D m: */ [[0x006E],[0x0000]], /* 0x6E n: */ [[0x006F],[0x0000]], /* 0x6F o: */ [[0x0070],[0x0000]], /* 0x70 p: */ [[0x0071],[0x0000]], /* 0x71 q: */ [[0x0072],[0x0000]], /* 0x72 r: */ [[0x0073],[0x0000]], /* 0x73 s: */ [[0x0074],[0x0000]], /* 0x74 t: */ [[0x0075],[0x0000]], /* 0x75 u: */ [[0x0076],[0x0000]], /* 0x76 v: */ [[0x0077],[0x0000]], /* 0x77 w: */ [[0x0078],[0x0000]], /* 0x78 x: */ [[0x0079],[0x0000]], /* 0x79 y: */ [[0x007A],[0x0000]], /* 0x7A z: */ [[0x007B],[0x0000]], /* 0x7B braceleft: braceleft */ [[0x007C],[0x0000]], /* 0x7C bar: vertical bar */ [[0x007D],[0x0000]], /* 0x7D braceright: braceright */ [[0x11AA],[0x0000]] /* 0x7E asciitilde: jongseong gieug-sieus */ ]; // 3-89 자판 K3_89_layout = [ 0x11C2, /* 0x21 exclam: jongseong hieuh */ 0x0023, /* 0x22 quotedbl: number sign */ 0x11BF, /* 0x23 numbersign: jongseong kieuk */ 0x11B5, /* 0x24 dollar: jongseong lieul-pieup */ 0x11B4, /* 0x25 percent: jongseong lieul-tieut */ 0x0037, /* 0x26 ampersand: 7 */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x0039, /* 0x28 parenleft: 9 */ 0x0025, /* 0x29 parenright: percent */ 0x0038, /* 0x2A asterisk: 8 */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x002A, /* 0x2D minus: aterisk */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x0029, /* 0x30 0: parenright */ 0x11B8, /* 0x31 1: jongseong bieub */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x116D, /* 0x33 3: jungseong yo */ 0x1168, /* 0x34 4: jungseong ye */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x0028, /* 0x37 7: parenleft */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x003A, /* 0x3A colon */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x002C, /* 0x3C less: comma */ 0x003D, /* 0x3D equal */ 0x002E, /* 0x3E greater: period */ 0x0021, /* 0x3F question: exclam */ 0x11BD, /* 0x40 at: jongseong jieuj */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x003F, /* 0x42 B: question mark */ 0x11B1, /* 0x43 C: jongseong lieul-mieum */ 0x11B0, /* 0x44 D: jongseong lieul-gieug */ 0x11AC, /* 0x45 E: jongseong nieun-jieuj */ 0x11A9, /* 0x46 F: jongseong ssang_gieug */ 0x11AA, /* 0x47 G: jongseong gieug-sieus */ 0x0022, /* 0x48 H: quotatioin mark */ 0x0035, /* 0x49 I: 5 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0030, /* 0x4D M: 0 */ 0x002D, /* 0x4E N: minus sign */ 0x0036, /* 0x4F O: 6 */ 0x007E, /* 0x50 P: tilde */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11B6, /* 0x52 R: jongseong lieul-hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x11B3, /* 0x54 T: jongseong lieul-sieus */ 0x0034, /* 0x55 U: 4 */ 0x11B2, /* 0x56 V: jongseong lieul-bieub */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x0027, /* 0x59 Y: apostrophe */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x110F, /* 0x5B bracketleft: choseong kieuk */ 0x005C, /* 0x5C backslash */ 0x003B, /* 0x5D bracketright: semicolon */ 0x1164, /* 0x5E asciicircum: jungseong yae */ 0x002F, /* 0x5F underscore: slash */ 0x005B, /* 0x60 quoteleft: bracketleft */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1162, /* 0x72 r: jungseong ae */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft */ 0x0040, /* 0x7C bar: commertial at */ 0x007D, /* 0x7D braceright */ 0x005D /* 0x7E asciitilde: bracketright */ ]; // 3-891 자판 K3_891_layout = [ 0x11BD, /* 0x21 exclam: jongseong jieuj */ 0x00B7, /* 0x22 quotedbl: middle dot */ 0x11BF, /* 0x23 numbersign: jongseong kieuk */ 0x11B5, /* 0x24 dollar: jongseong lieul-pieup */ 0x11B4, /* 0x25 percent: jongseong lieul-tieut */ 0x201C, /* 0x26 ampersand: left double quotation mark */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x2018, /* 0x28 parenleft: left single quotation mark */ 0x2019, /* 0x29 parenright: right single quotation mark */ 0x201D, /* 0x2A asterisk: right double quotation mark */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x0029, /* 0x2D minus: parenright */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuk */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x0034, /* 0x3A colon: 4 */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x002C, /* 0x3C less: comma */ 0x003A, /* 0x3D equal: colon */ 0x002E, /* 0x3E greater: period */ 0x0021, /* 0x3F question: exclam */ 0x203B, /* 0x40 at: reference mark */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x003F, /* 0x42 B: question mark */ 0x11B1, /* 0x43 C: jongseong lieul-mieum */ 0x11B0, /* 0x44 D: jongseong lieul-gieug */ 0x11AC, /* 0x45 E: jongseong nieun-jieuj */ 0x11A9, /* 0x46 F: jongseong ssang_gieug */ 0x11AA, /* 0x47 G: jongseong gieug-sieus */ 0x0030, /* 0x48 H: 0 */ 0x0037, /* 0x49 I: 7 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0022, /* 0x4D M: quotatioin mark */ 0x002D, /* 0x4E N: minus sign */ 0x0038, /* 0x4F O: 8 */ 0x0039, /* 0x50 P: 9 */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11B6, /* 0x52 R: jongseong lieul-hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x11B3, /* 0x54 T: jongseong lieul-sieus */ 0x0036, /* 0x55 U: 6 */ 0x11B2, /* 0x56 V: jongseong lieul-bieub */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x0035, /* 0x59 Y: 5 */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x0028, /* 0x5B bracketleft: parenleft */ 0x003E, /* 0x5C backslash: greater-than sign */ 0x003C, /* 0x5D bracketright: less-than sign */ 0x2170, /* 0x5E asciicircum: small roman numeral one */ 0x007E, /* 0x5F underscore: tilde */ 0x1164, /* 0x60 quoteleft: jungseong yae */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1162, /* 0x72 r: jungseong ae */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x0025, /* 0x7B braceleft: percent */ 0x005C, /* 0x7C bar: backslash */ 0x002F, /* 0x7D braceright: slash */ 0x0000 /* 0x7E asciitilde */ ]; // 3-891 자판 확장 배열 K3_891_extended_layout = [ [[0xFFE5],[0x0000]], /* 0x21 exclam: fullwidth yen sign */ [[0x2193],[0x0000]], /* 0x22 quotedbl: downwards arrow ↓ */ [[0x0023],[0x0000]], /* 0x23 numbersign: number sign */ [[0x0024],[0x0000]], /* 0x24 dollar: dollar */ [[0x00B1],[0x0000]], /* 0x25 percent: plus minus sign ± */ [[0x2026],[0x0000]], /* 0x26 ampersand: horizontal ellipsis … */ [[0x110A],[0x0000]], /* 0x27 apostrophe: choseong ssang-sieus */ [[0x25B2],[0x0000]], /* 0x28 parenleft: black up-pointing ▲ */ [[0x00B0],[0x0000]], /* 0x29 parenright: degree sign ° */ [[0x002A],[0x0000]], /* 0x2A asterisk: asterisk */ [[0x003D],[0x0000]], /* 0x2B plus: equal sign */ [[0x1101],[0x0000]], /* 0x2C comma: choseong ssang-gieug */ [[0x2610],[0x0000]], /* 0x2D minus: ballot box ☐ */ [[0x110D],[0x0000]], /* 0x2E period: choseong ssang-jieuj */ [[0x1108],[0x0000]], /* 0x2F slash: choseong ssang-bieub */ [[0x2122],[0x0000]], /* 0x30 0: trademark ™ */ [[0x00B9],[0x0000]], /* 0x31 1: superscript one ¹ */ [[0x00B2],[0x0000]], /* 0x32 2: superscript two ² */ [[0xACF5],[0x0000]], /* 0x33 3: 공 */ [[0xACF5],[0x0000]], /* 0x34 4: 공 */ [[0x327E],[0x0000]], /* 0x35 5: circled hangeul ieung u */ [[0x2076],[0x0000]], /* 0x36 6: superscript six ⁶, */ [[0x2015],[0x0000]], /* 0x37 7: horizontal bar ―, */ [[0x00AE],[0x0000]], /* 0x38 8: registerd sign ® */ [[0x00A9],[0x0000]], /* 0x39 9: copyright sign © */ [[0x2191],[0x0000]], /* 0x3A colon: upwards arrow ↑ */ [[0x1104],[0x0000]], /* 0x3B semicolon: choseong ssang-dieud, */ [[0x2198],[0x0000]], /* 0x3C less: south-east arrow ↘ */ [[0x2713],[0x0000]], /* 0x3D equal: check mark ✓ */ [[0x2190],[0x0000]], /* 0x3E greater: leftwards arrow ← */ [[0x25FE],[0x0000]], /* 0x3F question: black medium small square ◾ */ [[0x0040],[0x0000]], /* 0x40 at: at */ [[0x0041],[0x0000]], /* 0x41 A: */ [[0x0042],[0x0000]], /* 0x42 B: */ [[0x0043],[0x0000]], /* 0x43 C: */ [[0x0044],[0x0000]], /* 0x44 D: */ [[0x0045],[0x0000]], /* 0x45 E: */ [[0x0046],[0x0000]], /* 0x46 F: */ [[0x0047],[0x0000]], /* 0x47 G: */ [[0x0048],[0x0000]], /* 0x48 H: */ [[0x0049],[0x0000]], /* 0x49 I: */ [[0x004A],[0x0000]], /* 0x4A J: */ [[0x004B],[0x0000]], /* 0x4B K: */ [[0x004C],[0x0000]], /* 0x4C L: */ [[0x004D],[0x0000]], /* 0x4D M: */ [[0x004E],[0x0000]], /* 0x4E N: */ [[0x004F],[0x0000]], /* 0x4F O: */ [[0x0050],[0x0000]], /* 0x50 P: */ [[0x0051],[0x0000]], /* 0x51 Q: */ [[0x0052],[0x0000]], /* 0x52 R: */ [[0x0053],[0x0000]], /* 0x53 S: */ [[0x0054],[0x0000]], /* 0x54 T: */ [[0x0055],[0x0000]], /* 0x55 U: */ [[0x0056],[0x0000]], /* 0x56 V: */ [[0x0057],[0x0000]], /* 0x57 W: */ [[0x0058],[0x0000]], /* 0x58 X: */ [[0x0059],[0x0000]], /* 0x59 Y: */ [[0x005A],[0x0000]], /* 0x5A Z: */ [[0x005B],[0x0000]], /* 0x5B bracketleft: bracketleft */ [[0x005C],[0x0000]], /* 0x5C backslash: backslash, */ [[0x005D],[0x0000]], /* 0x5D bracketright: bracketright */ [[0x0000],[0x0000]], /* 0x5E asciicircum: */ [[0x25A1],[0x0000]], /* 0x5F underscore: white squre */ [[0x0000],[0x0000]], /* 0x60 quoteleft: */ [[0x0061],[0x0000]], /* 0x61 a: */ [[0x0062],[0x0000]], /* 0x62 b: */ [[0x0063],[0x0000]], /* 0x63 c: */ [[0x0064],[0x0000]], /* 0x64 d: */ [[0x0065],[0x0000]], /* 0x65 e: */ [[0x0066],[0x0000]], /* 0x66 f: */ [[0x0067],[0x0000]], /* 0x67 g: */ [[0x0068],[0x0000]], /* 0x68 h: */ [[0x0069],[0x0000]], /* 0x69 i: */ [[0x006A],[0x0000]], /* 0x6A j: */ [[0x006B],[0x0000]], /* 0x6B k: */ [[0x006C],[0x0000]], /* 0x6C l: */ [[0x006D],[0x0000]], /* 0x6D m: */ [[0x006E],[0x0000]], /* 0x6E n: */ [[0x006F],[0x0000]], /* 0x6F o: */ [[0x0070],[0x0000]], /* 0x70 p: */ [[0x0071],[0x0000]], /* 0x71 q: */ [[0x0072],[0x0000]], /* 0x72 r: */ [[0x0073],[0x0000]], /* 0x73 s: */ [[0x0074],[0x0000]], /* 0x74 t: */ [[0x0075],[0x0000]], /* 0x75 u: */ [[0x0076],[0x0000]], /* 0x76 v: */ [[0x0077],[0x0000]], /* 0x77 w: */ [[0x0078],[0x0000]], /* 0x78 x: */ [[0x0079],[0x0000]], /* 0x79 y: */ [[0x007A],[0x0000]], /* 0x7A z: */ [[0x300C],[0x0000]], /* 0x7B braceleft: left corner bracker 「 */ [[0x007C],[0x0000]], /* 0x7C bar: vertical bar */ [[0x300D],[0x0000]], /* 0x7D braceright: right corner bracker 」 */ [[0x003B],[0x0000]] /* 0x7E asciitilde: semicolon */ ]; // 3-95 자판안 (김창용) K3_95_proposal_layout = [ 0x11A9, /* 0x21 exclam: jongseong ssang_gieug */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x11BD, /* 0x23 numbersign: jognseong jieuj */ 0x11B5, /* 0x24 dollar: jongseong lieul-pieup */ 0x11B4, /* 0x25 percent: jongseong lieul-tieut */ 0x0026, /* 0x26 ampersand: ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieut */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuk */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong yi */ 0x116E, /* 0x39 9: jungseong u */ 0x003A, /* 0x3A colon: colon */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x0032, /* 0x3C less: 2 */ 0x003D, /* 0x3D equal: euals sign */ 0x0033, /* 0x3E greater: 3 */ 0x003F, /* 0x3F question: question mark */ 0x11B0, /* 0x40 at: jongseong lieul-gieug */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x0021, /* 0x42 B: exclamation mark */ 0x11BF, /* 0x43 C: jongseong kieuk */ 0x11B2, /* 0x44 D: jongseong lieul-bieub */ 0x11AC, /* 0x45 E: jongseong nieun-jieuj */ 0x11B1, /* 0x46 F: jongseong lieul-mieum */ 0x1164, /* 0x47 G: jungseong yae */ 0x0027, /* 0x48 H: apostrophe */ 0x0038, /* 0x49 I: 8 */ 0x0034, /* 0x4A J: 4 */ 0x0035, /* 0x4B K: 5 */ 0x0036, /* 0x4C L: 6 */ 0x0031, /* 0x4D M: 1 */ 0x0030, /* 0x4E N: 0 */ 0x0039, /* 0x4F O: 9 */ 0x003E, /* 0x50 P: greater-than sign */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11B6, /* 0x52 R: jongseong lieul-hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x11B3, /* 0x54 T: jongseong lieul-sieus */ 0x0037, /* 0x55 U: 7 */ 0x11AA, /* 0x56 V: jongseong gieug-sieus */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x003C, /* 0x59 Y: less-than sign */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x003B, /* 0x5C backslash: semicolon */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1162, /* 0x72 r: jungseong ae */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x00B7, /* 0x7C bar: middle dot */ 0x007D, /* 0x7D braceright: right brace */ 0x007E, /* 0x7E asciitilde: tilde */ ]; K3_95_extended_layout = [ // 3-95 김창용 자판안 확장 배열 [[0x0000],[0x0000]], /* 0x21 exclam: */ [[0x0000],[0x0000]], /* 0x22 quotedbl: */ [[0x0000],[0x0000]], /* 0x23 numbersign */ [[0x0000],[0x0000]], /* 0x24 dollar */ [[0x0000],[0x0000]], /* 0x25 percent: */ [[0x0000],[0x0000]], /* 0x26 ampersand: */ [[0x0000],[0x0000]], /* 0x27 apostrophe: */ [[0x0000],[0x0000]], /* 0x28 parenleft: */ [[0x0000],[0x0000]], /* 0x29 parenright: */ [[0x0000],[0x0000]], /* 0x2A asterisk */ [[0x0000],[0x0000]], /* 0x2B plus: */ [[0x2026],[0x0000]], /* 0x2C comma: horizontal epllipsis … */ [[0x00F7],[0x0000]], /* 0x2D minus: division sign ÷ */ [[0x00B7],[0x0000]], /* 0x2E period: middle dot · */ [[0x0000],[0x0000]], /* 0x2F slash: */ [[0x300B],[0x0000]], /* 0x30 0: right double angle bracket 》 */ [[0xF8FF],[0x0000]], /* 0x31 1: apple logo (PUA) */ [[0x0040],[0x0000]], /* 0x32 2: commercial at */ [[0x0023],[0x0000]], /* 0x33 3: number sign # */ [[0x0024],[0x0000]], /* 0x34 4: dollar sign $ */ [[0x0025],[0x0000]], /* 0x35 5: percent sign % */ [[0x005E],[0x0000]], /* 0x36 6: circumflex accent ^ */ [[0x26AB],[0x0000]], /* 0x37 7: medium black circle ⚫ */ [[0x203B],[0x0000]], /* 0x38 8: reference mark ※ */ [[0x300A],[0x0000]], /* 0x39 9: left double angle bracket 《 */ [[0x0000],[0x0000]], /* 0x3A colon: */ [[0x1108],[0x0000]], /* 0x3B semicolon: choseong ssang_bieup */ [[0x0000],[0x0000]], /* 0x3C less: */ [[0x2260],[0x0000]], /* 0x3D equal: not equal to */ [[0x0000],[0x0000]], /* 0x3E greater: */ [[0x0000],[0x0000]], /* 0x3F question: */ [[0x0000],[0x0000]], /* 0x40 at */ [[0x0000],[0x0000]], /* 0x41 A: */ [[0x0000],[0x0000]], /* 0x42 B: */ [[0x0000],[0x0000]], /* 0x43 C: */ [[0x0000],[0x0000]], /* 0x44 D: */ [[0x0000],[0x0000]], /* 0x45 E: */ [[0x0000],[0x0000]], /* 0x46 F: */ [[0x0000],[0x0000]], /* 0x47 G: */ [[0x0000],[0x0000]], /* 0x48 H: */ [[0x0000],[0x0000]], /* 0x49 I: */ [[0x0000],[0x0000]], /* 0x4A J: */ [[0x0000],[0x0000]], /* 0x4B K: */ [[0x0000],[0x0000]], /* 0x4C L: */ [[0x0000],[0x0000]], /* 0x4D M: */ [[0x0000],[0x0000]], /* 0x4E N: */ [[0x0000],[0x0000]], /* 0x4F O: */ [[0x0000],[0x0000]], /* 0x50 P: */ [[0x0000],[0x0000]], /* 0x51 Q: */ [[0x0000],[0x0000]], /* 0x52 R: */ [[0x0000],[0x0000]], /* 0x53 S: */ [[0x0000],[0x0000]], /* 0x54 T: */ [[0x0000],[0x0000]], /* 0x55 U: */ [[0x0000],[0x0000]], /* 0x56 V: */ [[0x0000],[0x0000]], /* 0x57 W: */ [[0x0000],[0x0000]], /* 0x58 X: */ [[0x0000],[0x0000]], /* 0x59 Y: */ [[0x0000],[0x0000]], /* 0x5A Z: */ [[0x201C],[0x0000]], /* 0x5B bracketleft: left double quotation mark “ */ [[0x003A],[0x0000]], /* 0x5C backslash: colon */ [[0x201D],[0x0000]], /* 0x5D bracketright: right double quotation mark ” */ [[0x0000],[0x0000]], /* 0x5E asciicircum: */ [[0x0000],[0x0000]], /* 0x5F underscore: */ [[0x007E],[0x0000]], /* 0x60 quoteleft: tilde */ [[0x11BC],[0x0000]], /* 0x61 a: jongseong ieung */ [[0x116E],[0x0000]], /* 0x62 b: jungseong u */ [[0x1166],[0x0000]], /* 0x63 c: jungseong e */ [[0x1175],[0x0000]], /* 0x64 d: jungseong i */ [[0x1167],[0x0000]], /* 0x65 e: jungseong yeo */ [[0x1161],[0x0000]], /* 0x66 f: jungseong a */ [[0x1173],[0x0000]], /* 0x67 g: jungseong eu */ [[0x1102],[0x0000]], /* 0x68 h: choseong nieun */ [[0x1106],[0x0000]], /* 0x69 i: choseong mieum */ [[0x110B],[0x0000]], /* 0x6A j: choseong ieung */ [[0x1101],[0x0000]], /* 0x6B k: choseong ssang_gieug */ [[0x110D],[0x0000]], /* 0x6C l: choseong ssang_jieuj */ [[0x1112],[0x0000]], /* 0x6D m: choseong hieuh */ [[0x110A],[0x0000]], /* 0x6E n: choseong ssang_sieus */ [[0x2018],[0x0000]], /* 0x6F o: left single quotation mark ‘ */ [[0x2019],[0x0000]], /* 0x70 p: right single quotation mark ’ */ [[0x11BA],[0x0000]], /* 0x71 q: jongseong sieus */ [[0x1162],[0x0000]], /* 0x72 r: jungseong ae */ [[0x11AB],[0x0000]], /* 0x73 s: jongseong nieun */ [[0x1165],[0x0000]], /* 0x74 t: jungseong eo */ [[0x1104],[0x0000]], /* 0x75 u: choseong ssang_dieud */ [[0x1169],[0x0000]], /* 0x76 v: jungseong o */ [[0x11AF],[0x0000]], /* 0x77 w: jongseong lieul */ [[0x11A8],[0x0000]], /* 0x78 x: jongseong gieug */ [[0x1105],[0x0000]], /* 0x79 y: choseong lieul */ [[0x11B7],[0x0000]], /* 0x7A z: jongseong mieum */ [[0x0000],[0x0000]], /* 0x7B braceleft: */ [[0x0000],[0x0000]], /* 0x7C bar: */ [[0x0000],[0x0000]], /* 0x7D braceright: */ [[0x0000],[0x0000]] /* 0x7E asciitilde: */ ]; // 순아래 자판 K3_sun1990_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieut */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x11BD, /* 0x2D minus: jongseong jieuj */ 0x002E, /* 0x2E period: period */ 0x11AE, /* 0x2F slash: jongseong dieud */ 0x1164, /* 0x30 0: choseong yae */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong yi */ 0x110F, /* 0x39 9: choseong kieuk */ 0x003A, /* 0x3A colon: colon */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x0032, /* 0x3C less: 2 */ 0x11BE, /* 0x3D equal: jongseong chieuch */ 0x0033, /* 0x3E greater: 3 */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x11BC, /* 0x41 A: jongseong ieung */ 0x0021, /* 0x42 B: exclamation mark */ 0x005C, /* 0x43 C: backslash */ 0x005D, /* 0x44 D: right bracket */ 0x1167, /* 0x45 E: jungseong yeo */ 0x1161, /* 0x46 F: jungseong a */ 0x002F, /* 0x47 G: slash */ 0x0027, /* 0x48 H: apostrophe */ 0x0038, /* 0x49 I: 8 */ 0x0034, /* 0x4A J: 4 */ 0x0035, /* 0x4B K: 5 */ 0x0036, /* 0x4C L: 6 */ 0x0031, /* 0x4D M: 1 */ 0x0030, /* 0x4E N: 0 */ 0x0039, /* 0x4F O: 9 */ 0x003E, /* 0x50 P: greater-than sign */ 0x11BA, /* 0x51 Q: jongseong sieus */ 0x1162, /* 0x52 R: jungseong ae */ 0x005B, /* 0x53 S: left bracket */ 0x003B, /* 0x54 T: semicolon */ 0x0037, /* 0x55 U: 7 */ 0x1169, /* 0x56 V: jungseong o */ 0x11AF, /* 0x57 W: jongseong lieul */ 0x003D, /* 0x58 X: equals sign */ 0x003C, /* 0x59 Y: less-than sign */ 0x002D, /* 0x5A Z: minus sign */ 0x11C0, /* 0x5B bracketleft: jongseong tieut */ 0x11BF, /* 0x5C backslash: jongseong kieuk */ 0x11C1, /* 0x5D bracketright: jongseong pieup */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1162, /* 0x72 r: jungseong ae */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E, /* 0x7E asciitilde: tilde */ ]; K3_Gaon38A_layout = [ 0x0021, /* 0x21 exclam: */ 0x0022, /* 0x22 quotedbl: */ 0x0023, /* 0x23 numbersign: */ 0x0024, /* 0x24 dollar: */ 0x0025, /* 0x25 percent: */ 0x0026, /* 0x26 ampersand: */ 0x0027, /* 0x27 apostrophe: */ 0x0028, /* 0x28 parenleft: */ 0x0029, /* 0x29 parenright: */ 0x002A, /* 0x2A asterisk: */ 0x002B, /* 0x2B plus: */ 0x002C, /* 0x2C comma: */ 0x002D, /* 0x2D minus: */ 0x002E, /* 0x2E period: */ 0x1110, /* 0x2F slash: */ 0x110F, /* 0x30 0: */ 0x11BF, /* 0x31 1: */ 0x11BE, /* 0x32 2: */ 0x11C1, /* 0x33 3: */ 0x116D, /* 0x34 4: */ 0x1172, /* 0x35 5: */ 0x1163, /* 0x36 6: */ 0x11C0, /* 0x37 7: */ 0x11BD, /* 0x38 8: */ 0x11AE, /* 0x39 9: */ 0x003A, /* 0x3A colon: */ 0x1107, /* 0x3B semicolon: */ 0x003C, /* 0x3C less: */ 0x003D, /* 0x3D equal: */ 0x003E, /* 0x3E greater: */ 0x003F, /* 0x3F question: */ 0x0040, /* 0x40 at: */ 0x0000, /* 0x41 A: */ 0x2019, /* 0x42 B: */ 0x0000, /* 0x43 C: */ 0x20A9, /* 0x44 D: */ 0x0033, /* 0x45 E: */ 0x002E, /* 0x46 F: */ 0x002F, /* 0x47 G: */ 0x0000, /* 0x48 H: */ 0x0038, /* 0x49 I: */ 0x0000, /* 0x4A J: */ 0x0000, /* 0x4B K: */ 0x003B, /* 0x4C L: */ 0x0000, /* 0x4D M: */ 0x0000, /* 0x4E N: */ 0x0039, /* 0x4F O: */ 0x0030, /* 0x50 P: */ 0x0031, /* 0x51 Q: */ 0x0034, /* 0x52 R: */ 0x203B, /* 0x53 S: */ 0x0035, /* 0x54 T: */ 0x0037, /* 0x55 U: */ 0x2018, /* 0x56 V: */ 0x0032, /* 0x57 W: */ 0x0000, /* 0x58 X: */ 0x0036, /* 0x59 Y: */ 0x0000, /* 0x5A Z: */ 0x005B, /* 0x5B bracketleft: */ 0x005C, /* 0x5C backslash: */ 0x005D, /* 0x5D bracketright: */ 0x005E, /* 0x5E asciicircum: */ 0x005F, /* 0x5F underscore: */ 0x0060, /* 0x60 quoteleft: */ 0x11BC, /* 0x61 a: */ 0x116E, /* 0x62 b: */ 0x11A8, /* 0x63 c: */ 0x1175, /* 0x64 d: */ 0x11AF, /* 0x65 e: */ 0x1161, /* 0x66 f: */ 0x1173, /* 0x67 g: */ 0x1102, /* 0x68 h: */ 0x1106, /* 0x69 i: */ 0x110B, /* 0x6A j: */ 0x1100, /* 0x6B k: */ 0x110C, /* 0x6C l: */ 0x1112, /* 0x6D m: */ 0x1109, /* 0x6E n: */ 0x110E, /* 0x6F o: */ 0x1111, /* 0x70 p: */ 0x11B8, /* 0x71 q: */ 0x1167, /* 0x72 r: */ 0x11AB, /* 0x73 s: */ 0x1165, /* 0x74 t: */ 0x1103, /* 0x75 u: */ 0x1169, /* 0x76 v: */ 0x11BA, /* 0x77 w: */ 0x11B7, /* 0x78 x: */ 0x1105, /* 0x79 y: */ 0x11C2, /* 0x7A z: */ 0x007B, /* 0x7B braceleft: */ 0x007C, /* 0x7C bar: */ 0x007D, /* 0x7D braceright: */ 0x007E /* 0x7E asciitilde: */ ]; // 3-2011 자판 K3_2011_layout = [ 0x11A9, /* 0x21 exclam: jongseong ssang_gieug */ 0x0025, /* 0x22 quotedbl: percent sign */ 0x11AC, /* 0x23 numbersign: jongseong nieun-jieuj */ 0x0024, /* 0x24 dollar */ 0x0023, /* 0x25 percent: number sign */ 0x0026, /* 0x26 ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x0028, /* 0x28 parenleft */ 0x0029, /* 0x29 parenright */ 0x007E, /* 0x2A asterisk: tilde */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x005B, /* 0x2D minus: left bracket */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuk */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x0034, /* 0x3A colon: 4 */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x003C, /* 0x3C less */ 0x005D, /* 0x3D equal: right bracket */ 0x003E, /* 0x3E greater */ 0x003F, /* 0x3F question */ 0x11B0, /* 0x40 at: jongseong lieul-gieug */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x0040, /* 0x42 B: commertial at */ 0x11BF, /* 0x43 C: jongseong kieuk */ 0x11B2, /* 0x44 D: jongseong lieul-bieub */ 0x11BD, /* 0x45 E: jongseong jieuj */ 0x11B1, /* 0x46 F: jongseong lieul-mieum */ 0x0021, /* 0x47 G: exclamation mark */ 0x0030, /* 0x48 H: 0 */ 0x0037, /* 0x49 I: 7 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0022, /* 0x4D M: quotatioin mark */ 0x0027, /* 0x4E N: apostrophe */ 0x0038, /* 0x4F O: 8 */ 0x0039, /* 0x50 P: 9 */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11B6, /* 0x52 R: jongseong lieul-hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x1164, /* 0x54 T: jungseong yae */ 0x0036, /* 0x55 U: 6 */ 0x11AA, /* 0x56 V: jongseong gieug-sieus */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x0035, /* 0x59 Y: 5 */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x00B7, /* 0x5B bracketleft: middle dot */ 0x003D, /* 0x5C backslash: equals sign */ 0x003A, /* 0x5D bracketright: colon */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x002A, /* 0x5F underscore: asterisk */ 0x003B, /* 0x60 quoteleft: semicolon */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong giueg */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1162, /* 0x74 t: jungseong ae */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x002D, /* 0x7B braceleft: minus sign */ 0x005C, /* 0x7C bar: backslash */ 0x002F, /* 0x7D braceright: slash */ 0x005F /* 0x7E asciitilde: underscore */ ]; // 3-2012 자판 K3_2012_layout = [ 0x0021, /* 0x21 exclam */ 0x002F, /* 0x22 quotedbl: slash */ 0x0023, /* 0x23 numbersign */ 0x0024, /* 0x24 dollar */ 0x0025, /* 0x25 percent */ 0x0026, /* 0x26 ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x0028, /* 0x28 parenleft */ 0x0029, /* 0x29 parenright */ 0x002A, /* 0x2A asterisk */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x002D, /* 0x2D minus */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuk */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x0034, /* 0x3A colon: 4 */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x003C, /* 0x3C less */ 0x003D, /* 0x3D equal */ 0x003E, /* 0x3E greater */ 0x003F, /* 0x3F question */ 0x0040, /* 0x40 at:commertial at */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x003B, /* 0x42 B: semicolon */ 0x11BF, /* 0x43 C: jongseong kieuk */ 0x11B0, /* 0x44 D: jongseong lieul-gieug */ 0x11BD, /* 0x45 E: jongseong jieuj */ 0x11B1, /* 0x46 F: jongseong lieul-mieum */ 0x003A, /* 0x47 G: colon */ 0x0030, /* 0x48 H: 0 */ 0x0037, /* 0x49 I: 7 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0022, /* 0x4D M: quotatioin mark */ 0x0027, /* 0x4E N: apostrophe */ 0x0038, /* 0x4F O: 8 */ 0x0039, /* 0x50 P: 9 */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11B6, /* 0x52 R: jongseong lieul-hieuh */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x1164, /* 0x54 T: jungseong yae */ 0x0036, /* 0x55 U: 6 */ 0x11A9, /* 0x56 V: jongseong ssang_gieug */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x0035, /* 0x59 Y: 5 */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x005B, /* 0x5B bracketleft */ 0x005C, /* 0x5C backslash */ 0x005D, /* 0x5D bracketright */ 0x005E, /* 0x5E asciicircum */ 0x005F, /* 0x5F underscore */ 0x0060, /* 0x60 quoteleft */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1162, /* 0x74 t: jungseong ae */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft */ 0x007C, /* 0x7C bar */ 0x007D, /* 0x7D braceright */ 0x007E /* 0x7E asciitilde */ ]; K3_14_proposal_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieut */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuh */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x003A, /* 0x3A colon: colon */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x0032, /* 0x3C less: 2 */ 0x003D, /* 0x3D equal: euals sign */ 0x0033, /* 0x3E greater: 3 */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x11AE, /* 0x41 A: jongseong dieud */ 0x002F, /* 0x42 B: slash */ 0x11B5, /* 0x43 C: jongseong lieul-pieup */ 0x1164, /* 0x44 D: jungseong yae */ 0x11BD, /* 0x45 E: jongseong jieuj */ 0x11B4, /* 0x46 F: jongseong lieul-tiuet */ 0x11B0, /* 0x47 G: jongseong lieul-gieug */ 0x0027, /* 0x48 H: apostrophe */ 0x0038, /* 0x49 I: 8 */ 0x0034, /* 0x4A J: 4 */ 0x0035, /* 0x4B K: 5 */ 0x0036, /* 0x4C L: 6 */ 0x0031, /* 0x4D M: 1 */ 0x0030, /* 0x4E N: 0 */ 0x0039, /* 0x4F O: 9 */ 0x003B, /* 0x50 P: semicolon */ 0x11C1, /* 0x51 Q: jongseong pieup */ 0x11AC, /* 0x52 R: jongseong nieun-jieuj */ 0x11AD, /* 0x53 S: jongseong nieun-hieuh */ 0x003C, /* 0x54 T: less-than sign */ 0x0037, /* 0x55 U: 7 */ 0x11BF, /* 0x56 V: jongseong kieuk */ 0x11C0, /* 0x57 W: jongseong tieut */ 0x11B9, /* 0x58 X: jongseong bieub-sieus */ 0x003E, /* 0x59 Y: greater-than sign */ 0x11BE, /* 0x5A Z: jongseong chieuch */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1162, /* 0x72 r: jungseong ae */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1165, /* 0x74 t: jungseong eo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E, /* 0x7E asciitilde: tilde */ 0x0000 /* 0x7F delete */ ]; K3_2014_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11C2,0x0000], /* 0x31 1: jongseong hieuh */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x0034,0x0000], /* 0x3A colon: 4 */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x11B9], /* 0x41 A: jongseong bieub-sieus, jongseong bieub-sieuh */ [0x003B,0x0000], /* 0x42 B: semicolon */ [0x11BF,0x11AA], /* 0x43 C: jongseong kieuk, jongseong gieug-sieus */ [0x11AE,0x11B2], /* 0x44 D: jongseong dieud, jongseong lieul-bieub */ [0x11BD,0x11AC], /* 0x45 E: jongseong jieuj, jongseong nieun-jieuj */ [0x11C0,0x11B4], /* 0x46 F: jongseong tieut, jongseong lieul-tieut */ [0x003A,0x0000], /* 0x47 G: colon */ [0x0030,0x0000], /* 0x48 H: 0 */ [0x0037,0x0000], /* 0x49 I: 7 */ [0x0031,0x0000], /* 0x4A J: 1 */ [0x0032,0x0000], /* 0x4B K: 2 */ [0x0033,0x0000], /* 0x4C L: 3 */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x0038,0x0000], /* 0x4F O: 8 */ [0x0039,0x0000], /* 0x50 P: 9 */ [0x11B6,0x11B6], /* 0x51 Q: jongseong lieul-hieuh, jongseong lieul-hieuh */ [0x11BE,0x11B3], /* 0x52 R: jongseong chieuch, jongseong lieul-sieus */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0036,0x0000], /* 0x55 U: 6 */ [0x11C1,0x11B5], /* 0x56 V: jongseong pieup, jongseong lieul-pieup */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11A9,0x11A9], /* 0x58 X: jongseong ssang_gieug, jongseong ssang_gieug */ [0x0035,0x0000], /* 0x59 Y: 5 */ [0x11B1,0x11B1], /* 0x5A Z: jongseong lieul-mieum, jongseong lieul-mieum */ [0x005B,0x005B], /* 0x5B bracketleft */ [0x005C,0x005C], /* 0x5C backslash */ [0x005D,0x005D], /* 0x5D bracketright */ [0x005E,0x005E], /* 0x5E asciicircum */ [0x005F,0x005F], /* 0x5F underscore */ [0x0060,0x0060], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1165,0x0000], /* 0x72 r: jungseong eo */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1162,0x0000], /* 0x74 t: jungseong ae */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_2015_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11AE,0x0000], /* 0x31 1: jongseong dieud */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x0034,0x0000], /* 0x3A colon: 4 */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B4,0x11B4], /* 0x41 A: jongseong lieul-tieut, jongseong lieul-tieut */ [0x003B,0x0000], /* 0x42 B: semicolon */ [0x11C0,0x11B5], /* 0x43 C: jongseong tieut, jongseong lieul-pieup */ [0x11C2,0x11B2], /* 0x44 D: jongseong hieuh, jongseong lieul-bieub */ [0x11BD,0x11AC], /* 0x45 E: jongseong jieuj, jongseong nieun-jieuj */ [0x11C1,0x11B1], /* 0x46 F: jongseong pieup, jongseong lieul-mieum */ [0x003A,0x0000], /* 0x47 G: colon */ [0x0030,0x0000], /* 0x48 H: 0 */ [0x0037,0x0000], /* 0x49 I: 7 */ [0x0031,0x0000], /* 0x4A J: 1 */ [0x0032,0x0000], /* 0x4B K: 2 */ [0x0033,0x0000], /* 0x4C L: 3 */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x0038,0x0000], /* 0x4F O: 8 */ [0x0039,0x0000], /* 0x50 P: 9 */ [0x11A9,0x11A9], /* 0x51 Q: jongseong ssang_gieug, jongseong ssang_gieug */ [0x11BE,0x11B6], /* 0x52 R: jongseong chieuch, jongseong lieul-hieuh */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0036,0x0000], /* 0x55 U: 6 */ [0x11BF,0x11AA], /* 0x56 V: jongseong kieuk, jongseong gieug-sieus */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11B9,0x11B9], /* 0x58 X: jongseong bieub-sieuh, jongseong bieub-sieuh */ [0x0035,0x0000], /* 0x59 Y: 5 */ [0x11B3,0x11B3], /* 0x5A Z: jongseong lieul-sieus, jongseong lieul-sieus */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1165,0x0000], /* 0x72 r: jungseong eo */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1162,0x0000], /* 0x74 t: jungseong ae */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_2015y_layout = [ 0x11F9, /* 0x21 exclam: jongseong yeolin_hieuh */ 0x002F, /* 0x22 quotedbl: slash */ 0x0023, /* 0x23 numbersign */ 0x0024, /* 0x24 dollar */ 0x0025, /* 0x25 percent */ 0x0026, /* 0x26 ampersand */ 0x1110, /* 0x27 apostrophe: choseong tieuh */ 0x0028, /* 0x28 parenleft */ 0x0029, /* 0x29 parenright */ 0x002A, /* 0x2A asterisk */ 0x002B, /* 0x2B plus */ 0x002C, /* 0x2C comma */ 0x002D, /* 0x2D minus */ 0x002E, /* 0x2E period */ 0x1169, /* 0x2F slash: jungseong o */ 0x110F, /* 0x30 0: choseong kieuk */ 0x11C2, /* 0x31 1: jongseong hieuh */ 0x11BB, /* 0x32 2: jongseong ssang_sieus */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1172, /* 0x35 5: jungseong yu */ 0x1163, /* 0x36 6: jungseong ya */ 0x1168, /* 0x37 7: jungseong ye */ 0x1174, /* 0x38 8: jungseong eui */ 0x116E, /* 0x39 9: jungseong u */ 0x003A, /* 0x3A colon */ 0x1107, /* 0x3B semicolon: choseong bieub */ 0x113C, /* 0x3C less: choseong ap_sieus */ 0x003D, /* 0x3D equal */ 0x113E, /* 0x3E greater: choseong dwis_sieus */ 0x003F, /* 0x3F question */ 0x0040, /* 0x40 at */ 0x11F0, /* 0x41 A: jongseong yes_ieung */ 0x0021, /* 0x42 B: exclam */ 0x11C0, /* 0x43 C: jongseong tieut */ 0x11AE, /* 0x44 D: jongseong dieud */ 0x11BD, /* 0x45 E: jongseong jieuj */ 0x11C1, /* 0x46 F: jongseong pieup */ 0X119E, /* 0x47 G: jungseong alae_a */ 0x00B7, /* 0x48 H: middle dot */ 0x1154, /* 0x49 I: choseong ap_chieuch */ 0x114C, /* 0x4A J: choseong yes_ieung */ 0x114E, /* 0x4B K: choseong ap_jieuj */ 0x1150, /* 0x4C L: choseong dwis_jieuj */ 0x1159, /* 0x4D M: choseong yeolin_hieuh */ 0x1140, /* 0x4E N: choseong yeolin_sieus */ 0x1155, /* 0x4F O: choseong dwis_chieuch */ 0x003B, /* 0x50 P: semicolon */ 0x11EB, /* 0x51 Q: jongseong yeolin_sieus */ 0x11BE, /* 0x52 R: jongseong chieuch */ 0x0000, /* 0x53 S: */ 0x1164, /* 0x54 T: jungseong yae */ 0x302E, /* 0x55 U: hangeul single dot tone mark */ 0x11BF, /* 0x56 V: jongseong kieuk */ 0x0000, /* 0x57 W: */ 0x0000, /* 0x58 X: */ 0X302F, /* 0x59 Y: hangeul double dot tone mark */ 0x0000, /* 0x5A Z: */ 0x005B, /* 0x5B bracketleft */ 0x005C, /* 0x5C backslash */ 0x005D, /* 0x5D bracketright */ 0x005E, /* 0x5E asciicircum */ 0x005F, /* 0x5F underscore */ 0x0060, /* 0x60 quoteleft */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x1167, /* 0x65 e: jungseong yeo */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x110E, /* 0x6F o: choseong chieuch */ 0x1111, /* 0x70 p: choseong pieup */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1162, /* 0x74 t: jungseong ae */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11AF, /* 0x77 w: jongseong lieul */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x0027, /* 0x7B braceleft: apostrophe */ 0x007C, /* 0x7C bar */ 0x0022, /* 0x7D braceright: quotatioin mark */ 0x007E /* 0x7E asciitilde */ ]; K3_2015M_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11AE,0x0000], /* 0x31 1: jongseong dieud */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x0034,0x0000], /* 0x3A colon: 4 */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B4,0x11B4], /* 0x41 A: jongseong lieul-tieut, jongseong lieul-tieut*/ [0x003B,0x0000], /* 0x42 B: semicolon */ [0x11C0,0x11B5], /* 0x43 C: jongseong tieut, jongseong lieul-pieup */ [0x11C2,0x11B2], /* 0x44 D: jongseong hieuh, jongseong lieul-bieub */ [0x11BD,0x11AC], /* 0x45 E: jongseong jieuj, jongseong nieun-jieuj */ [0x11C1,0x11B1], /* 0x46 F: jongseong pieup, jongseong lieul-mieum */ [0x003A,0x0000], /* 0x47 G: colon */ [0x0030,0x0000], /* 0x48 H: 0 */ [0x0037,0x0000], /* 0x49 I: 7 */ [0x0031,0x0000], /* 0x4A J: 1 */ [0x0032,0x0000], /* 0x4B K: 2 */ [0x0033,0x0000], /* 0x4C L: 3 */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x0038,0x0000], /* 0x4F O: 8 */ [0x0039,0x0000], /* 0x50 P: 9 */ [0x11A9,0x11A9], /* 0x51 Q: jongseong ssang_gieug, jongseong ssang_gieug */ [0x11BE,0x11B6], /* 0x52 R: jongseong chieuch, jongseong lieul-hieuh */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0036,0x0000], /* 0x55 U: 6 */ [0x11BF,0x11AA], /* 0x56 V: jongseong kieuk, jongseong gieug-sieus */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11B9,0x11B9], /* 0x58 X: jongseong bieub-sieuh, jongseong bieub-sieuh */ [0x0035,0x0000], /* 0x59 Y: 5 */ [0x11B3,0x11B3], /* 0x5A Z: jongseong lieul-sieus, jongseong lieul-sieus */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_2015P_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11AE,0x0000], /* 0x31 1: jongseong dieud */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x0034,0x0000], /* 0x3A colon: 4 */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x11B9], /* 0x41 A: jongseong bieub-sieuh, jongseong bieub-sieuh */ [0x003B,0x0000], /* 0x42 B: semicolon */ [0x11C0,0x11B4], /* 0x43 C: jongseong tieut, jongseong lieul-tieut */ [0x11C2,0x11B2], /* 0x44 D: jongseong hieuh, jongseong lieul-bieub */ [0x11BD,0x11AC], /* 0x45 E: jongseong jieuj, jongseong nieun-jieuj */ [0x11C1,0x11B5], /* 0x46 F: jongseong pieup, jongseong lieul-pieup */ [0x003A,0x0000], /* 0x47 G: colon */ [0x0030,0x0000], /* 0x48 H: 0 */ [0x0037,0x0000], /* 0x49 I: 7 */ [0x0031,0x0000], /* 0x4A J: 1 */ [0x0032,0x0000], /* 0x4B K: 2 */ [0x0033,0x0000], /* 0x4C L: 3 */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x0038,0x0000], /* 0x4F O: 8 */ [0x0039,0x0000], /* 0x50 P: 9 */ [0x11B6,0x11B6], /* 0x51 Q: jongseong lieul-hieuh, jongseong lieul-hieuh */ [0x11BE,0x11B3], /* 0x52 R: jongseong chieuch, jongseong lieul-sieus */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0036,0x0000], /* 0x55 U: 6 */ [0x11BF,0x11AA], /* 0x56 V: jongseong kieuk, jongseong gieug-sieus */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11A9,0x11A9], /* 0x58 X: jongseong ssang_gieug, jongseong ssang_gieug */ [0x0035,0x0000], /* 0x59 Y: 5 */ [0x11B1,0x11B1], /* 0x5A Z: jongseong lieul-mieum, jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong I */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1165,0x0000], /* 0x72 r: jungseong eo */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1162,0x0000], /* 0x74 t: jungseong ae */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 3-P3 자판 K3_P3_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x0022,0x0000], /* 0x22 quotedbl: quotatioin mark */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1173,0x1173], /* 0x38 8: jungseong eu, jungseong eu */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x0032,0x0000], /* 0x3C less: 2 */ [0x003D,0x0000], /* 0x3D equal */ [0x0033,0x0000], /* 0x3E greater: 3 */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x11B9], /* 0x41 A: jongseong bieub-sieuh, jongseong bieub-sieuh */ [0x003E,0x0000], /* 0x42 B: greater */ [0x11AE,0x11AA], /* 0x43 C: jongseong dieud, jongseong gieug-sieus */ [0x11C2,0x11B2], /* 0x44 D: jongseong hieuh, jongseong lieul-bieub */ [0x11C0,0x11B4], /* 0x45 E: jongseong tieut, jongseong lieul-tieut */ [0x11C1,0x11B5], /* 0x46 F: jongseong pieup, jongseong lieul-pieup */ [0x003C,0x0000], /* 0x47 G: less */ [0x0027,0x0000], /* 0x48 H: apostrophe */ [0x0038,0x0000], /* 0x49 I: 8 */ [0x0034,0x0000], /* 0x4A J: 4 */ [0x0035,0x0000], /* 0x4B K: 5 */ [0x0036,0x0000], /* 0x4C L: 6 */ [0x0031,0x0000], /* 0x4D M: 1 */ [0x0030,0x0000], /* 0x4E N: 0 */ [0x0039,0x0000], /* 0x4F O: 9 */ [0x003B,0x0000], /* 0x50 P: semicolon */ [0x11B6,0x11B6], /* 0x51 Q: jongseong lieul-hieuh, jongseong lieul-hieuh */ [0x11BE,0x11B3], /* 0x52 R: jongseong chieuch, jongseong lieul-sieus */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0037,0x0000], /* 0x55 U: 7 */ [0x11BD,0x11AC], /* 0x56 V: jongseong jieuj, jongseong nieun-jieuj */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11A9,0x11A9], /* 0x58 X: jongseong ssang_gieug, jongseong ssang_gieug */ [0x002F,0x0000], /* 0x59 Y: slash */ [0x11B1,0x11B1], /* 0x5A Z: jongseong lieul-mieum, jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1165,0x0000], /* 0x72 r: jungseong eo */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1162,0x0000], /* 0x74 t: jungseong ae */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 3-P2 자판 K3_P2_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x33 3: jongseong bieub */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1173,0x1173], /* 0x38 8: jungseong eu, jungseong eu */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x0034,0x0000], /* 0x3A colon: 4 */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x11B9], /* 0x41 A: jongseong bieub-sieuh, jongseong bieub-sieuh */ [0x003B,0x0000], /* 0x42 B: semicolon */ [0x11AE,0x11AA], /* 0x43 C: jongseong dieud, jongseong gieug-sieus */ [0x11C2,0x11B2], /* 0x44 D: jongseong hieuh, jongseong lieul-bieub */ [0x11C0,0x11B4], /* 0x45 E: jongseong tieut, jongseong lieul-tieut */ [0x11C1,0x11B5], /* 0x46 F: jongseong pieup, jongseong lieul-pieup */ [0x003A,0x0000], /* 0x47 G: colon */ [0x0030,0x0000], /* 0x48 H: 0 */ [0x0037,0x0000], /* 0x49 I: 7 */ [0x0031,0x0000], /* 0x4A J: 1 */ [0x0032,0x0000], /* 0x4B K: 2 */ [0x0033,0x0000], /* 0x4C L: 3 */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x0038,0x0000], /* 0x4F O: 8 */ [0x0039,0x0000], /* 0x50 P: 9 */ [0x11B6,0x11B6], /* 0x51 Q: jongseong lieul-hieuh, jongseong lieul-hieuh */ [0x11BE,0x11B3], /* 0x52 R: jongseong chieuch, jongseong lieul-sieus */ [0x11AD,0x11AD], /* 0x53 S: jongseong nieun-hieuh, jongseong nieun-hieuh */ [0x1164,0x0000], /* 0x54 T: jungseong yae */ [0x0036,0x0000], /* 0x55 U: 6 */ [0x11BD,0x11AC], /* 0x56 V: jongseong jieuj, jongseong nieun-jieuj */ [0x11B0,0x11B0], /* 0x57 W: jongseong lieul-gieug, jongseong lieul-gieug */ [0x11A9,0x11A9], /* 0x58 X: jongseong ssang_gieug, jongseong ssang_gieug */ [0x0035,0x0000], /* 0x59 Y: 5 */ [0x11B1,0x11B1], /* 0x5A Z: jongseong lieul-mieum, jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1165,0x0000], /* 0x72 r: jungseong eo */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1162,0x0000], /* 0x74 t: jungseong ae */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 3-D1 자판 - 기본 배열 K3_D1_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11C0,0x0000], /* 0x33 3: jongseong tieut */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x003A,0x0000], /* 0x3A colon: colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x0032,0x0000], /* 0x3C less: 2 */ [0x003D,0x0000], /* 0x3D equal */ [0x0033,0x0000], /* 0x3E greater: 3 */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x0000], /* 0x41 A: jongseong bieub-sieuh */ [0x003E,0x0000], /* 0x42 B: greater */ [0x11AE,0x0000], /* 0x43 C: jongseong dieud */ [0x11C2,0x0000], /* 0x44 D: jongseong hieuh */ [0x11B8,0x0000], /* 0x45 E: jongseong bieub */ [0x11BD,0x0000], /* 0x46 F: jongseong jieuj */ [0x11C1,0x0000], /* 0x47 G: jongseong pieup */ [0x0022,0x0000], /* 0x48 H: quotatioin mark */ [0x0038,0x1174], /* 0x49 I: 8, jungseong eui */ [0x0034,0x0000], /* 0x4A J: 4 */ [0x0035,0x0000], /* 0x4B K: 5 */ [0x0036,0x0000], /* 0x4C L: 6 */ [0x0031,0x0000], /* 0x4D M: 1 */ [0x0030,0x0000], /* 0x4E N: 0 */ [0x0039,0x0000], /* 0x4F O: 9 */ [0x003B,0x0000], /* 0x50 P: semicolon */ [0x11B6,0x0000], /* 0x51 Q: jongseong lieul-hieuh */ [0x1164,0x0000], /* 0x52 R: jungseong yae */ [0x11AD,0x0000], /* 0x53 S: jongseong nieun-hieuh */ [0x003C,0x0000], /* 0x54 T: less */ [0x0037,0x0000], /* 0x55 U: 7 */ [0x11BE,0x0000], /* 0x56 V: jongseong chieuch */ [0x11B0,0x0000], /* 0x57 W: jongseong lieul-gieug */ [0x11A9,0x0000], /* 0x58 X: jongseong ssang_gieug */ [0x0027,0x0000], /* 0x59 Y: apostrophe */ [0x11B1,0x0000], /* 0x5A Z: jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x00B7,0x0000], /* 0x60 quoteleft: middle dot */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x1168], /* 0x68 h: choseong nieun, jungseong ye */ [0x1106,0x1174], /* 0x69 i: choseong mieum, jungseong eui */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch */ [0x1111,0x116E], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x1168], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 3-D1 자판 - 기호 확장 배열 (캡스 록을 켜고 씀) K3_D1_capslock_layout = [ 0x0021, /* 0x21 exclam */ 0x002F, /* 0x22 quotedbl */ 0x0023, /* 0x23 numbersign */ 0x0024, /* 0x24 dollar */ 0x0025, /* 0x25 percent */ 0x0026, /* 0x26 ampersand */ 0x00AE, /* 0x27 apostrophe: registerd sign ® */ 0x0028, /* 0x28 parenleft */ 0x0029, /* 0x29 parenright */ 0x002A, /* 0x2A asterisk */ 0x002B, /* 0x2B plus */ 0x3001, /* 0x2C comma: ideographic comma 、 */ 0x002D, /* 0x2D minus */ 0x3002, /* 0x2E period: ideographic full stop 。 */ 0x00A9, /* 0x2F slash: copyright sign © */ 0x3015, /* 0x30 0: right tortoise shell bracket 〕 */ 0x203D, /* 0x31 1: interrobang ‽ */ 0xFFE1, /* 0x32 2: fullwidth pound sign £ */ 0x20AC, /* 0x33 3: euro sign € */ 0xFFE0, /* 0x34 4: fullwidth cent sign ¢ */ 0x2030, /* 0x35 5: per mille(per thousand) sign ‰ */ 0x00B6, /* 0x36 6: pilcrow sign ¶ */ 0x00A7, /* 0x37 7: section sign § */ 0x203B, /* 0x38 8: reference mark */ 0x3014, /* 0x39 9: left tortoise shell bracket 〔 */ 0x003A, /* 0x3A colon */ 0x2020, /* 0x3B semicolon: dagger † */ 0x0032, /* 0x3C less: 2 */ 0x003D, /* 0x3D equal */ 0x0033, /* 0x3E greater: 3 */ 0x003F, /* 0x3F question */ 0x0040, /* 0x40 at */ 0x2190, /* 0x41 A: leftwards arrow ← */ 0x2198, /* 0x42 B: south-east arrow ↘ */ 0x2195, /* 0x43 C: up-down arrow ↕ */ 0x2194, /* 0x44 D: ↔ left right arrow */ 0x2022, /* 0x45 E: bullet • */ 0x2196, /* 0x46 F: north-west arrow ↖ */ 0x2197, /* 0x47 G: north-east arrow ↗ */ 0x003E, /* 0x48 H: greater-than sign */ 0x0038, /* 0x49 I: 8 */ 0x0034, /* 0x4A J: 4 */ 0x0035, /* 0x4B K: 5 */ 0x0036, /* 0x4C L: 6 */ 0x0031, /* 0x4D M: 1 */ 0x0030, /* 0x4E N: 0 */ 0x0039, /* 0x4F O: 9 */ 0x003B, /* 0x50 P: semicolon */ 0x25B2, /* 0x51 Q: black up-pointing triangle */ 0x00F7, /* 0x52 R: division sign ÷ */ 0x2192, /* 0x53 S: rightwards arrow → */ 0x00B1, /* 0x54 T: plus minus sign ± */ 0x0037, /* 0x55 U: 7 */ 0x2199, /* 0x56 V: south-west arrow ↙ */ 0x25A0, /* 0x57 W: black square ■ */ 0x2191, /* 0x58 X: upwards arrow ↑ */ 0x003C, /* 0x59 Y: less-than sign */ 0x2193, /* 0x5A Z: downwards arrow ↓ */ 0x005B, /* 0x5B bracketleft */ 0xFFE6, /* 0x5C backslash */ 0x005D, /* 0x5D bracketright */ 0x005E, /* 0x5E asciicircum */ 0x005F, /* 0x5F underscore */ 0x00B7, /* 0x60 quoteleft: middle dot */ 0x300E, /* 0x61 a: left white corner bracket 『 */ 0x2265, /* 0x62 b: greater-than or equal to ≥ */ 0x300B, /* 0x63 c: right double angle bracket 》 */ 0x300A, /* 0x64 d: left double angle bracket 《 */ 0x25CB, /* 0x65 e: white circle ○ */ 0x3008, /* 0x66 f: left angle bracket 〈 */ 0x2264, /* 0x67 g: less-than or equal to ≤ */ 0x0022, /* 0x68 h: quotatioin mark */ 0x261E, /* 0x69 i: white right pointing index ☞ */ 0x2015, /* 0x6A j: horizontal bar ― */ 0x2026, /* 0x6B k: horizontal epllipsis */ 0x0060, /* 0x6C l: quoteleft */ 0x2122, /* 0x6D m: trademark ™ */ 0x3003, /* 0x6E n: ditto mark 〃 */ 0x00B0, /* 0x6F o: degree sign ° */ 0x2021, /* 0x70 p: double dagger ‡ */ 0x25B3, /* 0x71 q: up-pointing triangle △ */ 0x00D7, /* 0x72 r: multiplication X × */ 0x300C, /* 0x73 s: left corner bracket 「 */ 0x2260, /* 0x74 t: not equal to ≠ */ 0x261C, /* 0x75 u: white left pointing index ☜ */ 0x3009, /* 0x76 v: right angle bracket 〉 */ 0x25A1, /* 0x77 w: square □ */ 0x300D, /* 0x78 x: right corner bracket 」 */ 0x0027, /* 0x79 y: apostrophe */ 0x300F, /* 0x7A z: right white corner bracket 』 */ 0x007B, /* 0x7B braceleft */ 0xFFE5, /* 0x7C bar: fullwidth yen sign ¥ */ 0x007D, /* 0x7D braceright */ 0x007E /* 0x7E asciitilde */ ]; K3_D1_y_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x005B,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x005D,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11C0,0x0000], /* 0x33 3: jongseong tieut */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x113C,0x0000], /* 0x3C less: choseong ap_sieus */ [0x003D,0x0000], /* 0x3D equal */ [0x113E,0x0000], /* 0x3E greater: choseong dwis_sieus */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11F0,0x0000], /* 0x41 A: jongseong yes_ieung */ [0x0021,0x0000], /* 0x42 B: greater */ [0x11AE,0x0000], /* 0x43 C: jongseong dieud */ [0x11C2,0x0000], /* 0x44 D: jongseong hieuh */ [0x11B8,0x0000], /* 0x45 E: jongseong bieub */ [0x11BD,0x0000], /* 0x46 F: jongseong jieuj */ [0x11C1,0x0000], /* 0x47 G: jongseong pieup */ [0x2015,0x0000], /* 0x48 H: horizontal bar */ [0x1154,0x0000], /* 0x49 I: choseong ap_chieuch */ [0x114C,0x0000], /* 0x4A J: choseong yes_ieung */ [0x114E,0x0000], /* 0x4B K: choseong ap_jieuj */ [0x1150,0x0000], /* 0x4C L: choseong dwis_jieuj */ [0x1159,0x0000], /* 0x4D M: choseong yeolin_hieuh */ [0x1140,0x0000], /* 0x4E N: choseong yeolin_sieus */ [0x1155,0x0000], /* 0x4F O: choseong dwis_chieuch */ [0x2026,0x0000], /* 0x50 P: horizontal epllipsis */ [0x11EB,0x0000], /* 0x51 Q: jongseong yeolin_sieus */ [0x1164,0x0000], /* 0x52 R: jungseong yae */ [0x11F9,0x0000], /* 0x53 S: jongseong yeolin_hieuh */ [0x119E,0x0000], /* 0x54 T: jungseong alae_a */ [0x302E,0x0000], /* 0x55 U: hangeul single dot tone mark */ [0x11BE,0x0000], /* 0x56 V: jongseong chieuch */ [0x11AF,0x0000], /* 0x57 W: jongseong lieul */ [0x11A8,0x0000], /* 0x58 X: jongseong gieug */ [0x302F,0x0000], /* 0x59 Y: hangeul double dot tone mark */ [0x11B7,0x0000], /* 0x5A Z: jongseong mieum */ [0x300C,0x0000], /* 0x5B bracketleft */ [0x3002,0x0000], /* 0x5C backslash */ [0x300D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x00B7,0x0000], /* 0x60 quoteleft: middle dot */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x300E,0x0000], /* 0x7B braceleft */ [0x3001,0x0000], /* 0x7C bar */ [0x300F,0x0000], /* 0x7D braceright */ [0x203B,0x0000] /* 0x7E asciitilde */ ]; // 3-D2 자판 - 기본 배열 K3_D2_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11C0,0x0000], /* 0x33 3: jongseong tieut */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x0032,0x0000], /* 0x3C less: 2 */ [0x003D,0x0000], /* 0x3D equal */ [0x0033,0x0000], /* 0x3E greater: 3 */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11B9,0x0000], /* 0x41 A: jongseong bieub-sieuh */ [0x003E,0x0000], /* 0x42 B: greater */ [0x11AE,0x0000], /* 0x43 C: jongseong dieud */ [0x11C2,0x0000], /* 0x44 D: jongseong hieuh */ [0x11B8,0x0000], /* 0x45 E: jongseong bieub */ [0x11C1,0x0000], /* 0x46 F: jongseong pieup */ [0x11BD,0x0000], /* 0x47 G: jongseong jieuj */ [0x0022,0x0000], /* 0x48 H: quotatioin mark */ [0x0038,0x0000], /* 0x49 I: 8 */ [0x0034,0x0000], /* 0x4A J: 4 */ [0x0035,0x0000], /* 0x4B K: 5 */ [0x0036,0x0000], /* 0x4C L: 6 */ [0x0031,0x0000], /* 0x4D M: 1 */ [0x0030,0x0000], /* 0x4E N: 0 */ [0x0039,0x0000], /* 0x4F O: 9 */ [0x003B,0x0000], /* 0x50 P: semicolon */ [0x11B6,0x0000], /* 0x51 Q: jongseong lieul-hieuh */ [0x1164,0x0000], /* 0x52 R: jungseong yae */ [0x11AD,0x0000], /* 0x53 S: jongseong nieun-hieuh */ [0x11BE,0x0000], /* 0x54 T: jongseong chieuch */ [0x0037,0x0000], /* 0x55 U: 7 */ [0x003C,0x0000], /* 0x56 V: less */ [0x11B0,0x0000], /* 0x57 W: jongseong lieul-gieug */ [0x11A9,0x0000], /* 0x58 X: jongseong ssang_gieug */ [0x0027,0x0000], /* 0x59 Y: apostrophe */ [0x11B1,0x0000], /* 0x5A Z: jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x11A2], /* 0x68 h: choseong nieun, jungseong ssang_alae_a */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x119E], /* 0x79 y: choseong lieul, jungseong alae_a */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_D2_capslock_layout = [ 0x25BC, /* 0x21 exclam: */ 0x00AE, /* 0x22 quotedbl: registerd sign ® */ 0x20AC, /* 0x23 numbersign: euro sign € */ 0x00A4, /* 0x24 dollar: currency sign ¤ */ 0x2031, /* 0x25 percent: per ten thousand sign ‱ */ 0x2640, /* 0x26 ampersand: */ 0x2026, /* 0x27 apostrophe: horizontal epllipsis */ 0x300E, /* 0x28 parenleft: left white corner bracket 『 */ 0x300F, /* 0x29 parenright: right white corner bracket 』 */ 0x2116, /* 0x2A asterisk: numero sign № */ 0x002B, /* 0x2B plus */ 0x0032, /* 0x2C comma: 2 */ 0x2015, /* 0x2D minus: horizontal bar ― */ 0x0033, /* 0x2E period: 3 */ 0x002C, /* 0x2F slash: comma */ 0x300D, /* 0x30 0: right corner bracket 」 */ 0x25BD, /* 0x31 1: down-pointing triangle ▽ */ 0x25C7, /* 0x32 2: diamond ◇ */ 0xFFE1, /* 0x33 3: fullwidth pound sign £ */ 0xFFE0, /* 0x34 4: fullwidth cent sign ¢ */ 0x2030, /* 0x35 5: per mille(per thousand) sign ‰ */ 0x00B6, /* 0x36 6: pilcrow sign ¶ */ 0x00A7, /* 0x37 7: section sign § */ 0x203B, /* 0x38 8: reference mark */ 0x300C, /* 0x39 9: left corner bracket 「 */ 0x02D0, /* 0x3A colon: modifier letter triangular colon ː */ 0x002E, /* 0x3B semicolon: period */ 0x3001, /* 0x3C less: ideographic comma 、 */ 0x003D, /* 0x3D equal */ 0x3002, /* 0x3E greater: ideographic full stop 。 */ 0x00A9, /* 0x3F question: copyright sign ⓒ */ 0x25C6, /* 0x40 at: black diamond ◆ */ 0x2196, /* 0x41 A: north-west arrow ↖ */ 0x2265, /* 0x42 B: greater-than or equal to ≥ */ 0x2195, /* 0x43 C: up-down arrow ↕ */ 0x2605, /* 0x44 D: black star ★ */ 0x2022, /* 0x45 E: bullet • */ 0x300A, /* 0x46 F: left double angle bracket 《 */ 0x300B, /* 0x47 G: right double angle bracket 》 */ 0x2205, /* 0x48 H: empty set ∅ */ 0x261E, /* 0x49 I: white right pointing index ☞ */ 0x00B1, /* 0x4A J: plus minus sign ± */ 0x2225, /* 0x4B K: parallel to ∥ */ 0x221E, /* 0x4C L: infinity ∞ */ 0xFFE2, /* 0x4D M: fullwidth not sign */ 0x2122, /* 0x4E N: trademark ™ */ 0x00B0, /* 0x4F O: degree sign ° */ 0x2021, /* 0x50 P: double dagger ‡ */ 0x25B2, /* 0x51 Q: black up-pointing triangle */ 0x00F7, /* 0x52 R: division sign ÷ */ 0x2197, /* 0x53 S: north-east arrow ↗ */ 0x2252, /* 0x54 T: approximately equal to ≒ */ 0x261C, /* 0x55 U: white left pointing index ☜ */ 0x2264, /* 0x56 V: less-than or equal to ≤ */ 0x25A0, /* 0x57 W: black square ■ */ 0x2198, /* 0x58 X: south-east arrow ↘ */ 0x2312, /* 0x59 Y: [A1D2] arc ⌒ */ 0x2199, /* 0x5A Z: south-west arrow ↙ */ 0x3010, /* 0x5B bracketleft: left black lenticular bracket 【 */ 0xFFE6, /* 0x5C backslash: fullwidth won sign ₩ */ 0x3011, /* 0x5D bracketright: right black lenticular bracket 】 */ 0x2642, /* 0x5E asciicircum: male sign ♂ */ 0x002D, /* 0x5F underscore: minus */ 0x2713, /* 0x60 quoteleft: check mark ✓ */ 0x2190, /* 0x61 a: leftwards arrow ← */ 0x003E, /* 0x62 b: greater-than sign */ 0x2194, /* 0x63 c: left right arrow ↔ */ 0x2606, /* 0x64 d: white start */ 0x25CB, /* 0x65 e: white circle ○ */ 0x3008, /* 0x66 f: left angle bracket 〈 */ 0x3009, /* 0x67 g: right angle bracket 〉 */ 0x00B7, /* 0x68 h: middle dot */ 0x0038, /* 0x69 i: 8 */ 0x0034, /* 0x6A j: 4 */ 0x0035, /* 0x6B k: 5 */ 0x0036, /* 0x6C l: 6 */ 0x0031, /* 0x6D m: 1 */ 0x0030, /* 0x6E n: 0 */ 0x0039, /* 0x6F o: 9 */ 0x2020, /* 0x70 p: dagger † */ 0x25B3, /* 0x71 q: up-pointing triangle △ */ 0x00D7, /* 0x72 r: multiplication X × */ 0x2192, /* 0x73 s: rightwards arrow → */ 0x2260, /* 0x74 t: not equal to ≠ */ 0x0037, /* 0x75 u: 7 */ 0x003C, /* 0x76 v: less-than sign */ 0x25A1, /* 0x77 w: square □ */ 0x2191, /* 0x78 x: upwards arrow ↑ */ 0x3003, /* 0x79 y: ditto mark 〃 */ 0x2193, /* 0x7A z: downwards arrow ↓ */ 0x3014, /* 0x7B braceleft: left tortoise shell bracket 〔 */ 0xFFE5, /* 0x7C bar: fullwidth yen sign ¥ */ 0x3015, /* 0x7D braceright: right tortoise shell bracket 〕 */ 0x2611 /* 0x7E asciitilde: ballot with check ☑ */ ]; K3_D2_y_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x1169,0x1169], /* 0x2F slash: jungseong o, jungseong o */ [0x110F,0x0000], /* 0x30 0: choseong kieuk */ [0x11BF,0x0000], /* 0x31 1: jongseong kieuk */ [0x11BB,0x0000], /* 0x32 2: jongseong ssang_sieus */ [0x11C0,0x0000], /* 0x33 3: jongseong tieut */ [0x116D,0x0000], /* 0x34 4: jungseong yo */ [0x1172,0x0000], /* 0x35 5: jungseong yu */ [0x1163,0x0000], /* 0x36 6: jungseong ya */ [0x1168,0x0000], /* 0x37 7: jungseong ye */ [0x1174,0x0000], /* 0x38 8: jungseong eui */ [0x116E,0x116E], /* 0x39 9: jungseong u, jungseong u */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x113C,0x0000], /* 0x3C less: choseong ap_sieus */ [0x003D,0x0000], /* 0x3D equal */ [0x113E,0x0000], /* 0x3E greater: choseong dwis_sieus */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11F0,0x0000], /* 0x41 A: jongseong yes_ieung */ [0x003E,0x0000], /* 0x42 B: greater-than sign */ [0x11AE,0x0000], /* 0x43 C: jongseong dieud */ [0x11C2,0x0000], /* 0x44 D: jongseong hieuh */ [0x11B8,0x0000], /* 0x45 E: jongseong bieub */ [0x11C1,0x0000], /* 0x46 F: jongseong pieup */ [0x11BD,0x0000], /* 0x47 G: jongseong jieuj */ [0x0022,0x0000], /* 0x48 H: quotation mark */ [0x1154,0x0000], /* 0x49 I: choseong ap_chieuch */ [0x114C,0x0000], /* 0x4A J: choseong yes_ieung */ [0x114E,0x0000], /* 0x4B K: choseong ap_jieuj */ [0x1150,0x0000], /* 0x4C L: choseong dwis_jieuj */ [0x1159,0x0000], /* 0x4D M: choseong yeolin_hieuh */ [0x1140,0x0000], /* 0x4E N: choseong yeolin_sieus */ [0x1155,0x0000], /* 0x4F O: choseong dwis_chieuch */ [0x003B,0x0000], /* 0x50 P: semicolon */ [0x11EB,0x0000], /* 0x51 Q: jongseong yeolin_sieus */ [0x1164,0x0000], /* 0x52 R: jungseong yae */ [0x11F9,0x0000], /* 0x53 S: jongseong yeolin_hieuh */ [0x11BE,0x0000], /* 0x54 T: jongseong chieuch */ [0x302E,0x0000], /* 0x55 U: hangeul single dot tone mark */ [0x003C,0x0000], /* 0x56 V: less-than sign */ [0x11B0,0x0000], /* 0x57 W: jongseong lieul-gieug */ [0x11A8,0x0000], /* 0x58 X: jongseong gieug */ [0x0027,0x0000], /* 0x59 Y: apostrophe */ [0x11B1,0x0000], /* 0x5A Z: jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x0000], /* 0x6F o: choseong chieuch */ [0x1111,0x0000], /* 0x70 p: choseong pieup */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x119E,0x0000], /* 0x78 x: jungseong alae_a */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_18Na_layout = [ [0x0021,0x0000], /* 0x21 exclam: exclamation mark */ [0x0022,0x0000], /* 0x22 quotedbl: quotation mark */ [0x0023,0x0000], /* 0x23 numbersign: number sign */ [0x0024,0x0000], /* 0x24 dollar: dollar sign */ [0x0025,0x0000], /* 0x25 percent: percent sign */ [0x0026,0x0000], /* 0x26 ampersand: ampersand */ [0x0027,0x0000], /* 0x27 apostrophe: apostrophe */ [0x0028,0x0000], /* 0x28 parenleft: left parenthesis */ [0x0029,0x0000], /* 0x29 parenright: right parenthesis */ [0x002A,0x0000], /* 0x2A asterisk: asterisk */ [0x002B,0x0000], /* 0x2B plus: plus sign */ [0x002C,0x0000], /* 0x2C comma: comma */ [0x002D,0x0000], /* 0x2D minus: minus sign */ [0x002E,0x0000], /* 0x2E period: period */ [0x002F,0x0000], /* 0x2F slash: slash */ [0x0030,0x0000], /* 0x30 0: 0 */ [0x0031,0x0000], /* 0x31 1: 1 */ [0x0032,0x0000], /* 0x32 2: 2 */ [0x0033,0x0000], /* 0x33 3: 3 */ [0x0034,0x0000], /* 0x34 4: 4 */ [0x0035,0x0000], /* 0x35 5: 5 */ [0x0036,0x0000], /* 0x36 6: 6 */ [0x0037,0x0000], /* 0x37 7: 7 */ [0x0038,0x0000], /* 0x38 8: 8 */ [0x0039,0x0000], /* 0x39 9: 9 */ [0x003A,0x0000], /* 0x3A colon: colon */ [0x11BD,0x11BE], /* 0x3B semicolon: jongseong jieuj, jongseong chieuch */ [0x003C,0x0000], /* 0x3C less: less-than sign */ [0x003D,0x0000], /* 0x3D equal: equals sign */ [0x003E,0x0000], /* 0x3E greater: greater-than sign */ [0x003F,0x0000], /* 0x3F question: question mark */ [0x0040,0x0000], /* 0x40 at: commercial at */ [0x1106,0x0000], /* 0x41 A: choseong mieum */ [0x11B8,0x11C1], /* 0x42 B: jongseong bieub, jongseong pieup */ [0x110E,0x0000], /* 0x43 C: choseong chieuch */ [0x110B,0x0000], /* 0x44 D: choseong ieung */ [0x1104,0x0000], /* 0x45 E: choseong ssang_dieud */ [0x1105,0x0000], /* 0x46 F: choseong lieul */ [0x1112,0x0000], /* 0x47 G: choseong hieuh */ [0x11AB,0x11C0], /* 0x48 H: jongseong nieun, jongseong tieut */ [0x11BC,0x11C2], /* 0x49 I: jongseong ieung, jongseong hieuh */ [0x0000,0x0000], /* 0x4A J: */ [0x0000,0x0000], /* 0x4B K: */ [0x003B,0x0000], /* 0x4C L: semicolon */ [0x11B7,0x11AE], /* 0x4D M: jongseong mieum, jongseong dieud */ [0x11AF,0x11BF], /* 0x4E N: jongseong rieul, jongseong kieuk */ [0x1164,0x0000], /* 0x4F O: jungseong yae */ [0x1168,0x0000], /* 0x50 P: jungseong ye */ [0x1108,0x0000], /* 0x51 Q: choseong ssang_pieup */ [0x1101,0x0000], /* 0x52 R: choseong ssang_gieug */ [0x1102,0x0000], /* 0x53 S: choseong nieun */ [0x110A,0x0000], /* 0x54 T: choseong ssang_sios */ [0x11BA,0x0000], /* 0x55 U: jongseong sieus */ [0x1111,0x0000], /* 0x56 V: choseong pieup */ [0x110D,0x0000], /* 0x57 W: choseong ssang_jieuj */ [0x1110,0x0000], /* 0x58 X: choseong tieut */ [0x11A8,0x0000], /* 0x59 Y: jongseong gieug */ [0x110F,0x0000], /* 0x5A Z: choseong kieuk */ [0x005B,0x0000], /* 0x5B bracketleft: left bracket */ [0x005C,0x0000], /* 0x5C backslash: backslash */ [0x005D,0x0000], /* 0x5D bracketright: right bracket */ [0x005E,0x0000], /* 0x5E asciicircum: circumflex accent */ [0x005F,0x0000], /* 0x5F underscore: underscore */ [0x0060,0x0000], /* 0x60 quoteleft: grave accent */ [0x1106,0x0000], /* 0x61 a: choseong mieum */ [0x1172,0x0000], /* 0x62 b: jungseong yu */ [0x110E,0x0000], /* 0x63 c: choseong chieuch */ [0x110B,0x0000], /* 0x64 d: choseong ieung */ [0x1103,0x0000], /* 0x65 e: choseong dieud */ [0x1105,0x0000], /* 0x66 f: choseong lieul */ [0x1112,0x0000], /* 0x67 g: choseong hieuh */ [0x1169,0x11AD], /* 0x68 h: jungseong o, jongseong nieun-hieuh */ [0x1163,0x0000], /* 0x69 i: jungseong ya */ [0x1165,0x11B9], /* 0x6A j: jungseong eo, jongseong bieup-sieus */ [0x1161,0x11B0], /* 0x6B k: jungseong a, jongseong lieul-gieug */ [0x1175,0x0000], /* 0x6C l: jungseong i */ [0x1173,0x0000], /* 0x6D m: jungseong eu */ [0x116E,0x0000], /* 0x6E n: jungseong u */ [0x1162,0x0000], /* 0x6F o: jungseong ae */ [0x1166,0x0000], /* 0x70 p: jungseong e */ [0x1107,0x0000], /* 0x71 q: choseong pieup */ [0x1100,0x0000], /* 0x72 r: choseong gieug */ [0x1102,0x0000], /* 0x73 s: choseong nieun */ [0x1109,0x0000], /* 0x74 t: choseong sieus */ [0x1167,0x0000], /* 0x75 u: jungseong yeo */ [0x1111,0x0000], /* 0x76 v: choseong pieup */ [0x110c,0x0000], /* 0x77 w: choseong cieuc */ [0x1110,0x0000], /* 0x78 x: choseong tieut */ [0x116D,0x0000], /* 0x79 y: jungseong yo */ [0x110F,0x0000], /* 0x7A z: choseong kieuk */ [0x007B,0x0000], /* 0x7B braceleft: left brace */ [0x007C,0x0000], /* 0x7C bar: vertical bar */ [0x007D,0x0000], /* 0x7D braceright: right brace */ [0x007E,0x0000] /* 0x7E asciitilde: tilde */ ]; K3_Sin3_1995_layout = [ [0x0021,0x0000], /* 0x21 exclam: exclamation mark */ [0x0022,0x0000], /* 0x22 quotedbl: quotatioin mark */ [0x0023,0x0000], /* 0x23 numbersign: number sign */ [0x0024,0x0000], /* 0x24 dollar: dollar sign */ [0x0025,0x0000], /* 0x25 percent: percent sign */ [0x0026,0x0000], /* 0x26 ampersand: ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft: left parenthesis */ [0x0029,0x0000], /* 0x29 parenright: right parenthesis */ [0x002A,0x0000], /* 0x2A asterisk: asterisk */ [0x002B,0x0000], /* 0x2B plus: plus sign */ [0x002C,0x0000], /* 0x2C comma: comma */ [0x002D,0x0000], /* 0x2D minus: minus sign */ [0x002E,0x0000], /* 0x2E period: period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0: 0 */ [0x0031,0x0000], /* 0x31 1: 1 */ [0x0032,0x0000], /* 0x32 2: 2 */ [0x0033,0x0000], /* 0x33 3: 3 */ [0x0034,0x0000], /* 0x34 4: 4 */ [0x0035,0x0000], /* 0x35 5: 5 */ [0x0036,0x0000], /* 0x36 6: 6 */ [0x0037,0x0000], /* 0x37 7: 7 */ [0x0038,0x0000], /* 0x38 8: 8 */ [0x0039,0x0000], /* 0x39 9: 9 */ [0x003A,0x0000], /* 0x3A colon: colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less: less-than sign */ [0x003D,0x0000], /* 0x3D equal: equals sign */ [0x003E,0x0000], /* 0x3E greater: greater-than sign */ [0x003F,0x0000], /* 0x3F question: question mark */ [0x0040,0x0000], /* 0x40 at: commertial at */ [0x1164,0x0000], /* 0x41 A: jungseong yae */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x0000], /* 0x43 C: jungseong e */ [0x1175,0x0000], /* 0x44 D: jungseong i */ [0x1167,0x0000], /* 0x45 E: jungseong yeo */ [0x1161,0x0000], /* 0x46 F: jungseong a */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x0000,0x0000], /* 0x48 H: */ [0x116E,0x0000], /* 0x49 I: jungseong o */ [0x003B,0x0000], /* 0x4A J: semicolon */ [0x0027,0x0000], /* 0x4B K: apostrophe */ [0x0000,0x0000], /* 0x4C L: */ [0x002F,0x0000], /* 0x4D M: slash */ [0x0000,0x0000], /* 0x4E N: */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x1174,0x0000], /* 0x51 Q: jungseong eui */ [0x1162,0x0000], /* 0x52 R: jungseong ae */ [0x1168,0x0000], /* 0x53 S: jungseong ye */ [0x1165,0x0000], /* 0x54 T: jungseong eo */ [0x0000,0x0000], /* 0x55 U: */ [0x1169,0x0000], /* 0x56 V: jungseong o */ [0x1163,0x0000], /* 0x57 W: jungseong ya */ [0x116D,0x0000], /* 0x58 X: jungseong yo */ [0x0000,0x0000], /* 0x59 Y: */ [0x1172,0x0000], /* 0x5A Z: jungseong yu */ [0x005B,0x0000], /* 0x5B bracketleft: left bracket */ [0x005C,0x0000], /* 0x5C backslash: backslash */ [0x005D,0x0000], /* 0x5D bracketright: right bracket */ [0x005E,0x0000], /* 0x5E asciicircum: circumflex accent */ [0x005F,0x0000], /* 0x5F underscore: underscore */ [0x0060,0x0000], /* 0x60 quoteleft: grave accent */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x11BB,0x0000], /* 0x62 b: jongseong ssang_sieus */ [0x11BE,0x0000], /* 0x63 c: jongseong chieuch */ [0x11C2,0x0000], /* 0x64 d: jongseong hieuh */ [0x11B8,0x0000], /* 0x65 e: jongseong bieub */ [0x11BD,0x0000], /* 0x66 f: jongseong jieuj */ [0x11C1,0x0000], /* 0x67 g: jongseong pieup */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x116E], /* 0x69 i: choseong mieum, jungseong u */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x11AE,0x0000], /* 0x72 r: jongseong dieud */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x11C0,0x0000], /* 0x74 t: jongseong tieut */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11BF,0x0000], /* 0x76 v: jongseong kieuk */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft: left brace */ [0x007C,0x0000], /* 0x7C bar: vertical line(bar) */ [0x007D,0x0000], /* 0x7D braceright: right brace */ [0x007E,0x0000] /* 0x7E asciitilde: tilde */ ]; // 박경남 신세벌식 자판 K3_Sin3_BGN_layout = [ [0x203B,0x0000], /* 0x21 exclam: reference mark */ [0x00B7,0x0000], /* 0x22 quotedbl: middle dot */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x0021,0x0000], /* 0x3F question: exclamation mark */ [0x0040,0x0000], /* 0x40 at: commertial at */ [0x1172,0x0000], /* 0x41 A: jungseong yu */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x0000], /* 0x43 C: jungseong e */ [0x1175,0x0000], /* 0x44 D: jungseong i */ [0x1167,0x0000], /* 0x45 E: jungseong yeo */ [0x1161,0x0000], /* 0x46 F: jungseong a */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x0000,0x0000], /* 0x48 H: */ [0x1174,0x0000], /* 0x49 I: jungseong eui */ [0x0022,0x0000], /* 0x4A J: quotatioin mark */ [0x003B,0x0000], /* 0x4B K: semicolon */ [0x0027,0x0000], /* 0x4C L: apostrophe */ [0x002F,0x0000], /* 0x4D M: slash */ [0x0000,0x0000], /* 0x4E N: */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x1164,0x0000], /* 0x51 Q: jungseong yae */ [0x1162,0x0000], /* 0x52 R: jungseong ae */ [0x1168,0x0000], /* 0x53 S: jungseong ye */ [0x1165,0x0000], /* 0x54 T: jungseong eo */ [0x0000,0x0000], /* 0x55 U: */ [0x1169,0x0000], /* 0x56 V: jungseong o */ [0x1163,0x0000], /* 0x57 W: jungseong ya */ [0x116D,0x0000], /* 0x58 X: jungseong yo */ [0x0000,0x0000], /* 0x59 Y: */ [0x003F,0x0000], /* 0x5A Z: question mark */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x11BF,0x0000], /* 0x62 b: jongseong kieuk */ [0x11BE,0x0000], /* 0x63 c: jongseong chieuch */ [0x11AE,0x0000], /* 0x64 d: jongseong dieud */ [0x11B8,0x0000], /* 0x65 e: jongseong bieub */ [0x11BB,0x0000], /* 0x66 f: jongseong ssang_sieus */ [0x11BD,0x0000], /* 0x67 g: jongseong jieuj */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x1174], /* 0x69 i: choseong mieum, jungseong eui */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x11C0,0x0000], /* 0x72 r: jongseong tieut */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x11C1,0x0000], /* 0x74 t: jongseong pieup */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11C2,0x0000], /* 0x76 v: jongseong hieuh */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 박경남 수정 신세벌식 자판 (2003) K3_Sin3_2003_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x0022,0x0000], /* 0x22 quotedbl */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0: 0 */ [0x0031,0x0000], /* 0x31 1: 1 */ [0x0032,0x0000], /* 0x32 2: 2 */ [0x0033,0x0000], /* 0x33 3: 3 */ [0x0034,0x0000], /* 0x34 4: 4 */ [0x0035,0x0000], /* 0x35 5: 5 */ [0x0036,0x0000], /* 0x36 6: 6 */ [0x0037,0x0000], /* 0x37 7: 7 */ [0x0038,0x0000], /* 0x38 8: 8 */ [0x0039,0x0000], /* 0x39 9: 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less: */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at:commertial at */ [0x1172,0x11AC], /* 0x41 A: jungseong yu, jongseong nieun-jieuj */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x11AA], /* 0x43 C: jungseong e, jongseong gieug-sieus */ [0x1175,0x11B9], /* 0x44 D: jungseong i, jongseong bieub-sieus */ [0x1167,0x11B2], /* 0x45 E: jungseong yeo, jongseong lieul-bieub */ [0x1161,0x0000], /* 0x46 F: jungseong a */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x2018,0x0000], /* 0x48 H: left single quoatation mark */ [0x1174,0x0000], /* 0x49 I: jungseong eui */ [0x2019,0x0000], /* 0x4A J: right single quoatation mark */ [0x003B,0x0000], /* 0x4B K: semicolon */ [0x0027,0x0000], /* 0x4C L: apostrophe */ [0x002F,0x0000], /* 0x4D M: slash */ [0x00B7,0x0000], /* 0x4E N: middle dot */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x1164,0x11B3], /* 0x51 Q: jungseong yae, jongseong lieul-sieus */ [0x1162,0x11B4], /* 0x52 R: jungseong ae, jongseong lieul-tieut */ [0x1168,0x11AD], /* 0x53 S: jungseong ye, jongseong nieun-hieuh */ [0x1165,0x11B5], /* 0x54 T: jungseong eo, jongseong lieul-pieup */ [0x201D,0x0000], /* 0x55 U: right double quoatation mark */ [0x1169,0x11B6], /* 0x56 V: jungseong o, jongseong lieul-hieuh */ [0x1163,0x11B0], /* 0x57 W: jungseong ya, jongseong lieul-gieug */ [0x116D,0x11A9], /* 0x58 X: jungseong yo, jongseong ssang_gieug */ [0x201C,0x0000], /* 0x59 Y: left single quoatation mark */ [0x203B,0x11B1], /* 0x5A Z: reference mark, jongseong lieul-mieum */ [0x005B,0x119E], /* 0x5B bracketleft, jungseong alae_a */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x11AC], /* 0x61 a: jongseong ieung, jongseong nieun-jieuj */ [0x11BF,0x0000], /* 0x62 b: jongseong kieuk */ [0x11BE,0x11AA], /* 0x63 c: jongseong chieuch, jongseong gieug-sieus */ [0x11AE,0x11B9], /* 0x64 d: jongseong dieud, jongseong bieub-sieus */ [0x11B8,0x11B2], /* 0x65 e: jongseong bieub, jongseong lieul-bieub */ [0x11BB,0x0000], /* 0x66 f: jongseong ssang_sieus */ [0x11BD,0x0000], /* 0x67 g: jongseong jieuj */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x1174], /* 0x69 i: choseong mieum, jungseong eui */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x11BA,0x11B3], /* 0x71 q: jongseong sieus, jongseong lieul-sieus */ [0x11C0,0x11B4], /* 0x72 r: jongseong tieut, jongseong lieul-tieut */ [0x11AB,0x11AD], /* 0x73 s: jongseong nieun, jongseong nieun-hieuh */ [0x11C1,0x11B5], /* 0x74 t: jongseong pieup, jongseong lieul-pieup */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11C2,0x11B6], /* 0x76 v: jongseong hieuh, jongseong lieul-hieuh */ [0x11AF,0x11B0], /* 0x77 w: jongseong lieul, jongseong lieul-gieug */ [0x11A8,0x11A9], /* 0x78 x: jongseong gieug, jongseong ssang_gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x11B1], /* 0x7A z: jongseong mieum, jongseong lieul-mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_Sin3_2012_layout = [ [0x0021,0x0000], /* 0x21 exclamation */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 number */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand: ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 left parenthesis */ [0x0029,0x0000], /* 0x29 right parenthesis */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less-than */ [0x003D,0x0000], /* 0x3D equals */ [0x003E,0x0000], /* 0x3E greater-than */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 commertial at */ [0x1172,0x11B9], /* 0x41 A: jungseong yu, jongseong bieub-sieus */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x11B6], /* 0x43 C: jungseong e,jongseong lieul-hieuh */ [0x1175,0x11AC], /* 0x44 D: jungseong i, jongseong nieun-jieuj */ [0x1167,0x11B2], /* 0x45 E: jungseong yeo, jongseong lieul-bieub */ [0x1161,0x11AA], /* 0x46 F: jungseong a, jongseong gieug-sieus */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x25A1,0x0000], /* 0x48 H: white squre */ [0x1174,0x0000], /* 0x49 I: jungseong eui */ [0x2015,0x0000], /* 0x4A J: horizontal bar */ [0x00B7,0x0000], /* 0x4B K: middle dot */ [0x003B,0x0000], /* 0x4C L: semicolon */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x1164,0x11B3], /* 0x51 Q: jungseong yae, jongseong lieul-sieus */ [0x1165,0x11B4], /* 0x52 R: jungseong eo, jongseong lieul-tieut */ [0x1168,0x11AD], /* 0x53 S: jungseong ye, jongseong nieun-hieuh */ [0x1162,0x0000], /* 0x54 T: jungseong ae */ [0x25CB,0x0000], /* 0x55 U: white circle */ [0x1169,0x11B5], /* 0x56 V: jungseong o, jongseong lieul-pieup */ [0x1163,0x11B0], /* 0x57 W: jungseong ya, jongseong lieul-gieug */ [0x116D,0x11A9], /* 0x58 X: jungseong yo, jongseong ssang_gieug */ [0x00D7,0x0000], /* 0x59 Y: multiplication*/ [0x119E,0x11B1], /* 0x5A Z: jungseong alae_a, jongseong lieul-mieum*/ [0x005B,0x119E], /* 0x5B left bracket, jungseong alae_a */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D right bracket */ [0x005E,0x0000], /* 0x5E circumflex accent */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft: grave accent */ [0x11BC,0x11B9], /* 0x61 a: jongseong ieung, jongseong bieub-sieus */ [0x11BF,0x0000], /* 0x62 b: jongseong kieuk */ [0x11C2,0x11B6], /* 0x63 c: jongseong hieuh, jongseong lieul-hieuh */ [0x11BB,0x11AC], /* 0x64 d: jongseong ssang_sieus, jongseong nieun-jieuj */ [0x11B8,0x11B2], /* 0x65 e: jongseong bieub, jongseong lieul-bieub */ [0x11BE,0x11AA], /* 0x66 f: jongseong chieuch, jongseong gieug-sieus */ [0x11BD,0x0000], /* 0x67 g: jongseong jieuj */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x1174], /* 0x69 i: choseong mieum, jungseong eui */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x11BA,0x11B3], /* 0x71 q: jongseong sios, jongseong lieul-sieus */ [0x11C0,0x11B4], /* 0x72 r: jongseong tieut, jongseong lieul-tieut */ [0x11AB,0x11AD], /* 0x73 s: jongseong nieun, jongseong nieun-hieuh */ [0x11AE,0x0000], /* 0x74 t: jongseong dieud */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11C1,0x11B5], /* 0x76 v: jongseong pieup, jongseong lieul-pieup */ [0x11AF,0x11B0], /* 0x77 w: jongseong lieul, jongseong lieul-gieug */ [0x11A8,0x11A9], /* 0x78 x: jongseong gieug, jongseong ssang_gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x11B1], /* 0x7A z: jongseong mieum, jongseong lieul-mieum */ [0x007B,0x0000], /* 0x7B left brace */ [0x007C,0x0000], /* 0x7C vertical line(bar) */ [0x007D,0x0000], /* 0x7D right brace */ [0x007E,0x0000] /* 0x7E tilde */ ]; // 신세벌식 2015 자판 K3_Sin3_2015_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11BB,0x0000], /* 0x41 A: jongseong ssang_sieus */ [0x11BF,0x0000], /* 0x42 B: jongseong kieuk */ [0x11BA,0x0000], /* 0x43 C: jongseong sieus */ [0x11AF,0x0000], /* 0x44 D: jongseong lieul */ [0x11BC,0x0000], /* 0x45 E: jongseong ieung */ [0x11C0,0x0000], /* 0x46 F: jongseong tieut */ [0x11AE,0x0000], /* 0x47 G: jongseong dieud */ [0x3008,0x0000], /* 0x48 H: left angle bracket 〈 */ [0x2026,0x0000], /* 0x49 I: horizontal ellipsis … */ [0x3009,0x0000], /* 0x4A J: right angle bracket 〉 */ [0x00B7,0x0000], /* 0x4B K: middle dot */ [0x003B,0x0000], /* 0x4C L: semicolon */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x11C2,0x0000], /* 0x51 Q: jongseong hieuh */ [0x11BD,0x0000], /* 0x52 R: jongseong jieuj */ [0x11AB,0x0000], /* 0x53 S: jongseong nieun */ [0x11C1,0x0000], /* 0x54 T: jongseong pieup */ [0x300B,0x0000], /* 0x55 U: right double angle bracket 》 */ [0x11BE,0x0000], /* 0x56 V: jongseong chieuch */ [0x11B7,0x0000], /* 0x57 W: jongseong mieum */ [0x11A8,0x0000], /* 0x58 X: jongseong gieug */ [0x300A,0x0000], /* 0x59 Y: left double angle bracket 《 */ [0x11B8,0x0000], /* 0x5A Z: jongseong bieub */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x1163,0x0000], /* 0x61 a: jungseong ya */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x1164,0x0000], /* 0x71 q: jungseong yae */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x1174,0x0000], /* 0x73 s: jungseong eui */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x1168,0x0000], /* 0x77 w: jungseong ye */ [0x116D,0x0000], /* 0x78 x: jungseong yo */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x1172,0x0000], /* 0x7A z: jungseong yu */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 신세벌식 M 자판 (up↔down) K3_Sin3_M_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x11BC,0x0000], /* 0x41 A: jongseong ieung */ [0x11BE,0x0000], /* 0x42 B: jongseong chieuch */ [0x11C2,0x0000], /* 0x43 C: jongseong hieuh */ [0x11BB,0x0000], /* 0x44 D: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x45 E: jongseong bieub */ [0x11C0,0x0000], /* 0x46 F: jongseong tieut */ [0x11AE,0x0000], /* 0x47 G: jongseong dieud */ [0x300A,0x0000], /* 0x48 H: left double angle bracket 《 */ [0x203B,0x0000], /* 0x49 I: reference mark */ [0x300B,0x0000], /* 0x4A J: right double angle bracket 》 */ [0x00B7,0x0000], /* 0x4B K: middle dot */ [0x003B,0x0000], /* 0x4C L: semicolon */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x0027,0x0000], /* 0x4E N: apostrophe */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x11BA,0x0000], /* 0x51 Q: jongseong sieus */ [0x11BD,0x0000], /* 0x52 R: jongseong jieuj */ [0x11AB,0x0000], /* 0x53 S: jongseong nieun */ [0x11BF,0x0000], /* 0x54 T: jongseong kieuk */ [0x201D,0x0000], /* 0x55 U: right double quotation mark ” */ [0x11C1,0x0000], /* 0x56 V: jongseong pieup */ [0x11AF,0x0000], /* 0x57 W: jongseong lieul */ [0x11A8,0x0000], /* 0x58 X: jongseong gieug */ [0x201C,0x0000], /* 0x59 Y: left double quotation mark “ */ [0x11B7,0x0000], /* 0x5A Z: jongseong mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x1172,0x0000], /* 0x61 a: jungseong yu */ [0x116E,0x0000], /* 0x62 b: jungseong u */ [0x1166,0x0000], /* 0x63 c: jungseong e */ [0x1175,0x0000], /* 0x64 d: jungseong i */ [0x1167,0x0000], /* 0x65 e: jungseong yeo */ [0x1161,0x0000], /* 0x66 f: jungseong a */ [0x1173,0x0000], /* 0x67 g: jungseong eu */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x1164,0x0000], /* 0x71 q: jungseong yae */ [0x1162,0x0000], /* 0x72 r: jungseong ae */ [0x1174,0x0000], /* 0x73 s: jungseong eui */ [0x1165,0x0000], /* 0x74 t: jungseong eo */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x1169,0x0000], /* 0x76 v: jungseong o */ [0x1163,0x0000], /* 0x77 w: jungseong ya */ [0x116D,0x0000], /* 0x78 x: jungseong yo */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x1168,0x0000], /* 0x7A z: jungseong ye */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 신세벌식 P K3_Sin3_P_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x002F,0x0000], /* 0x22 quotedbl: slash */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x0000], /* 0x2E period */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x1172,0x11AA], /* 0x41 A: jungseong yu, jongseong gieug-sieus */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x11A9], /* 0x43 C: jungseong e, jongseong ssang_gieug */ [0x1175,0x11B6], /* 0x44 D: jungseong i, jongseong lieul-hieuh */ [0x1167,0x11B2], /* 0x45 E: jungseong yeo, jongseong lieul-bieub */ [0x1161,0x11B5], /* 0x46 F: jungseong a, jongseong lieul-pieup */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x25A1,0x0000], /* 0x48 H: white squre */ [0x1173,0x0000], /* 0x49 I: jungseong eu */ [0x0027,0x0000], /* 0x4A J: apostrophe */ [0x00B7,0x0000], /* 0x4B K: middle dot */ [0x003B,0x0000], /* 0x4C L: semicolon */ [0x0022,0x0000], /* 0x4D M: quotatioin mark */ [0x2015,0x0000], /* 0x4E N: horizontal bar */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x119E,0x0000], /* 0x50 P: jungseong alae_a */ [0x1164,0x11B3], /* 0x51 Q: jungseong yae, jongseong lieul-sieus */ [0x1165,0x11B4], /* 0x52 R: jungseong eo, jongseong lieul-tieut */ [0x1168,0x11AD], /* 0x53 S: jungseong ye, jongseong nieun-hieuh */ [0x1162,0x0000], /* 0x54 T: jungseong ae */ [0x25CB,0x0000], /* 0x55 U: white circle */ [0x1169,0x11AC], /* 0x56 V: jungseong o, jongseong nieun-jieuj */ [0x1163,0x11B0], /* 0x57 W: jungseong ya, jongseong lieul-gieug */ [0x116D,0x11B9], /* 0x58 X: jungseong yo, jongseong bieub-sieus */ [0x00D7,0x0000], /* 0x59 Y: multiplication */ [0x119E,0x11B1], /* 0x5A Z: jungseong alae_a, jongseong lieul-mieum */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x11BF,0x0000], /* 0x62 b: jongseong kieuk */ [0x11A8,0x0000], /* 0x63 c: jongseong gieug */ [0x11C2,0x0000], /* 0x64 d: jongseong hieuh */ [0x11B8,0x0000], /* 0x65 e: jongseong bieub */ [0x11C1,0x0000], /* 0x66 f: jongseong pieup */ [0x11AE,0x0000], /* 0x67 g: jongseong dieud */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x1173], /* 0x69 i: choseong mieum, jungseong eu */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x119E], /* 0x70 p: choseong pieup, jungseong alae_a */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x11C0,0x0000], /* 0x72 r: jongseong tieut */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x11BE,0x0000], /* 0x74 t: jongseong chieuch */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11BD,0x0000], /* 0x76 v: jongseong jieuj */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11BB,0x0000], /* 0x78 x: jongseong ssang_sieus */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 신세벌식 P 옛한글 K3_Sin3_P_y_layout = []; for(i=0;i<K3_Sin3_P_layout.length;++i) K3_Sin3_P_y_layout.push([K3_Sin3_P_layout[i][0],K3_Sin3_P_layout[i][1]]); K3_Sin3_P_y_layout[52]=0x302E; /* 0x55 U: hangeul single dot tone mark */ K3_Sin3_P_y_layout[56]=0x302F; /* 0x59 Y: hangeul double dot tone mark */ K3_Sin3_P_y_capslock_layout = []; for(i=0;i<K3_Sin3_P_y_layout.length;++i) K3_Sin3_P_y_capslock_layout.push([K3_Sin3_P_y_layout[i][0],0]); K3_Sin3_P_y_capslock_layout[14]=[0x110F,0x0000]; /* 0x2F slash: choseong kieuk */ K3_Sin3_P_y_capslock_layout[40]=[0x0000,0x0000]; /* 0x49 I */ K3_Sin3_P_y_capslock_layout[46]=[0x0000,0x0000]; /* 0x4F O */ K3_Sin3_P_y_capslock_layout[47]=[0x0000,0x0000]; /* 0x50 P */ // 신세벌식 공동개발안 K3_Sin3_Gongdong_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x0022,0x0000], /* 0x22 quotedbl */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x1110,0x0000], /* 0x27 apostrophe: choseong tieuh */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x116F], /* 0x2C comma, jungseong weo */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x116A], /* 0x2E period, jungseong wa */ [0x110F,0x1169], /* 0x2F slash: choseong kieuk, jungseong o */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x1171], /* 0x32 2, jungseong wi*/ [0x0033,0x116C], /* 0x33 3, jungseong oe*/ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x116B], /* 0x38 8, jungseong wae */ [0x0039,0x1170], /* 0x39 9, jungseong we */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x1172,0x0000], /* 0x41 A: jungseong yu */ [0x116E,0x0000], /* 0x42 B: jungseong u */ [0x1166,0x0000], /* 0x43 C: jungseong e */ [0x1175,0x0000], /* 0x44 D: jungseong i */ [0x1167,0x0000], /* 0x45 E: jungseong yeo */ [0x1161,0x0000], /* 0x46 F: jungseong a */ [0x1173,0x0000], /* 0x47 G: jungseong eu */ [0x300E,0x0000], /* 0x48 H: left white corner bracket */ [0x1174,0x0000], /* 0x49 I: jungseong eui */ [0x300F,0x0000], /* 0x4A J: right white corner bracket */ [0x003B,0x0000], /* 0x4B K: semicolon */ [0x0027,0x0000], /* 0x4C L: apostrophe */ [0x002F,0x0000], /* 0x4D M: slash */ [0x00B7,0x0000], /* 0x4E N: middle dot */ [0x116E,0x0000], /* 0x4F O: jungseong u */ [0x1169,0x0000], /* 0x50 P: jungseong o */ [0x203B,0x0000], /* 0x51 Q: reference mark */ [0x1162,0x0000], /* 0x52 R: jungseong ae */ [0x116D,0x0000], /* 0x53 S: jungseong yo */ [0x1165,0x0000], /* 0x54 T: jungseong eo */ [0x300B,0x0000], /* 0x55 U: right double angle bracket */ [0x1169,0x0000], /* 0x56 V: jungseong o */ [0x1168,0x0000], /* 0x57 W: jungseong ye */ [0x1163,0x0000], /* 0x58 X: jungseong ya */ [0x300A,0x0000], /* 0x59 Y: left single angle bracket */ [0x1164,0x0000], /* 0x5A Z: jungseong yae */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung */ [0x11BE,0x0000], /* 0x62 b: jongseong chieuch */ [0x11C2,0x0000], /* 0x63 c: jongseong hieuh */ [0x11BB,0x0000], /* 0x64 d: jongseong ssang_sieus */ [0x11B8,0x0000], /* 0x65 e: jongseong bieub */ [0x11C0,0x0000], /* 0x66 f: jongseong tieut */ [0x11AE,0x0000], /* 0x67 g: jongseong dieud */ [0x1102,0x0000], /* 0x68 h: choseong nieun */ [0x1106,0x1174], /* 0x69 i: choseong mieum, jungseong eui */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x110C,0x0000], /* 0x6C l: choseong jieuj */ [0x1112,0x0000], /* 0x6D m: choseong hieuh */ [0x1109,0x0000], /* 0x6E n: choseong sieus */ [0x110E,0x116E], /* 0x6F o: choseong chieuch, jungseong u */ [0x1111,0x1169], /* 0x70 p: choseong pieup, jungseong o */ [0x11BA,0x0000], /* 0x71 q: jongseong sieus */ [0x11BD,0x0000], /* 0x72 r: jongseong jieuj */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun */ [0x11BF,0x0000], /* 0x74 t: jongseong kieuk */ [0x1103,0x0000], /* 0x75 u: choseong dieud */ [0x11C1,0x0000], /* 0x76 v: jongseong pieup */ [0x11AF,0x0000], /* 0x77 w: jongseong lieul */ [0x11A8,0x0000], /* 0x78 x: jongseong gieug */ [0x1105,0x0000], /* 0x79 y: choseong lieul */ [0x11B7,0x0000], /* 0x7A z: jongseong mieum */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; K3_Sin3_Cham_layout = [ [0x0021,0x0000], /* 0x21 exclam */ [0x0022,0x0000], /* 0x22 quotedbl */ [0x0023,0x0000], /* 0x23 numbersign */ [0x0024,0x0000], /* 0x24 dollar */ [0x0025,0x0000], /* 0x25 percent */ [0x0026,0x0000], /* 0x26 ampersand */ [0x0027,0x0000], /* 0x27 apostrophe: apostrophe */ [0x0028,0x0000], /* 0x28 parenleft */ [0x0029,0x0000], /* 0x29 parenright */ [0x002A,0x0000], /* 0x2A asterisk */ [0x002B,0x0000], /* 0x2B plus */ [0x002C,0x0000], /* 0x2C comma */ [0x002D,0x0000], /* 0x2D minus */ [0x002E,0x116E], /* 0x2E period, jungseong u */ [0x1111,0x0000], /* 0x2F slash: choseong pieup */ [0x0030,0x0000], /* 0x30 0 */ [0x0031,0x0000], /* 0x31 1 */ [0x0032,0x0000], /* 0x32 2 */ [0x0033,0x0000], /* 0x33 3 */ [0x0034,0x0000], /* 0x34 4 */ [0x0035,0x0000], /* 0x35 5 */ [0x0036,0x0000], /* 0x36 6 */ [0x0037,0x0000], /* 0x37 7 */ [0x0038,0x0000], /* 0x38 8 */ [0x0039,0x0000], /* 0x39 9 */ [0x003A,0x0000], /* 0x3A colon */ [0x1107,0x0000], /* 0x3B semicolon: choseong bieub */ [0x003C,0x0000], /* 0x3C less */ [0x003D,0x0000], /* 0x3D equal */ [0x003E,0x0000], /* 0x3E greater */ [0x003F,0x0000], /* 0x3F question */ [0x0040,0x0000], /* 0x40 at */ [0x2018,0x0000], /* 0x41 A: left single quotation mark ‘ */ [0x25B3,0x11BF], /* 0x42 B: white up-pointing triangle △, jongseong kieuk */ [0x300A,0x0000], /* 0x43 C: left double angle bracket 《 */ [0x201C,0x0000], /* 0x44 D: left double quotation mark “ */ [0x300E,0x0000], /* 0x45 E: left white corner bracket 『 */ [0x201D,0x0000], /* 0x46 F: right double quotation mark ” */ [0x25CB,0x0000], /* 0x47 G: white circle */ [0x2A09,0x0000], /* 0x48 H: n-ary times operator */ [0x2192,0x0000], /* 0x49 I: rightwards arrow → */ [0x00B7,0x0000], /* 0x4A J: middle dot */ [0x2026,0x0000], /* 0x4B K: semicolon: horizontal epllipsis */ [0x002F,0x0000], /* 0x4C L: slash */ [0x119E,0x0000], /* 0x4D M: jungseong alae_a */ [0x001B,0x0000], /* 0x4E N: escape */ [0x2014,0x0000], /* 0x4F O: em dash — */ [0x003B,0x0000], /* 0x50 P: semicolon */ [0x300C,0x0000], /* 0x51 Q: left corner bracket 「 */ [0x300F,0x0000], /* 0x52 R: right white corner bracket 』 */ [0x2019,0x0000], /* 0x53 S: right single quotation mark ’ */ [0x25A1,0x0000], /* 0x54 T: square □ */ [0x2190,0x0000], /* 0x55 U: leftwards arrow ← */ [0x300B,0x0000], /* 0x56 V: right double angle bracket 》 */ [0x300D,0x0000], /* 0x57 W: reference mark */ [0x3009,0x0000], /* 0x58 X: right angle bracket 〉 */ [0x203B,0x0000], /* 0x59 Y: reference mark ※ */ [0x3008,0x0000], /* 0x5A Z: left angle bracket 〈 */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x005E,0x0000], /* 0x5E asciicircum */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x116D,0x11B7], /* 0x61 a: jungseong yo, jongseong mieum */ [0x1163,0x110F], /* 0x62 b: jungseong ya, choseong kieuk */ [0x1173,0x11C2], /* 0x63 c: jungseong eu, jongseong hieuh */ [0x1175,0x11BC], /* 0x64 d: jungseong i, jongseong ieung */ [0x1166,0x11A8], /* 0x65 e: jungseong e, jongseong gieug */ [0x1161,0x11AE], /* 0x66 f: jungseong a, jongseong dieud */ [0x1169,0x11C1], /* 0x67 g: jungseong o, jongseong pieup */ [0x1112,0x0000], /* 0x68 h: choseong dieud */ [0x1103,0x0000], /* 0x69 i: choseong mieum */ [0x110B,0x0000], /* 0x6A j: choseong ieung */ [0x1100,0x0000], /* 0x6B k: choseong gieug */ [0x1109,0x0000], /* 0x6C l: choseong sieus */ [0x1102,0x0000], /* 0x6D m: choseong nieun */ [0x110C,0x0000], /* 0x6E n: choseong jieuj */ [0x1105,0x1169], /* 0x6F o: choseong lieul, jungseong o */ [0x1110,0x0000], /* 0x70 p: choseong tieut */ [0x1172,0x11BB], /* 0x71 q: jungseong yu, jongseong ssang_sieus */ [0x116E,0x11C0], /* 0x72 r: jungseong u, jongseong tieut */ [0x1162,0x11AB], /* 0x73 s: jungseong ae, jongseong nieun */ [0x1167,0x11BE], /* 0x74 t: jungseong yeo, jongseong chieuch */ [0x1106,0x0000], /* 0x75 u: choseong hieuh */ [0x1165,0x11BD], /* 0x76 v: jungseong eo, jongseong jieuj */ [0x1174,0x11AF], /* 0x77 w: jungseong eui, jongseong lieul */ [0x1164,0x11BA], /* 0x78 x: jungseong yae, jongseong sieus */ [0x110E,0x0000], /* 0x79 y: choseong chieuch */ [0x1168,0x11B8], /* 0x7A z: jungseong ye, jongseong bieub */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 이건구 한 손 세벌식 오른손 K3_LGG_OH_r = [ [0x2026,0x0000], /* 0x21 exclam: horizontal ellipsis …*/ [0x0022,0x0000], /* 0x22 quotedbl: quotation mark " */ [0x25CB,0x0000], /* 0x23 numbersign: white circle ○ */ [0x25A1,0x0000], /* 0x24 dollar: square □ */ [0x1163,0x0000], /* 0x25 percent: jungseong ya ㅑ */ [0x1168,0x0000], /* 0x26 ampersand: jungseong ye ㅖ */ [0x0027,0x0000], /* 0x27 apostrophe */ [0x002A,0x0000], /* 0x28 parenleft: asterisk * */ [0xFFE6,0x0000], /* 0x29 parenright: full-width won sign ₩ */ [0x0026,0x0000], /* 0x2A asterisk: ampersand & */ [0x002B,0x0000], /* 0x2B plus */ [0x0030,0x0000], /* 0x2C comma: 0 */ [0x002D,0x0000], /* 0x2D minus */ [0x003B,0x0000], /* 0x2E period: semicolon ; */ [0x002F,0x0000], /* 0x2F slash */ [0x0039,0x0000], /* 0x30 0: 9 */ [0x2015,0x0000], /* 0x31 1: horizontal bar ― */ [0x203B,0x0000], /* 0x32 2: reference mark ※ */ [0x0028,0x0000], /* 0x33 3: left parenthesis ( */ [0x0029,0x0000], /* 0x34 4: right parenthesis ) */ [0x1163,0x0000], /* 0x35 5: jungseong ya ㅑ */ [0x11BA,0x0000], /* 0x36 6: jongseong sios _ㅅ */ [0x11B8,0x0000], /* 0x37 7: jongseong bieub _ㅂ */ [0x0037,0x0000], /* 0x38 8: 7 */ [0x0038,0x0000], /* 0x39 9: 8 */ [0x0023,0x0000], /* 0x3A colon: number sign # */ [0x0033,0x0000], /* 0x3B semicolon: 3 */ [0x00B7,0x0000], /* 0x3C less: middle dot · */ [0x003D,0x0000], /* 0x3D equal */ [0x003A,0x0000], /* 0x3E greater: colon : */ [0x003F,0x0000], /* 0x3F question */ [0x00D7,0x0000], /* 0x40 at: multiplication sign × */ [0x300E,0x0000], /* 0x41 A: left white corner bracket『 */ [0x1169,0x0000], /* 0x42 B: jungseong o ㅗ */ [0x1172,0x0000], /* 0x43 C: jungseong yu ㅠ */ [0x1100,0x0000], /* 0x44 D: choseong gieug ㄱ */ [0x116E,0x0000], /* 0x45 E: jungseong u (index finger) ㅜ */ [0x110B,0x0000], /* 0x46 F: choseong ieung ㅇ */ [0x1161,0x0000], /* 0x47 G: jungseong a ㅏ */ [0x1175,0x0000], /* 0x48 H: jungseong i ㅣ */ [0x0024,0x0000], /* 0x49 I: dollar sign $ */ [0x1173,0x0000], /* 0x4A J: jungseong eu ㅡ */ [0x0021,0x0000], /* 0x4B K: exclamation mark ! */ [0x0040,0x0000], /* 0x4C L: at sign @ */ [0x116E,0x0000], /* 0x4D M: jungseong u ㅜ */ [0x1162,0x0000], /* 0x4E N: jungseong ae ㅐ */ [0x0025,0x0000], /* 0x4F O: percent sign % */ [0x005E,0x0000], /* 0x50 P: circumflex accent mark ^ */ [0x300A,0x0000], /* 0x51 Q: left double angle bracket 《 */ [0x1169,0x0000], /* 0x52 R: jungseong o (index finger) ㅗ */ [0x003C,0x0000], /* 0x53 S: less-than sign < */ [0x1165,0x0000], /* 0x54 T: jungseong eo ㅓ */ [0x1166,0x0000], /* 0x55 U: jungseong e ㅔ */ [0x116D,0x0000], /* 0x56 V: jungseong yo ㅛ */ [0x300B,0x0000], /* 0x57 W: right double angle bracket 》 */ [0x003E,0x0000], /* 0x58 X: greater-than sign > */ [0x1167,0x0000], /* 0x59 Y: jungseong yeo ㅕ */ [0x300F,0x0000], /* 0x5A Z: right white corner bracket 』 */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x1174,0x0000], /* 0x5E asciicircum: jungseong eui ㅢ */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft: grave accent mark ` */ [0x300C,0x0000], /* 0x61 a: left corner bracket 「 */ [0x1106,0x0000], /* 0x62 b: choseong mieum ㅁ */ [0x1102,0x0000], /* 0x63 c: choseong nieun ㄴ */ [0x1100,0x0000], /* 0x64 d: choseong gieug ㄱ */ [0x110C,0x116E], /* 0x65 e: choseong jieuj ㅈ, jungseong u (index finger) ㅜ */ [0x110B,0x0000], /* 0x66 f: choseong ieung ㅇ */ [0x1107,0x0000], /* 0x67 g: choseong bieub ㅂ */ [0x11AB,0x0000], /* 0x68 h: jongseong nieun _ㄴ */ [0x0034,0x0000], /* 0x69 i: 4 */ [0x11BC,0x0000], /* 0x6A j: jongseong ieung _ㅇ */ [0x0031,0x0000], /* 0x6B k: 1 */ [0x0032,0x0000], /* 0x6C l: 2 */ [0x11BB,0x0000], /* 0x6D m: jongseong ssang-sios _ㅆ */ [0x11B7,0x0000], /* 0x6E n: jongseong mieum _ㅁ */ [0x0035,0x0000], /* 0x6F o: 5 */ [0x0036,0x0000], /* 0x70 p: 6 */ [0x3008,0x0000], /* 0x71 q: left angle bracket 〈 */ [0x1109,0x1169], /* 0x72 r: choseong sios ㅅ, jungseong o (index finger) ㅗ */ [0x002C,0x0000], /* 0x73 s: comma */ [0x1105,0x0000], /* 0x74 t: choseong rieul ㄹ */ [0x11A8,0x0000], /* 0x75 u: jongseong gieug _ㄱ */ [0x1103,0x0000], /* 0x76 v: choseong digeud ㄷ */ [0x3009,0x0000], /* 0x77 w: right angle bracket 〉 */ [0x002E,0x0000], /* 0x78 x: period . */ [0x11AF,0x0000], /* 0x79 y: jongseong rieul _ㄹ */ [0x300D,0x0000], /* 0x7A z: right corner bracket 」 */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 이건구 한 손 세벌식 오른손의 캡스락 배열 K3_LGG_OH_r_capslock_layout = [ [0x2015,0x0000], /* 0x21 exclam: horizontal bar ― */ [0x0027,0x0000], /* 0x22 quotedbl: apostrophe ' */ [0x0028,0x0000], /* 0x23 numbersign: left parenthesis ( */ [0x0029,0x0000], /* 0x24 dollar: right parenthesis ) */ [0x1163,0x0000], /* 0x25 percent: jungseong ya ㅑ */ [0x11B8,0x0000], /* 0x26 ampersand: jongseong bieub _ㅂ */ [0x0022,0x0000], /* 0x27 apostrophe: quotation mark " */ [0x0038,0x0000], /* 0x28 parenleft: 8 */ [0x0039,0x0000], /* 0x29 parenright: 9 */ [0x0037,0x0000], /* 0x2A asterisk: 7 */ [0x003D,0x0000], /* 0x2B plus: equal sign = */ [0x00B7,0x0000], /* 0x2C comma: middle dot · */ [0x005F,0x0000], /* 0x2D minus: underscore sign _ */ [0x003A,0x0000], /* 0x2E period: colon : */ [0x003F,0x0000], /* 0x2F slash: question mark ? */ [0xFFE6,0x0000], /* 0x30 0: full-width won sign */ [0x2026,0x0000], /* 0x31 1: horizontal ellipsis … */ [0x00D7,0x0000], /* 0x32 2: multiplication sign × */ [0x25CB,0x0000], /* 0x33 3: white circle ○ */ [0x25A1,0x0000], /* 0x34 4: square □ */ [0x1163,0x0000], /* 0x35 5: jungseong ya ㅑ */ [0x1174,0x0000], /* 0x36 6: jungseong eui ㅢ */ [0x1168,0x0000], /* 0x37 7: jungseong ye ㅖ */ [0x0026,0x0000], /* 0x38 8: ampersand & */ [0x002A,0x0000], /* 0x39 9: asterisk * */ [0x0033,0x0000], /* 0x3A colon: 3 */ [0x0023,0x0000], /* 0x3B semicolon: number sign # */ [0x0030,0x0000], /* 0x3C less: 0 */ [0x002B,0x0000], /* 0x3D equal: plus sign + */ [0x003B,0x0000], /* 0x3E greater: semicolon ; */ [0x002F,0x0000], /* 0x3F question: slash / */ [0x203B,0x0000], /* 0x40 at: reference mark ※ */ [0x300C,0x0000], /* 0x61 A: left corner bracket「 */ [0x1106,0x0000], /* 0x62 B: choseong mieum ㅁ */ [0x1102,0x0000], /* 0x63 C: choseong nieun ㄴ */ [0x1100,0x0000], /* 0x64 D: choseong gieug ㄱ */ [0x110C,0x0000], /* 0x65 E: choseong jieuj ㅈ */ [0x110B,0x0000], /* 0x66 F: choseong ieung ㅇ */ [0x1107,0x0000], /* 0x67 G: choseong bieub ㅂ */ [0x11AB,0x0000], /* 0x68 H: jongseong nieun _ㄴ */ [0x0034,0x0000], /* 0x69 I: 4 */ [0x11BC,0x0000], /* 0x6A J: jongseong ieung _ㅇ */ [0x0031,0x0000], /* 0x6B K: 1 */ [0x0032,0x0000], /* 0x6C L: 2 */ [0x11BB,0x0000], /* 0x6D M: jongseong ssang-sios _ㅆ */ [0x11B7,0x0000], /* 0x6E N: jongseong mieum _ㅁ */ [0x0035,0x0000], /* 0x6F O: 5 */ [0x0036,0x0000], /* 0x70 P: 6 */ [0x3008,0x0000], /* 0x71 Q: left angle bracket 〈 */ [0x1109,0x0000], /* 0x72 R: choseong sios ㅅ */ [0x002C,0x0000], /* 0x73 S: comma */ [0x1105,0x0000], /* 0x74 T: choseong rieul ㄹ */ [0x11A8,0x0000], /* 0x75 U: jongseong gieug _ㄱ */ [0x1103,0x0000], /* 0x76 V: choseong digeud ㄷ */ [0x3009,0x0000], /* 0x77 W: right angle bracket 〉 */ [0x002E,0x0000], /* 0x78 X: period . */ [0x11AF,0x0000], /* 0x79 Y: jongseong rieul _ㄹ */ [0x300D,0x0000], /* 0x7A Z: right corner bracket 」 */ [0x007B,0x0000], /* 0x5B bracketleft: left brace { */ [0x007C,0x0000], /* 0x5C backslash: pipe | */ [0x007D,0x0000], /* 0x5D bracketright: right brace } */ [0x11BA,0x0000], /* 0x5E asciicircum: jongseong sios _ㅅ */ [0x002D,0x0000], /* 0x5F underscore: minus sign - */ [0x007E,0x0000], /* 0x60 quoteleft: tilde ~ */ [0x300E,0x0000], /* 0x61 a: left white corner bracket 『 */ [0x1169,0x0000], /* 0x62 b: jungseong o ㅗ */ [0x1172,0x0000], /* 0x63 c: jungseong yu ㅠ */ [0x1100,0x0000], /* 0x64 d: choseong gieug ㄱ */ [0x116E,0x0000], /* 0x65 e: jungseong u ㅜ(index finger) */ [0x110B,0x0000], /* 0x66 f: choseong ieung ㅇ */ [0x1161,0x0000], /* 0x67 g: jungseong a ㅏ */ [0x1175,0x0000], /* 0x68 h: jungseong i ㅣ */ [0x0024,0x0000], /* 0x69 i: dollar sign $ */ [0x1173,0x0000], /* 0x6A j: jungseong eu ㅡ */ [0x0021,0x0000], /* 0x6B k: exclamation mark ! */ [0x0040,0x0000], /* 0x6C l: at sign @ */ [0x116E,0x0000], /* 0x6D m: jungseong u ㅜ */ [0x1162,0x0000], /* 0x6E n: jungseong ae ㅐ */ [0x0025,0x0000], /* 0x6F o: percent sign % */ [0x005E,0x0000], /* 0x70 p: circumflex accent mark ^ */ [0x300A,0x0000], /* 0x71 q: left double angle bracket 《 */ [0x1169,0x0000], /* 0x72 r: jungseong o ㅗ(index finger) */ [0x003C,0x0000], /* 0x73 s: less-than sign < */ [0x1165,0x0000], /* 0x74 t: jungseong eo ㅓ */ [0x1166,0x0000], /* 0x75 u: jungseong e ㅔ */ [0x116D,0x0000], /* 0x76 v: jungseong yo ㅛ */ [0x300B,0x0000], /* 0x77 w: right double angle bracket 》 */ [0x003E,0x0000], /* 0x78 x: greater-than sign > */ [0x1167,0x0000], /* 0x79 y: jungseong yeo ㅕ */ [0x300F,0x0000], /* 0x7A z: right white corner bracket 』 */ [0x005B,0x0000], /* 0x7B braceleft: left bracket [ */ [0x005C,0x0000], /* 0x7C bar: backslash \ */ [0x005D,0x0000], /* 0x7D braceright: right bracket ] */ [0x0060,0x0000] /* 0x7E asciitilde: grave accent mark ` */ ] // 이건구 한 손 세벌식 왼손 K3_LGG_OH_l = [ [0x0026,0x0000], /* 0x21 exclam: ampersand & */ [0x0022,0x0000], /* 0x22 quotedbl: quotation mark ? */ [0xFFE6,0x0000], /* 0x23 numbersign: full-width won sign ₩ */ [0x1168,0x0000], /* 0x24 dollar: jungseong ye ㅖ */ [0x1174,0x0000], /* 0x25 percent: jungseong eui ㅢ */ [0x25CB,0x0000], /* 0x26 ampersand: white circle ○ */ [0x0027,0x0000], /* 0x27 apostrophe */ [0x00D7,0x0000], /* 0x28 parenleft: multiplication sign × */ [0x2026,0x0000], /* 0x29 parenright: horizontal allipsis … */ [0x25A1,0x0000], /* 0x2A asterisk: square □ */ [0x002B,0x0000], /* 0x2B plus */ [0x1102,0x0000], /* 0x2C comma: choseong nieun ㄴ */ [0x002D,0x0000], /* 0x2D minus */ [0x300D,0x0000], /* 0x2E period: right corner bracket 」 */ [0x002F,0x0000], /* 0x2F slash */ [0x2015,0x0000], /* 0x30 0: horizontal bar ― */ [0x0037,0x0000], /* 0x31 1: 7 */ [0x0038,0x0000], /* 0x32 2: 8 */ [0x0039,0x0000], /* 0x33 3: 9 */ [0x11B8,0x0000], /* 0x34 4: jongseong bieub _ㅂ */ [0x11BA,0x0000], /* 0x35 5: jongseong sios _ㅅ */ [0x1163,0x0000], /* 0x36 6: jungseong ya ㅑ */ [0x0028,0x0000], /* 0x37 7: left parenthesis ( */ [0x0029,0x0000], /* 0x38 8: right parenthesis ) */ [0x203B,0x0000], /* 0x39 9: reference mark ※ */ [0x003A,0x0000], /* 0x3A colon */ [0x003B,0x0000], /* 0x3B semicolon */ [0x1172,0x0000], /* 0x3C less: jungseong yu ㅠ */ [0x003D,0x0000], /* 0x3D equal */ [0x300F,0x0000], /* 0x3E greater: right white corner bracket 』 */ [0x003F,0x0000], /* 0x3F question */ [0x002A,0x0000], /* 0x40 at: asterisk * */ [0x0021,0x0000], /* 0x41 A: exclamation mark ! */ [0x1162,0x0000], /* 0x42 B: jungseong ae ㅐ */ [0x003E,0x0000], /* 0x43 C: greater-than sign > */ [0x0023,0x0000], /* 0x44 D: number sign # */ [0x005E,0x0000], /* 0x45 E: circumflex accent mark ^ */ [0x1173,0x0000], /* 0x46 F: jungseong eu ㅡ */ [0x1175,0x0000], /* 0x47 G: jungseong i ㅣ */ [0x1161,0x0000], /* 0x48 H: jungseong a ㅏ */ [0x116E,0x0000], /* 0x49 I: jungseong u ㅜ(index finger) */ [0x110B,0x0000], /* 0x4A J: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x4B K: choseong gieug ㄱ */ [0x300E,0x0000], /* 0x4C L: left white corner bracket 『 */ [0x116D,0x0000], /* 0x4D M: jungseong yo ㅛ */ [0x1169,0x0000], /* 0x4E N: jungseong o ㅗ */ [0x300A,0x0000], /* 0x4F O: left double angle bracket 《 */ [0x300B,0x0000], /* 0x50 P: right double angle bracket 》 */ [0x0024,0x0000], /* 0x51 Q: dollar sign $ */ [0x1166,0x0000], /* 0x52 R: jungseong e ㅔ */ [0x0040,0x0000], /* 0x53 S: at sign @ */ [0x1167,0x0000], /* 0x54 T: jungseong yeo ㅕ */ [0x1169,0x0000], /* 0x55 U: jungseong o ㅗ(index finger) */ [0x116E,0x0000], /* 0x56 V: jungseong u ㅜ */ [0x0025,0x0000], /* 0x57 W: percent sign % */ [0x003C,0x0000], /* 0x58 X: less-than sign < */ [0x1165,0x0000], /* 0x59 Y: jungseong eo ㅓ */ [0x00B7,0x0000], /* 0x5A Z: middle dot · */ [0x005B,0x0000], /* 0x5B bracketleft */ [0x005C,0x0000], /* 0x5C backslash */ [0x005D,0x0000], /* 0x5D bracketright */ [0x1163,0x0000], /* 0x5E asciicircum: jungseong ya ㅑ */ [0x005F,0x0000], /* 0x5F underscore */ [0x0060,0x0000], /* 0x60 quoteleft */ [0x0031,0x0000], /* 0x61 a: 1 */ [0x11B7,0x0000], /* 0x62 b: jongseong mieum _ㅁ */ [0x002E,0x0000], /* 0x63 c: period . */ [0x0033,0x0000], /* 0x64 d: 3 */ [0x0036,0x0000], /* 0x65 e: 6 */ [0x11BC,0x0000], /* 0x66 f: jongseong ieung _ㅇ */ [0x11AB,0x0000], /* 0x67 g: jongseong nieun _ㄴ */ [0x1107,0x0000], /* 0x68 h: choseong bieub ㅂ */ [0x110C,0x116E], /* 0x69 i: choseong jieuj ㅈ, jungseong u ㅜ(index finger) */ [0x110B,0x0000], /* 0x6A j: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x6B k: choseong gieug ㄱ */ [0x300C,0x0000], /* 0x6C l: left corner bracket 「 */ [0x1103,0x0000], /* 0x6D m: choseong digeud ㄷ */ [0x1106,0x0000], /* 0x6E n: choseong mieum ㅁ */ [0x3008,0x0000], /* 0x6F o: left angle bracket 〈 */ [0x3009,0x0000], /* 0x70 p: right angle bracket 〉 */ [0x0034,0x0000], /* 0x71 q: 4 */ [0x11A8,0x0000], /* 0x72 r: jongseong gieug _ㄱ */ [0x0032,0x0000], /* 0x73 s: 2 */ [0x11AF,0x0000], /* 0x74 t: jongseong rieul _ㄹ */ [0x1109,0x1169], /* 0x75 u: choseong sios ㅅ, jungseong o ㅗ(index finger) */ [0x11BB,0x0000], /* 0x76 v: jongseong ssang-sios _ㅆ */ [0x0035,0x0000], /* 0x77 w: 5 */ [0x002C,0x0000], /* 0x78 x: comma */ [0x1105,0x0000], /* 0x79 y: choseong rieul ㄹ */ [0x0030,0x0000], /* 0x7A z: 0 */ [0x007B,0x0000], /* 0x7B braceleft */ [0x007C,0x0000], /* 0x7C bar */ [0x007D,0x0000], /* 0x7D braceright */ [0x007E,0x0000] /* 0x7E asciitilde */ ]; // 이건구 한 손 세벌식 왼손의 캡스락 배열 K3_LGG_OH_l_capslock_layout = [ [0x0037,0x0000], /* 0x21 exclam: 7 */ [0x0027,0x0000], /* 0x22 quotedbl: apostrophe ' */ [0x0039,0x0000], /* 0x23 numbersign: 9 */ [0x11B8,0x0000], /* 0x24 dollar: jongseong bieub _ㅂ */ [0x11BA,0x0000], /* 0x25 percent: jongseong sios _ㅅ */ [0x0028,0x0000], /* 0x26 ampersand: left parenthesis ( */ [0x0022,0x0000], /* 0x27 apostrophe: quotation mark " */ [0x203B,0x0000], /* 0x28 parenleft: reference mark ※ */ [0x2015,0x0000], /* 0x29 parenright: horizontal bar ― */ [0x0029,0x0000], /* 0x2A asterisk: right parenthesis ) */ [0x003D,0x0000], /* 0x2B plus: equal sign = */ [0x1172,0x0000], /* 0x2C comma: jungseong yu ㅠ */ [0x005F,0x0000], /* 0x2D minus: underscore sign _ */ [0x300F,0x0000], /* 0x2E period: right white corner bracket 』 */ [0x003F,0x0000], /* 0x2F slash: question mark ? */ [0x2026,0x0000], /* 0x30 0: horizontal ellipsis … */ [0x0026,0x0000], /* 0x31 1: ampersand & */ [0x002A,0x0000], /* 0x32 2: asterisk * */ [0xFFE6,0x0000], /* 0x33 3: full-width won sign ₩ */ [0x1168,0x0000], /* 0x34 4: jungseong ye ㅖ */ [0x1174,0x0000], /* 0x35 5: jungseong eui ㅢ */ [0x1163,0x0000], /* 0x36 6: jungseong ya ㅑ */ [0x25CB,0x0000], /* 0x37 7: white circle ○ */ [0x25A1,0x0000], /* 0x38 8: square □ */ [0x00D7,0x0000], /* 0x39 9: multiplication sign × */ [0x003B,0x0000], /* 0x3A colon: semicolon ; */ [0x003A,0x0000], /* 0x3B semicolon: colon : */ [0x1172,0x0000], /* 0x3C less: jungseong yu ㅠ */ [0x002B,0x0000], /* 0x3D equal: plus sign + */ [0x300D,0x0000], /* 0x3E greater: right corner bracket 」 */ [0x002F,0x0000], /* 0x3F question: slash / */ [0x0038,0x0000], /* 0x40 at: 8 */ [0x0031,0x0000], /* 0x41 A: 1 */ [0x11B7,0x0000], /* 0x42 B: jongseong mieum _ㅁ */ [0x002E,0x0000], /* 0x43 C: period . */ [0x0033,0x0000], /* 0x44 D: 3 */ [0x0036,0x0000], /* 0x45 E: 6 */ [0x11BC,0x0000], /* 0x46 F: jongseong ieung _ㅇ */ [0x11AB,0x0000], /* 0x47 G: jongseong nieun _ㄴ */ [0x1107,0x0000], /* 0x48 H: choseong bieub ㅂ */ [0x110C,0x0000], /* 0x49 I: choseong jieuj ㅈ */ [0x110B,0x0000], /* 0x4A J: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x4B K: choseong gieug ㄱ */ [0x300C,0x0000], /* 0x4C L: left corner bracket「 */ [0x1103,0x0000], /* 0x4D M: choseong digeud ㄷ */ [0x1106,0x0000], /* 0x4E N: choseong mieum ㅁ */ [0x3008,0x0000], /* 0x4F O: left angle bracket 〈 */ [0x3009,0x0000], /* 0x50 P: right angle bracket 〉 */ [0x0034,0x0000], /* 0x51 Q: 4 */ [0x11A8,0x0000], /* 0x52 R: jongseong gieug _ㄱ */ [0x0032,0x0000], /* 0x53 S: 2 */ [0x11AF,0x0000], /* 0x54 T: jongseong rieul _ㄹ */ [0x1109,0x0000], /* 0x55 U: choseong sios ㅅ */ [0x11BB,0x0000], /* 0x56 V: jongseong ssang-sios _ㅆ */ [0x0035,0x0000], /* 0x57 W: 5 */ [0x002C,0x0000], /* 0x58 X: comma */ [0x1105,0x0000], /* 0x59 Y: choseong rieul ㄹ */ [0x0030,0x0000], /* 0x5A Z: 0 */ [0x007B,0x0000], /* 0x5B bracketleft: left brace { */ [0x007C,0x0000], /* 0x5C backslash: pipe | */ [0x007D,0x0000], /* 0x5D bracketright: right brace } */ [0x1163,0x0000], /* 0x5E asciicircum: jungseong ya ㅑ */ [0x002D,0x0000], /* 0x5F underscore: minus sign - */ [0x007E,0x0000], /* 0x60 quoteleft: tilde ~ */ [0x0021,0x0000], /* 0x61 a: exclamation mark ! */ [0x1162,0x0000], /* 0x62 b: jungseong ae ㅐ */ [0x003E,0x0000], /* 0x63 c: greater-than sign > */ [0x0023,0x0000], /* 0x64 d: number sign # */ [0x005E,0x0000], /* 0x65 e: circumflex accent mark ^ */ [0x1173,0x0000], /* 0x66 f: jungseong eu ㅡ */ [0x1175,0x0000], /* 0x67 g: jungseong i ㅣ */ [0x1161,0x0000], /* 0x68 h: jungseong a ㅏ */ [0x116E,0x0000], /* 0x69 i: jungseong u ㅜ(index finger) */ [0x110B,0x0000], /* 0x6A j: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x6B k: choseong gieug ㄱ */ [0x300E,0x0000], /* 0x6C l: left white corner bracket 『 */ [0x116D,0x0000], /* 0x6D m: jungseong yo ㅛ */ [0x1169,0x0000], /* 0x6E n: jungseong o ㅗ */ [0x300A,0x0000], /* 0x6F o: left double angle bracket 《 */ [0x300B,0x0000], /* 0x70 p: right double angle bracket 》 */ [0x0024,0x0000], /* 0x71 q: dollar sign $ */ [0x1166,0x0000], /* 0x72 r: jungseong e ㅔ */ [0x0040,0x0000], /* 0x73 s: at sign @ */ [0x1167,0x0000], /* 0x74 t: jungseong yeo ㅕ */ [0x1169,0x0000], /* 0x75 u: jungseong o ㅗ(index finger) */ [0x116E,0x0000], /* 0x76 v: jungseong u ㅜ */ [0x0025,0x0000], /* 0x77 w: percent sign % */ [0x003C,0x0000], /* 0x78 x: less-than sign < */ [0x1165,0x0000], /* 0x79 y: jungseong eo ㅓ */ [0x00B7,0x0000], /* 0x7A z: middle dot · */ [0x005B,0x0000], /* 0x7B braceleft: left bracket [ */ [0x005C,0x0000], /* 0x7C bar: backslash \ */ [0x005D,0x0000], /* 0x7D braceright: right bracket ] */ [0x0060,0x0000] /* 0x7E asciitilde: grave accent mark ` */ ]; K3_LGG_OH_ohk = [ [0x1168,0x0000], /* 0x21 exclam: jungseong ye ㅖ */ [0x0000,0x0000], /* 0x22 quotedbl: null */ [0x000D,0x0000], /* 0x23 numbersign: ENTER */ [0x003F,0x0000], /* 0x24 dollar: question mark ? */ [0x0021,0x0000], /* 0x25 percent: exclamation mark ! */ [0x0000,0x0000], /* 0x26 ampersand: null */ [0x0000,0x0000], /* 0x27 apostrophe: null */ [0x0000,0x0000], /* 0x28 parenleft: null */ [0x0000,0x0000], /* 0x29 parenright: null */ [0x0000,0x0000], /* 0x2A asterisk: null */ [0x0000,0x0000], /* 0x2B plus: null */ [0x0000,0x0000], /* 0x2C comma: null */ [0x0000,0x0000], /* 0x2D minus: null */ [0x0000,0x0000], /* 0x2E period: null */ [0x0000,0x0000], /* 0x2F slash: null */ [0x0000,0x0000], /* 0x30 0: null */ [0x11B8,0x0000], /* 0x31 1: jongseong bieub _ㅂ */ [0x11BA,0x0000], /* 0x32 2: jongseong sios _ㅅ */ [0x1163,0x0000], /* 0x33 3: jungseong ya ㅑ */ [0x002E,0x0000], /* 0x34 4: period . */ [0x002C,0x0000], /* 0x35 5: comma */ [0x000D,0x0000], /* 0x36 6: ENTER */ [0x0000,0x0000], /* 0x37 7: null */ [0x0000,0x0000], /* 0x38 8: null */ [0x0000,0x0000], /* 0x39 9: null */ [0x0000,0x0000], /* 0x3A colon: null */ [0x0000,0x0000], /* 0x3B semicolon: null */ [0x0000,0x0000], /* 0x3C less: null */ [0x0000,0x0000], /* 0x3D equal: null */ [0x0000,0x0000], /* 0x3E greater: null */ [0x0000,0x0000], /* 0x3F question: null */ [0x1174,0x0000], /* 0x40 at: jungseong eui ㅢ */ [0x1173,0x0000], /* 0x41 A: jungseong eu ㅡ */ [0x1172,0x0000], /* 0x42 B: jungseong yu ㅠ */ [0x1169,0x0000], /* 0x43 C: jungseong o ㅗ */ [0x1161,0x0000], /* 0x44 D: jungseong ㅏ a */ [0x1165,0x0000], /* 0x45 E: jungseong eo ㅓ */ [0x110B,0x0000], /* 0x46 F: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x47 G: choseong giyeok ㄱ */ [0x0000,0x0000], /* 0x48 H: null */ [0x0000,0x0000], /* 0x49 I: null */ [0x0000,0x0000], /* 0x4A J: null */ [0x0000,0x0000], /* 0x4B K: null */ [0x0000,0x0000], /* 0x4C L: null */ [0x0000,0x0000], /* 0x4D M: null */ [0x0000,0x0000], /* 0x4E N: null */ [0x0000,0x0000], /* 0x4F O: null */ [0x0000,0x0000], /* 0x50 P: null */ [0x1166,0x0000], /* 0x51 Q: jungseong e ㅔ */ [0x1169,0x0000], /* 0x52 R: jungseong o ㅗ(index finger) */ [0x1175,0x0000], /* 0x53 S: jungseong i ㅣ */ [0x116E,0x0000], /* 0x54 T: jungseong u ㅜ(index finger) */ [0x0000,0x0000], /* 0x55 U: null */ [0x116D,0x0000], /* 0x56 V: jungseong yo ㅛ */ [0x1167,0x0000], /* 0x57 W: jungseong yeo ㅕ */ [0x1162,0x0000], /* 0x58 X: jungseong ae ㅐ */ [0x0000,0x0000], /* 0x59 Y: null */ [0x116E,0x0000], /* 0x5A Z: jungseong u ㅜ */ [0x0000,0x0000], /* 0x5B bracketleft: null */ [0x0000,0x0000], /* 0x5C backslash: null */ [0x0000,0x0000], /* 0x5D bracketright: null */ [0x000D,0x0000], /* 0x5E asciicircum: ENTER */ [0x0000,0x0000], /* 0x5F underscore: null */ [0x0008,0x0000], /* 0x60 quoteleft: BACKSPACE */ [0x11BC,0x0000], /* 0x61 a: jongseong ieung _ㅇ */ [0x1102,0x0000], /* 0x62 b: choseong nieun ㄴ */ [0x1106,0x0000], /* 0x63 c: choseong mieum ㅁ */ [0x1107,0x0000], /* 0x64 d: choseong bieub ㅂ */ [0x1105,0x0000], /* 0x65 e: choseong rieul ㄹ */ [0x110B,0x0000], /* 0x66 f: choseong ieung ㅇ */ [0x1100,0x0000], /* 0x67 g: choseong giyeog ㄱ */ [0x0000,0x0000], /* 0x68 h: null */ [0x0000,0x0000], /* 0x69 i: null */ [0x0000,0x0000], /* 0x6A j: null */ [0x0000,0x0000], /* 0x6B k: null */ [0x0000,0x0000], /* 0x6C l: null */ [0x0000,0x0000], /* 0x6D m: null */ [0x0000,0x0000], /* 0x6E n: null */ [0x0000,0x0000], /* 0x6F o: null */ [0x0000,0x0000], /* 0x70 p: null */ [0x11A8,0x0000], /* 0x71 q: jongseong giyeog _ㄱ */ [0x1109,0x1169], /* 0x72 r: choseong sios ㅅ, jungseong o ㅗ(index finger) */ [0x11AB,0x0000], /* 0x73 s: jongseong nieun _ㄴ */ [0x110C,0x116E], /* 0x74 t: choseong jieuj ㅈ, jungseong u ㅜ(index finger) */ [0x0000,0x0000], /* 0x75 u: null */ [0x1103,0x0000], /* 0x76 v: choseong digeud ㄷ */ [0x11AF,0x0000], /* 0x77 w: jongseong rieul _ㄹ */ [0x11B7,0x0000], /* 0x78 x: jongseong mieum _ㅁ */ [0x0000,0x0000], /* 0x79 y: null */ [0x11BB,0x0000], /* 0x7A z: jongseong ssang-sios _ㅆ */ [0x0000,0x0000], /* 0x7B braceleft: null */ [0x0000,0x0000], /* 0x7C bar: null */ [0x0000,0x0000], /* 0x7D braceright: null */ [0x0008,0x0000] /* 0x7E asciitilde: BACKSPACE */ ]; K3_LGG_OH_ohk_capslock_layout = [ [0x007B,0x0000], /* 0x21 exclam: left brace { */ [0x0000,0x0000], /* 0x22 quotedbl: null */ [0x007E,0x0000], /* 0x23 numbersign: tilde ~ */ [0x003C,0x0000], /* 0x24 dollar: less-than sign < */ [0x003E,0x0000], /* 0x25 percent: greater-than sign > */ [0x0000,0x0000], /* 0x26 ampersand: null */ [0x0000,0x0000], /* 0x27 apostrophe: null */ [0x0000,0x0000], /* 0x28 parenleft: null */ [0x0000,0x0000], /* 0x29 parenright: null */ [0x0000,0x0000], /* 0x2A asterisk: null */ [0x0000,0x0000], /* 0x2B plus: null */ [0x0000,0x0000], /* 0x2C comma: null */ [0x0000,0x0000], /* 0x2D minus: null */ [0x0000,0x0000], /* 0x2E period: null */ [0x0000,0x0000], /* 0x2F slash: null */ [0x0000,0x0000], /* 0x30 0: null */ [0x005B,0x0000], /* 0x31 1: left bracket [ */ [0x005D,0x0000], /* 0x32 2: right bracket ] */ [0x0060,0x0000], /* 0x33 3: grave accent mark ` */ [0x002E,0x0000], /* 0x34 4: period . */ [0x002C,0x0000], /* 0x35 5: comma */ [0x000D,0x0000], /* 0x36 6: ENTER */ [0x0000,0x0000], /* 0x37 7: null */ [0x0000,0x0000], /* 0x38 8: null */ [0x0000,0x0000], /* 0x39 9: null */ [0x0000,0x0000], /* 0x3A colon: null */ [0x0000,0x0000], /* 0x3B semicolon: null */ [0x0000,0x0000], /* 0x3C less: null */ [0x0000,0x0000], /* 0x3D equal: null */ [0x0000,0x0000], /* 0x3E greater: null */ [0x0000,0x0000], /* 0x3F question: null */ [0x007D,0x0000], /* 0x40 at: right brace } */ [0x005F,0x0000], /* 0x41 A: underscore sign _ */ [0x003A,0x0000], /* 0x42 B: colon : */ [0x0040,0x0000], /* 0x43 C: at sign @ */ [0x0025,0x0000], /* 0x44 D: percent sign % */ [0x002A,0x0000], /* 0x45 E: asterisk * */ [0x005E,0x0000], /* 0x46 F: circumflex accent mark ^ */ [0x0022,0x0000], /* 0x47 G: quotation mark */ [0x0000,0x0000], /* 0x48 H: null */ [0x0000,0x0000], /* 0x49 I: null */ [0x0000,0x0000], /* 0x4A J: null */ [0x0000,0x0000], /* 0x4B K: null */ [0x0000,0x0000], /* 0x4C L: null */ [0x0000,0x0000], /* 0x4D M: null */ [0x0000,0x0000], /* 0x4E N: null */ [0x0000,0x0000], /* 0x4F O: null */ [0x0000,0x0000], /* 0x50 P: null */ [0x005C,0x0000], /* 0x51 Q: backslash \ */ [0x0028,0x0000], /* 0x52 R: left parenthesis ( */ [0x0024,0x0000], /* 0x53 S: dollar sign $ */ [0x0029,0x0000], /* 0x54 T: right parenthesis ) */ [0x0000,0x0000], /* 0x55 U: null */ [0x0023,0x0000], /* 0x56 V: number sign # */ [0x0026,0x0000], /* 0x57 W: ampersand & */ [0x007C,0x0000], /* 0x58 X: pipe | */ [0x0000,0x0000], /* 0x59 Y: null */ [0x002B,0x0000], /* 0x5A Z: plus sign + */ [0x0000,0x0000], /* 0x5B bracketleft: null */ [0x0000,0x0000], /* 0x5C backslash: null */ [0x0000,0x0000], /* 0x5D bracketright: null */ [0x000D,0x0000], /* 0x5E asciicircum: ENTER */ [0x0000,0x0000], /* 0x5F underscore: null */ [0x0008,0x0000], /* 0x60 quoteleft: BACKSPACE */ [0x002D,0x0000], /* 0x61 a: minus sign - */ [0x003B,0x0000], /* 0x62 b: semicolon ; */ [0x0032,0x0000], /* 0x63 c: 2 */ [0x0035,0x0000], /* 0x64 d: 5 */ [0x0038,0x0000], /* 0x65 e: 8 */ [0x0036,0x0000], /* 0x66 f: 6 */ [0x0027,0x0000], /* 0x67 g: apostrophe ' */ [0x0000,0x0000], /* 0x68 h: null */ [0x0000,0x0000], /* 0x69 i: null */ [0x0000,0x0000], /* 0x6A j: null */ [0x0000,0x0000], /* 0x6B k: null */ [0x0000,0x0000], /* 0x6C l: null */ [0x0000,0x0000], /* 0x6D m: null */ [0x0000,0x0000], /* 0x6E n: null */ [0x0000,0x0000], /* 0x6F o: null */ [0x0000,0x0000], /* 0x70 p: null */ [0x002F,0x0000], /* 0x71 q: slash / */ [0x0039,0x0000], /* 0x72 r: 9 */ [0x0034,0x0000], /* 0x73 s: 4 */ [0x0030,0x0000], /* 0x74 t: 0 */ [0x0000,0x0000], /* 0x75 u: null */ [0x0033,0x0000], /* 0x76 v: 3 */ [0x0037,0x0000], /* 0x77 w: 7 */ [0x0031,0x0000], /* 0x78 x: 1 */ [0x0000,0x0000], /* 0x79 y: null */ [0x003D,0x0000], /* 0x7A z: equal sign = */ [0x0000,0x0000], /* 0x7B braceleft: null */ [0x0000,0x0000], /* 0x7C bar: null */ [0x0000,0x0000], /* 0x7D braceright: null */ [0x0008,0x0000] /* 0x7E asciitilde: BACKSPACE */ ]; // 3-2011 옛한글, 3-2012 옛한글, 3-2014, 3-2015P 자판의 한글 확장 배열 K3_2012y_extended_hangeul_layout = [ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x21 exclam: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x22 quotedbl: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x23 numbersign: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x24 dollar: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x25 percent: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x26 ampersand: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x27 apostrophe: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x28 parenleft: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x29 parenright: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2A asterisk: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2B plus: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2C comma: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2D minus: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2E period: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x2F slash: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x30 0: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x31 1: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x32 2: jongseong ap_sieus(non-standard), jongseong ssang_ap_sieus(non-standard), jongseong dwis_sieus(non-standard), jongseong ssang_dwis_sieus(non-standard) */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x33 3: */ [[0xD7C2,0x0000], [0x0000,0x0000]], /* 0x34 4: i+yo */ [[0xD7C3,0x0000], [0x0000,0x0000]], /* 0x35 5: i+yu */ [[0x1199,0x0000], [0x0000,0x0000]], /* 0x36 6: i+ya */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x37 7: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x38 8: */ [[0x113C,0x113D], [0x113E,0x113F]], /* 0x39 9: choseong ap_sieus, choseong ssang_ap_sieus, choseong dwis_sieus, choseong ssang_dwis_sieus */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3A colon: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3B semicolon: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3C less: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3D equal: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3E greater: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x3F question: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x40 at: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x41 A: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x42 B: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x43 C: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x44 D: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x45 E: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x46 F: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x47 G: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x48 H: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x49 I: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4A J: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4B K: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4C L: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4D M: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4E N: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x4F O: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x50 P: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x51 Q: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x52 R: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x53 S: */ [[0xD7BE,0x0000], [0x0000,0x0000]], /* 0x54 T: i+yae */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x55 U: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x56 V: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x57 W: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x58 X: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x59 Y: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5A Z: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5B */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5C */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5D */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5E */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x5F */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x60 */ [[0x0000,0x0000], [0x11F0,0x11EE]], /* 0x61 a: yes_ieung, ssang_yesieung */ [[0x119B,0x0000], [0x1195,0x0000]], /* 0x62 b: i+u, eu+u */ [[0x1168,0x0000], [0xD7BB,0x0000]], /* 0x63 c: ye, eu+e */ [[0xD7C4,0x0000], [0x1174,0x0000]], /* 0x64 d: i+i, eu+i */ [[0xD7BF,0x0000], [0x119E,0x11A2]], /* 0x65 e: i+yeo, alae_a, ssang_alae_a */ [[0x1198,0x0000], [0xD7B9,0x0000]], /* 0x66 f: i+a, eu+a */ [[0x119C,0x0000], [0x1196,0x0000]], /* 0x67 g: i+eu, eu+eu */ [[0x0000,0x0000], [0x1159,0xA97C]], /* 0x68 h: choseong yeolin_hieuh, choseong ssang_yeolin_hieuh */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x69 i: */ [[0x0000,0x0000], [0x114C,0x0000]], /* 0x6A j: choseong yes_ieung, choseong ssang_yes_ieung(non-standard) */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x6B k: */ [[0x114E,0x114F], [0x1150,0x1151]], /* 0x6C l: choseong ap_jieuj, choseong ssang_ap_jieuj, choseong dwis_jieuj, choseong ssang_dwis_jieuj */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x6D m: */ [[0x0000,0x0000], [0x1140,0x0000]], /* 0x6E n: choseong yeolin_sieus*/ [[0x1154,0x0000], [0x1155,0x0000]], /* 0x6F o: choseong ap_chieuch, choseong dwis_chieuch */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x70 p: */ [[0x0000,0x0000], [0x11EB,0x0000]], /* 0x71 q: jongseong yeolin_sieus */ [[0x0000,0x0000], [0xD7BA,0x0000]], /* 0x72 r: eu+eo */ [[0x0000,0x0000], [0x11F9,0x0000]], /* 0x73 s: jongseong yeolin_hieuh, jongseong yeolin_ssang_hieuh(non-standard) */ [[0x1164,0xD7BE], [0x0000,0x0000]], /* 0x74 t: yae, i+yae */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x75 u: */ [[0x119A,0x0000], [0xD7BC,0x0000]], /* 0x76 v: i+o, eu+o */ [[0x0000,0x0000], [0xD7DD,0x0000]], /* 0x77 w: jongseong lieul-ieung */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x78 x: jongseong ap_jieuj(non-standard), jongseong ssang_ap_jieuj(non-standard), jongseong dwis_jieuj(non-standard), jongseong ssang_dwis_jieuj(non-standard) */ [[0x0000,0x0000], [0x111B,0x0000]], /* 0x79 y: choseong lieul-ieung */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x7A z: jongseong ap_chieuch(non-standard), jongseong dwis_chieuch(non-standard) */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x7B braceleft: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x7C bar: */ [[0x0000,0x0000], [0x0000,0x0000]], /* 0x7D braceright: */ [[0x0000,0x0000], [0x0000,0x0000]] /* 0x7E asciitilde: */ ]; // 3-2011 자판 기호 확장 배열 K3_2011_extended_sign_layout = [/*!*/[0,0,0], /*"*/[0x266A,0x266C,0], /*#*/[0,0,0], /*$*/[0xFFE0,0,0], /*%*/[0,0,0], /*&*/[0,0,0], /*'*/[0x326B,0x2030,0x2031], /*(*/[0,0,0], /*)*/[0,0,0], /***/[0,0,0], /*+*/[0x2640,0,0], /*,*/[0x3001,0x3008,0x2266], /*-*/[0x3010,0x3014,0x2601], /*.*/[0x3002,0x3009,0x2267], /*/*/[0x2026,0x203B,0x2504], /*0*/[0x326A,0x300D,0], /*1*/[0x3BC,0x2122,0], /*2*/[0xB2,0x2109,0], /*3*/[0xB3,0x2103,0], /*4*/[0xFFE6,0xFFE5,0], /*5*/[0x20AC,0xFFE1,0], /*6*/[0x327E,0x2702,0], /*7*/[0xA7,0x300E,0], /*8*/[0,0x300F,0], /*9*/[0,0x300C,0], /*:*/[0x2463,0x246D,0x3254], /*;*/[0x3265,0x2463,0x246D], /*<*/[0,0,0], /*=*/[0x3011,0x3015,0x2603], /*>*/[0,0,0], /*?*/[0,0,0], /*@*/[0,0,0], /*A*/[0,0,0], /*B*/[0,0,0], /*C*/[0,0,0], /*D*/[0,0,0], /*E*/[0x2715,0,0], /*F*/[0,0,0], /*G*/[0xA6,0x2506,0], /*H*/[0x2469,0x2473,0x325A], /*I*/[0x2466,0x2470,0x3257], /*J*/[0x2460,0x246A,0x3251], /*K*/[0x2461,0x246B,0x3252], /*L*/[0x2462,0x246C,0x3253], /*M*/[0x201D,0x2019,0], /*N*/[0x201C,0x2018,0], /*O*/[0x2467,0x2471,0x3258], /*P*/[0x2468,0x2472,0x3259], /*Q*/[0x2199,0x2196,0x261F], /*R*/[0xB4,0,0], /*S*/[0,0,0], /*T*/[0,0,0x2610], /*U*/[0x2465,0x246F,0x3256], /*V*/[0,0,0], /*W*/[0x2198,0x2197,0x261D], /*X*/[0,0,0], /*Y*/[0x2464,0x246E,0x3255], /*Z*/[0,0,0], /*[*/[0x7B,0xB1,0x2600], /*\*/[0x2260,0x2252,0xB6], /*]*/[0x7D,0xF7,0x2602], /*^*/[0x321C,0,0], /*_*/[0x2642,0,0], /*`*/[0xA9,0xAE,0x2117], /*a*/[0x25C7,0x25C8,0x25C6], /*b*/[0x2D0,0x25C1,0x25C0], /*c*/[0xB0,0x260E,0x2668], /*d*/[0x25CB,0x25C9,0x25CF], /*e*/[0xD7,0x2194,0x2195], /*f*/[0x2015,0x25B3,0x25B2], /*g*/[0x7C,0x25BD,0x25BC], /*h*/[0x3261,0x2469,0x2473], /*i*/[0x3264,0x2466,0x2470], /*j*/[0x3267,0x2460,0x246A], /*k*/[0x3260,0x2461,0x246B], /*l*/[0x3268,0x2462,0x246C], /*m*/[0x326D,0x300B,0], /*n*/[0x3266,0x300A,0], /*o*/[0x3269,0x2467,0x2471], /*p*/[0x326C,0x2468,0x2472], /*q*/[0x2190,0x2193,0x261C], /*r*/[0x60,0x2022,0x25E6], /*s*/[0x25A1,0x25A3,0x25A0], /*t*/[0x3003,0x2713,0x2611], /*u*/[0x3262,0x2465,0x246F], /*v*/[0,0x25B7,0x25B6], /*w*/[0x2192,0x2191,0x261E], /*x*/[0x2032,0x2606,0x2605], /*y*/[0x3263,0x2464,0x246E], /*z*/[0x2033,0x2661,0x2665], /*{*/[0,0,0], /*|*/[0,0,0], /*}*/[0xF7,0,0], /*~*/[0x327F,0,0]]; // 3-2012 자판 기호 확장 배열 K3_2012_extended_sign_layout = [/*!*/[0,0,0], /*"*/[0x266A,0x266C,0], /*#*/[0,0,0], /*$*/[0xFFE0,0,0], /*%*/[8240,8241,0], /*&*/[0,0,0], /*'*/[0x326B,0xF7,0], /*(*/[0,0,0], /*)*/[0,0,0], /***/[0,0,0], /*+*/[0,0,0], /*,*/[0x3001,0x3008,0x2266], /*-*/[0xB1,0x2642,0x2601], /*.*/[0x3002,0x3009,0x2267], /*/*/[0x2026,0x203B,0x2504], /*0*/[0x326A,0x300D,0], /*1*/[0x3BC,0x2122,0], /*2*/[0xB2,0x2109,0], /*3*/[0xB3,0x2103,0], /*4*/[0xFFE6,0xFFE5,0x4B0], /*5*/[0x20AC,0xFFE1,0], /*6*/[0x327E,0x2702,0], /*7*/[0xA7,0x300E,0], /*8*/[0,0x300F,0], /*9*/[0,0x300C,0], /*:*/[0x2463,0x246D,0x3254], /*;*/[0x3265,0x2463,0x246D], /*<*/[0,0x2640,0], /*=*/[0x2260,0,0x2603], /*>*/[0,0,0], /*?*/[0,0,0], /*@*/[0,0,0], /*A*/[0,0,0], /*B*/[0,0,0], /*C*/[0,0,0], /*D*/[0,0,0], /*E*/[0x2715,0,0], /*F*/[0,0,0], /*G*/[0xA6,0x2506,0], /*H*/[0x2469,0x2473,0x325A], /*I*/[0x2466,0x2470,0x3257], /*J*/[0x2460,0x246A,0x3251], /*K*/[0x2461,0x246B,0x3252], /*L*/[0x2462,0x246C,0x3253], /*M*/[0x201D,0x2019,0], /*N*/[0x201C,0x2018,0], /*O*/[0x2467,0x2471,0x3258], /*P*/[0x2468,0x2472,0x3259], /*Q*/[0x2199,0x2196,0x261F], /*R*/[0,0,0], /*S*/[0,0,0], /*T*/[0,0,0x2610], /*U*/[0x2465,0x246F,0x3256], /*V*/[0,0,0], /*W*/[0x2198,0x2197,0x261D], /*X*/[0,0,0], /*Y*/[0x2464,0x246E,0x3255], /*Z*/[0,0,0], /*[*/[0x3010,0x3014,0x2600], /*\*/[0x2252,0xB6,0], /*]*/[0x3011,0x3015,0x2602], /*^*/[0x321C,0,0], /*_*/[0,0,0], /*`*/[0xA9,0xAE,0x2117], /*a*/[0x25C7,0x25C8,0x25C6], /*b*/[0x2D0,0x25C1,0x25C0], /*c*/[0xB0,0x260E,0x2668], /*d*/[0x25CB,0x25C9,0x25CF], /*e*/[0xD7,0x2194,0x2195], /*f*/[0xB7,0x25B3,0x25B2], /*g*/[0x2015,0x25BD,0x25BC], /*h*/[0x3261,0x2469,0x2473], /*i*/[0x3264,0x2466,0x2470], /*j*/[0x3267,0x2460,0x246A], /*k*/[0x3260,0x2461,0x246B], /*l*/[0x3268,0x2462,0x246C], /*m*/[0x326D,0x300B,0], /*n*/[0x3266,0x300A,0], /*o*/[0x3269,0x2467,0x2471], /*p*/[0x326C,0x2468,0x2472], /*q*/[0x2190,0x2193,0x261C], /*r*/[0xB4,0x2022,0x25E6], /*s*/[0x25A1,0x25A3,0x25A0], /*t*/[0x3003,0x2713,0x2611], /*u*/[0x3262,0x2465,0x246F], /*v*/[0,0x25B7,0x25B6], /*w*/[0x2192,0x2191,0x261E], /*x*/[0x2032,0x2606,0x2605], /*y*/[0x3263,0x2464,0x246E], /*z*/[0x2033,0x2661,0x2665], /*{*/[0,0,0], /*|*/[0,0,0], /*}*/[0,0,0], /*~*/[0x327F,0,0]]; K3_2011y_extended_sign_layout = [ [[0x2921,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x21 exclam */ [[0x266A,0x266C,0x0000], [0x0000,0x0000,0x0000]], /* 0x22 quotedbl */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x23 numbersign */ [[0x0000,0x0000,0x0000], [0xFE35,0x0000,0x0000]], /* 0x24 dollar */ [[0x0000,0x0000,0x0000], [0xFE36,0x0000,0x0000]], /* 0x25 percent */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x26 ampersand */ [[0x326B,0x3279,0x0000], [0x2030,0x2031,0x0000]], /* 0x27 apostrophe */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x28 parenleft */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x29 parenright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2A asterisk */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2B plus */ [[0x3001,0x0000,0x0000], [0x2266,0x226A,0x0000]], /* 0x2C comma */ [[0x2642,0x2600,0x2601], [0x0000,0x0000,0x0000]], /* 0x2D minus */ [[0x3002,0x0000,0x0000], [0x2267,0x226B,0x0000]], /* 0x2E period */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2F slash */ [[0x326A,0x3278,0x0000], [0x2713,0x2611,0x2610]], /* 0x30 0 */ [[0x2195,0x21C5,0x21F3], [0x03BC,0x0000,0x0000]], /* 0x31 1 */ [[0x2194,0x21C4,0x2B04], [0x2109,0x0000,0x0000]], /* 0x32 2 */ [[0xFFE5,0x04B0,0x0000], [0x2103,0x0000,0x0000]], /* 0x33 3 */ [[0xFFE6,0x0000,0x0000], [0xFFE0,0x0000,0x0000]], /* 0x34 4 */ [[0x20AC,0xFFE1,0x0000], [0x0000,0x0000,0x0000]], /* 0x35 5 */ [[0x327E,0x321C,0x327F], [0x2702,0x0000,0x0000]], /* 0x36 6 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x37 7 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x38 8 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x39 9 */ [[0x2084,0x0000,0x0000], [0x2074,0x0000,0x0000]], /* 0x3A colon */ [[0x3265,0x3273,0x0000], [0x2463,0x246D,0x3254,0x325E,0x32B9]], /* 0x3B semicolon */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3C less */ [[0x2640,0x2602,0x2603], [0x2260,0x2245,0x0000]], /* 0x3D equal */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3E greater */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3F question */ [[0x2922,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x40 at */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x41 A */ [[0x0000,0x0000,0x0000], [0xFE40,0xFE3E,0x0000]], /* 0x42 B */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x43 C */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x44 D */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x45 E */ [[0x0000,0x0000,0x0000], [0xFE41,0xFE43,0x0000]], /* 0x46 F */ [[0x0000,0x0000,0x0000], [0xFE42,0xFE44,0x0000]], /* 0x47 G */ [[0x2080,0x0000,0x0000], [0x2070,0x0000,0x0000]], /* 0x48 H */ [[0x2087,0x0000,0x0000], [0x2077,0x0000,0x0000]], /* 0x49 I */ [[0x2081,0x0000,0x0000], [0x00B9,0x0000,0x0000]], /* 0x4A J */ [[0x2082,0x0000,0x0000], [0x00B2,0x33A1,0x0000]], /* 0x4B K */ [[0x2083,0x0000,0x0000], [0x00B3,0x33A5,0x0000]], /* 0x4C L */ [[0x2019,0x201D,0x0000], [0x0000,0x0000,0x0000]], /* 0x4D M */ [[0x2018,0x201C,0x0000], [0x0000,0x0000,0x0000]], /* 0x4E N */ [[0x2088,0x0000,0x0000], [0x2078,0x0000,0x0000]], /* 0x4F O */ [[0x2089,0x0000,0x0000], [0x2079,0x0000,0x0000]], /* 0x50 P */ [[0x2199,0x21B2,0x2B10], [0x2196,0x21B0,0x2B11]], /* 0x51 Q */ [[0x0000,0x0000,0x0000], [0xFE3B,0xFE39,0x0000]], /* 0x52 R */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x53 S */ [[0x0000,0x0000,0x0000], [0xFE38,0xFE3C,0xFE3A]], /* 0x54 T */ [[0x2086,0x0000,0x0000], [0x2076,0x0000,0x0000]], /* 0x55 U */ [[0x0000,0x0000,0x0000], [0xFE3F,0xFE3D,0x0000]], /* 0x56 V */ [[0x2198,0x21B3,0x2B0E], [0x2197,0x21B1,0x2B0F]], /* 0x57 W */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x58 X */ [[0x2085,0x0000,0x0000], [0x2075,0x0000,0x0000]], /* 0x59 Y */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5A Z */ [[0x2022,0x25B3,0x25B2], [0x00B1,0x25B7,0x25B6]], /* 0x5B bracketleft */ [[0x00B6,0x23CE,0x0000], [0x2252,0x2248,0x0000]], /* 0x5C backslash */ [[0x2025,0x25BD,0x25BC], [0x00F7,0x25C1,0x25C0]], /* 0x5D bracketright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5E asciicircum */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5F underscore */ [[0x00A9,0x00AE,0x2117], [0x2122,0x0000,0x0000]], /* 0x60 quoteleft */ [[0x2026,0x2504,0x0000], [0x25C7,0x25C8,0x25C6]], /* 0x61 a */ [[0x00A7,0x0000,0x0000], [0x3009,0x300B,0x0000]], /* 0x62 b */ [[0x00B0,0x0000,0x0000], [0x260E,0x2668,0x0000]], /* 0x63 c */ [[0x302E,0x0000,0x0000], [0x25CB,0x25C9,0x25CF]], /* 0x64 d */ [[0x00D7,0x2715,0x0000], [0x203B,0x327C,0x327D]], /* 0x65 e */ [[0x2015,0x00AF,0xFFE3], [0x300C,0x300E,0x0000]], /* 0x66 f */ [[0x007C,0x00A6,0x2506], [0x300D,0x300F,0x0000]], /* 0x67 g */ [[0x3261,0x326F,0x0000], [0x2469,0x2473,0x325A,0x32B5,0x32BF]], /* 0x68 h */ [[0x3264,0x3272,0x0000], [0x2466,0x2470,0x3257,0x32B2,0x32BC]], /* 0x69 i */ [[0x3267,0x3275,0x0000], [0x2460,0x246A,0x3251,0x325B,0x32B6]], /* 0x6A j */ [[0x3260,0x326E,0x0000], [0x2461,0x246B,0x3252,0x325C,0x32B7]], /* 0x6B k */ [[0x3268,0x3276,0x0000], [0x2462,0x246C,0x3253,0x325D,0x32B8]], /* 0x6C l */ [[0x326D,0x327B,0x0000], [0x2234,0x2235,0x2261]], /* 0x6D m */ [[0x3266,0x3274,0x0000], [0x221E,0x221D,0x0000]], /* 0x6E n */ [[0x3269,0x3277,0x0000], [0x2467,0x2471,0x3258,0x32B3,0x32BD]], /* 0x6F o */ [[0x326C,0x327A,0x0000], [0x2468,0x2472,0x3259,0x32B4,0x32BE]], /* 0x70 p */ [[0x2190,0x261C,0x21E6], [0x2193,0x261F,0x21E9]], /* 0x71 q */ [[0x0060,0x00B4,0x0000], [0x007B,0x3010,0x3014]], /* 0x72 r */ [[0x302F,0x0000,0x0000], [0x25A1,0x25A3,0x25A0]], /* 0x73 s */ [[0x3003,0x0000,0x0000], [0x3011,0x3015,0x0000]], /* 0x74 t */ [[0x3262,0x3270,0x0000], [0x2465,0x246F,0x3256,0x32B1,0x32BB]], /* 0x75 u */ [[0x02D0,0x0000,0x0000], [0x3008,0x300A,0x0000]], /* 0x76 v */ [[0x2192,0x261E,0x21E8], [0x2191,0x261D,0x21E7]], /* 0x77 w */ [[0x2032,0x0000,0x0000], [0x2606,0x2605,0x0000]], /* 0x78 x */ [[0x3263,0x3271,0x0000], [0x2464,0x246E,0x3255,0x325F,0x32BA]], /* 0x79 y */ [[0x2033,0x0000,0x0000], [0x2661,0x2665,0x0000]], /* 0x7A z */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7B braceleft */ [[0x00A6,0x2506,0x0000], [0x0000,0x0000,0x0000]], /* 0x7C bar */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7D braceright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]] /* 0x7E asciitilde */ ]; K3_2012y_extended_sign_layout = [ // 3-2012 옛한글, 3-2014, 3-2015P, 3-P2 자판의 기호 확장 배열 [[0x2921,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x21 exclam */ [[0x266A,0x266C,0x0000], [0x0000,0x0000,0x0000]], /* 0x22 quotedbl */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x23 numbersign */ [[0x0000,0x0000,0x0000], [0xFE35,0x0000,0x0000]], /* 0x24 dollar */ [[0x0000,0x0000,0x0000], [0xFE36,0x0000,0x0000]], /* 0x25 percent */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x26 ampersand */ [[0x326B,0x3279,0x0000], [0x00F7,0x0000,0x0000]], /* 0x27 apostrophe */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x28 parenleft */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x29 parenright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2A asterisk */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2B plus */ [[0x3001,0x0000,0x0000], [0x2266,0x226A,0x0000]], /* 0x2C comma */ [[0x2642,0x2600,0x2601], [0x00B1,0x002D,0x0000]], /* 0x2D minus */ [[0x3002,0x0000,0x0000], [0x2267,0x226B,0x0000]], /* 0x2E period */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2F slash */ [[0x326A,0x3278,0x0000], [0x2713,0x2611,0x2610]], /* 0x30 0 */ [[0x2195,0x21C5,0x21F3], [0x03BC,0x0000,0x0000]], /* 0x31 1 */ [[0x2194,0x21C4,0x2B04], [0x2109,0x0000,0x0000]], /* 0x32 2 */ [[0xFFE5,0x04B0,0x0000], [0x2103,0x0000,0x0000]], /* 0x33 3 */ [[0xFFE6,0x0000,0x0000], [0xFFE0,0x0000,0x0000]], /* 0x34 4 */ [[0x20AC,0xFFE1,0x0000], [0x2030,0x2031,0x0000]], /* 0x35 5 */ [[0x327E,0x321C,0x327F], [0x2702,0x0000,0x0000]], /* 0x36 6 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x37 7 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x38 8 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x39 9 */ [[0x2084,0x0000,0x0000], [0x2074,0x0000,0x0000]], /* 0x3A colon */ [[0x3265,0x3273,0x0000], [0x2463,0x246D,0x3254,0x325E,0x32B9]], /* 0x3B semicolon */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3C less */ [[0x2640,0x2602,0x2603], [0x2260,0x2245,0x0000]], /* 0x3D equal */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3E greater */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3F question */ [[0x2922,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x40 at */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x41 A */ [[0x0000,0x0000,0x0000], [0xFE40,0xFE3E,0x0000]], /* 0x42 B */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x43 C */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x44 D */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x45 E */ [[0x0000,0x0000,0x0000], [0xFE41,0xFE43,0x0000]], /* 0x46 F */ [[0x0000,0x0000,0x0000], [0xFE42,0xFE44,0x0000]], /* 0x47 G */ [[0x2080,0x0000,0x0000], [0x2070,0x0000,0x0000]], /* 0x48 H */ [[0x2087,0x0000,0x0000], [0x2077,0x0000,0x0000]], /* 0x49 I */ [[0x2081,0x0000,0x0000], [0x00B9,0x0000,0x0000]], /* 0x4A J */ [[0x2082,0x0000,0x0000], [0x00B2,0x33A1,0x0000]], /* 0x4B K */ [[0x2083,0x0000,0x0000], [0x00B3,0x33A5,0x0000]], /* 0x4C L */ [[0x2019,0x201D,0x0000], [0x0000,0x0000,0x0000]], /* 0x4D M */ [[0x2018,0x201C,0x0000], [0x0000,0x0000,0x0000]], /* 0x4E N */ [[0x2088,0x0000,0x0000], [0x2078,0x0000,0x0000]], /* 0x4F O */ [[0x2089,0x0000,0x0000], [0x2079,0x0000,0x0000]], /* 0x50 P */ [[0x2199,0x21B2,0x2B10], [0x2196,0x21B0,0x2B11]], /* 0x51 Q */ [[0x0000,0x0000,0x0000], [0xFE3B,0xFE39,0x0000]], /* 0x52 R */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x53 S */ [[0x0000,0x0000,0x0000], [0xFE3C,0xFE3A,0x0000]], /* 0x54 T */ [[0x2086,0x0000,0x0000], [0x2076,0x0000,0x0000]], /* 0x55 U */ [[0x0000,0x0000,0x0000], [0xFE3F,0xFE3D,0x0000]], /* 0x56 V */ [[0x2198,0x21B3,0x2B0E], [0x2197,0x21B1,0x2B0F]], /* 0x57 W */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x58 X */ [[0x2085,0x0000,0x0000], [0x2075,0x0000,0x0000]], /* 0x59 Y */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5A Z */ [[0x25B3,0x25B2,0x0000], [0x25B7,0x25B6,0x0000]], /* 0x5B bracketleft */ [[0x00B6,0x23CE,0x0000], [0x2252,0x2248,0x0000]], /* 0x5C backslash:*/ [[0x25BD,0x25BC,0x0000], [0x25C1,0x25C0,0x0000]], /* 0x5D bracketright:*/ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5E asciicircum:*/ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5F */ [[0x00A9,0x00AE,0x2117], [0x2122,0x0000,0x0000]], /* 0x60 */ [[0x2026,0x2504,0x0000], [0x25C7,0x25C8,0x25C6]], /* 0x61 a */ [[0x00A7,0x0000,0x0000], [0x3009,0x300B,0x0000]], /* 0x62 b */ [[0x00B0,0x0000,0x0000], [0x260E,0x2668,0x0000]], /* 0x63 c */ [[0x302E,0x0000,0x0000], [0x25CB,0x25C9,0x25CF]], /* 0x64 d */ [[0x00D7,0x2715,0x0000], [0x203B,0x327C,0x327D]], /* 0x65 e */ [[0x00B7,0x2022,0x25E6], [0x300C,0x300E,0x0000]], /* 0x66 f */ [[0x2015,0x00AF,0xFFE3], [0x300D,0x300F,0x0000]], /* 0x67 g */ [[0x3261,0x326F,0x0000], [0x2469,0x2473,0x325A,0x32B5,0x32BF]], /* 0x68 h */ [[0x3264,0x3272,0x0000], [0x2466,0x2470,0x3257,0x32B2,0x32BC]], /* 0x69 i */ [[0x3267,0x3275,0x0000], [0x2460,0x246A,0x3251,0x325B,0x32B6]], /* 0x6A j */ [[0x3260,0x326E,0x0000], [0x2461,0x246B,0x3252,0x325C,0x32B7]], /* 0x6B k */ [[0x3268,0x3276,0x0000], [0x2462,0x246C,0x3253,0x325D,0x32B8]], /* 0x6C l */ [[0x326D,0x327B,0x0000], [0x2234,0x2235,0x2261]], /* 0x6D m */ [[0x3266,0x3274,0x0000], [0x221E,0x221D,0x0000]], /* 0x6E n */ [[0x3269,0x3277,0x0000], [0x2467,0x2471,0x3258,0x32B3,0x32BD]], /* 0x6F o */ [[0x326C,0x327A,0x0000], [0x2468,0x2472,0x3259,0x32B4,0x32BE]], /* 0x70 p */ [[0x2190,0x261C,0x21E6], [0x2193,0x261F,0x21E9]], /* 0x71 q */ [[0x00B4,0x0000,0x0000], [0x3010,0x3014,0x0000]], /* 0x72 r */ [[0x302F,0x0000,0x0000], [0x25A1,0x25A3,0x25A0]], /* 0x73 s */ [[0x3003,0x0000,0x0000], [0x3011,0x3015,0x0000]], /* 0x74 t */ [[0x3262,0x3270,0x0000], [0x2465,0x246F,0x3256,0x32B1,0x32BB]], /* 0x75 u */ [[0x02D0,0x0000,0x0000], [0x3008,0x300A,0x0000]], /* 0x76 v */ [[0x2192,0x261E,0x21E8], [0x2191,0x261D,0x21E7]], /* 0x77 w */ [[0x2032,0x0000,0x0000], [0x2606,0x2605,0x0000]], /* 0x78 x */ [[0x3263,0x3271,0x0000], [0x2464,0x246E,0x3255,0x325F,0x32BA]], /* 0x79 y */ [[0x2033,0x0000,0x0000], [0x2661,0x2665,0x0000]], /* 0x7A z */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7B braceleft */ [[0x00A6,0x2506,0x0000], [0x0000,0x0000,0x0000]], /* 0x7C bar */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7D braceright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]] /* 0x7E asciitilde */ ]; // 3-P3 자판의 기호 확장 배열 K3_P3_extended_sign_layout = [ [[0x2921,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x21 exclam */ [[0x2019,0x201D,0x0000], [0x0000,0x0000,0x0000]], /* 0x22 quotedbl */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x23 numbersign */ [[0x0000,0x0000,0x0000], [0xFE35,0x0000,0x0000]], /* 0x24 dollar */ [[0x0000,0x0000,0x0000], [0xFE36,0x0000,0x0000]], /* 0x25 percent */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x26 ampersand */ [[0x326B,0x3279,0x0000], [0x221E,0x221D,0x0000]], /* 0x27 apostrophe */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x28 parenleft */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x29 parenright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2A asterisk */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2B plus */ [[0x3001,0x0000,0x0000], [0x2461,0x246B,0x3252,0x325C,0x32B7]], /* 0x2C comma */ [[0x2642,0x2600,0x2601], [0x00B1,0x002D,0x0000]], /* 0x2D minus */ [[0x3002,0x0000,0x0000], [0x2462,0x246C,0x3253,0x325D,0x32B8]], /* 0x2E period */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x2F slash */ [[0x326A,0x3278,0x0000], [0x2713,0x2611,0x2610]], /* 0x30 0 */ [[0x2195,0x21C5,0x21F3], [0x03BC,0x0000,0x0000]], /* 0x31 1 */ [[0x2194,0x21C4,0x2B04], [0x2109,0x0000,0x0000]], /* 0x32 2 */ [[0xFFE5,0x04B0,0x0000], [0x2103,0x0000,0x0000]], /* 0x33 3 */ [[0xFFE6,0x0000,0x0000], [0xFFE0,0x0000,0x0000]], /* 0x34 4 */ [[0x20AC,0xFFE1,0x0000], [0x2030,0x2031,0x0000]], /* 0x35 5 */ [[0x327E,0x321C,0x327F], [0x2702,0x0000,0x0000]], /* 0x36 6 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x37 7 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x38 8 */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x39 9 */ [[0x266A,0x266C,0x0000], [0x0000,0x0000,0x0000]], /* 0x3A colon */ [[0x3265,0x3273,0x0000], [0x2234,0x2235,0x2261]], /* 0x3B semicolon */ [[0x2082,0x0000,0x0000], [0x00B2,0x33A1,0x0000]], /* 0x3C less */ [[0x2640,0x2602,0x2603], [0x2260,0x2245,0x0000]], /* 0x3D equal */ [[0x2083,0x0000,0x0000], [0x00B3,0x33A5,0x0000]], /* 0x3E greater */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x3F question */ [[0x2922,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x40 at */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x41 A */ [[0x2267,0x226B,0x0000], [0xFE40,0xFE3E,0x0000]], /* 0x42 B */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x43 C */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x44 D */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x45 E */ [[0x0000,0x0000,0x0000], [0xFE41,0xFE43,0x0000]], /* 0x46 F */ [[0x2266,0x226A,0x0000], [0xFE42,0xFE44,0x0000]], /* 0x47 G */ [[0x2018,0x201C,0x0000], [0x0000,0x0000,0x0000]], /* 0x48 H */ [[0x2088,0x0000,0x0000], [0x2078,0x0000,0x0000]], /* 0x49 I */ [[0x2084,0x0000,0x0000], [0x2074,0x0000,0x0000]], /* 0x4A J */ [[0x2085,0x0000,0x0000], [0x2075,0x0000,0x0000]], /* 0x4B K */ [[0x2086,0x0000,0x0000], [0x2076,0x0000,0x0000]], /* 0x4C L */ [[0x2081,0x0000,0x0000], [0x00B9,0x0000,0x0000]], /* 0x4D M */ [[0x2080,0x0000,0x0000], [0x2070,0x0000,0x0000]], /* 0x4E N */ [[0x2089,0x0000,0x0000], [0x2079,0x0000,0x0000]], /* 0x4F O */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x50 P */ [[0x2199,0x21B2,0x2B10], [0x2196,0x21B0,0x2B11]], /* 0x51 Q */ [[0x0000,0x0000,0x0000], [0xFE3B,0xFE39,0x0000]], /* 0x52 R */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x53 S */ [[0x0000,0x0000,0x0000], [0xFE3C,0xFE3A,0x0000]], /* 0x54 T */ [[0x2087,0x0000,0x0000], [0x2077,0x0000,0x0000]], /* 0x55 U */ [[0x0000,0x0000,0x0000], [0xFE3F,0xFE3D,0x0000]], /* 0x56 V */ [[0x2198,0x21B3,0x2B0E], [0x2197,0x21B1,0x2B0F]], /* 0x57 W */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x58 X */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x59 Y */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5A Z */ [[0x25B3,0x25B2,0x0000], [0x25B7,0x25B6,0x0000]], /* 0x5B bracketleft */ [[0x00B6,0x23CE,0x0000], [0x2252,0x2248,0x0000]], /* 0x5C backslash:*/ [[0x25BD,0x25BC,0x0000], [0x25C1,0x25C0,0x0000]], /* 0x5D bracketright:*/ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5E asciicircum:*/ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x5F */ [[0x00A9,0x00AE,0x2117], [0x2122,0x0000,0x0000]], /* 0x60 */ [[0x2026,0x2504,0x0000], [0x25C7,0x25C8,0x25C6]], /* 0x61 a */ [[0x00A7,0x0000,0x0000], [0x3009,0x300B,0x0000]], /* 0x62 b */ [[0x00B0,0x0000,0x0000], [0x260E,0x2668,0x0000]], /* 0x63 c */ [[0x302E,0x0000,0x0000], [0x25CB,0x25C9,0x25CF]], /* 0x64 d */ [[0x00D7,0x2715,0x0000], [0x203B,0x327C,0x327D]], /* 0x65 e */ [[0x00B7,0x2022,0x25E6], [0x300C,0x300E,0x0000]], /* 0x66 f */ [[0x2015,0x00AF,0xFFE3], [0x300D,0x300F,0x0000]], /* 0x67 g */ [[0x3261,0x326F,0x0000], [0x0000,0x0000,0x0000]], /* 0x68 h */ [[0x3264,0x3272,0x0000], [0x2467,0x2471,0x3258,0x32B3,0x32BD]], /* 0x69 i */ [[0x3267,0x3275,0x0000], [0x2463,0x246D,0x3254,0x325E,0x32B9]], /* 0x6A j */ [[0x3260,0x326E,0x0000], [0x2464,0x246E,0x3255,0x325F,0x32BA]], /* 0x6B k */ [[0x3268,0x3276,0x0000], [0x2465,0x246F,0x3256,0x32B1,0x32BB]], /* 0x6C l */ [[0x326D,0x327B,0x0000], [0x2460,0x246A,0x3251,0x325B,0x32B6]], /* 0x6D m */ [[0x3266,0x3274,0x0000], [0x2469,0x2473,0x325A,0x32B5,0x32BF]], /* 0x6E n */ [[0x3269,0x3277,0x0000], [0x2468,0x2472,0x3259,0x32B4,0x32BE]], /* 0x6F o */ [[0x326C,0x327A,0x0000], [0x0000,0x0000,0x0000]], /* 0x70 p */ [[0x2190,0x261C,0x21E6], [0x2193,0x261F,0x21E9]], /* 0x71 q */ [[0x00B4,0x0000,0x0000], [0x3010,0x3014,0x0000]], /* 0x72 r */ [[0x302F,0x0000,0x0000], [0x25A1,0x25A3,0x25A0]], /* 0x73 s */ [[0x3003,0x0000,0x0000], [0x3011,0x3015,0x0000]], /* 0x74 t */ [[0x3262,0x3270,0x0000], [0x2466,0x2470,0x3257,0x32B2,0x32BC]], /* 0x75 u */ [[0x02D0,0x0000,0x0000], [0x3008,0x300A,0x0000]], /* 0x76 v */ [[0x2192,0x261E,0x21E8], [0x2191,0x261D,0x21E7]], /* 0x77 w */ [[0x2032,0x0000,0x0000], [0x2606,0x2605,0x0000]], /* 0x78 x */ [[0x3263,0x3271,0x0000], [0x00F7,0x0000,0x0000]], /* 0x79 y */ [[0x2033,0x0000,0x0000], [0x2661,0x2665,0x0000]], /* 0x7A z */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7B braceleft */ [[0x00A6,0x2506,0x0000], [0x0000,0x0000,0x0000]], /* 0x7C bar */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]], /* 0x7D braceright */ [[0x0000,0x0000,0x0000], [0x0000,0x0000,0x0000]] /* 0x7E asciitilde */ ]; K3_Anmatae_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotation mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x002E, /* 0x27 apostrophe: period */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x11B7, /* 0x2C comma: jongseong mieum */ 0x002D, /* 0x2D minus: minus sign */ 0x11AF, /* 0x2E period: jongseong lieul */ 0x11C2, /* 0x2F slash: jongseong hieuh */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x116E, /* 0x3B semicolon: jungseong u */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: equals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x11F9, /* 0x3F question: jongseong yeolin_hieuh */ 0x0040, /* 0x40 at: commercial at */ 0x1107, /* 0x41 A: choseong bieub */ 0x11f0, /* 0x42 B: jongseong yesieung */ 0x11B8, /* 0x43 C: jongseong bieub */ 0x1103, /* 0x44 D: choseong dieud */ 0x1102, /* 0x45 E: choseong nieun */ 0x1100, /* 0x46 F: choseong gieug */ 0x114C, /* 0x47 G: choseong yesieung */ 0x1165, /* 0x48 H: jungseong eo */ 0x002F, /* 0x49 I: slash */ 0x119E, /* 0x4A J: jungseong alae_a */ 0x1175, /* 0x4B K: jungseong i */ 0x1169, /* 0x4C L: jungseong o */ 0x11AB, /* 0x4D M: jongseong nienu */ 0x11EB, /* 0x4E N: jongseung pansieus */ 0x005B, /* 0x4F O: left bracket */ 0x005D, /* 0x50 P: right bracket */ 0x1106, /* 0x51 Q: choseong mieum */ 0x1105, /* 0x52 R: choseong lieul */ 0x110C, /* 0x53 S: choseong jieuj */ 0x1159, /* 0x54 T: choseong yeolin_hieuh */ 0x0027, /* 0x55 U: apostrophe */ 0x11A8, /* 0x56 V: jongseong gieug */ 0x1140, /* 0x57 W: choseong pansieus */ 0x11AE, /* 0x58 X: jongseong dieud */ 0x003B, /* 0x59 Y: semicolon */ 0x11BD, /* 0x5A Z: jongseong jieuj */ 0x002C, /* 0x5B bracketleft: comma */ 0x005C, /* 0x5C backslash: backslash */ 0x003F, /* 0x5D bracketright: qustion mark */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x1107, /* 0x61 a: choseong bieub */ 0x11BC, /* 0x62 b: jongseong ieung */ 0x11B8, /* 0x63 c: jongseong bieub */ 0x1103, /* 0x64 d: choseong dieud */ 0x1102, /* 0x65 e: choseong nieun */ 0x1100, /* 0x66 f: choseong gieug */ 0x110B, /* 0x67 g: choseong ieung */ 0x1165, /* 0x68 h: jungseong eo */ 0x1173, /* 0x69 i: jungseong eu */ 0x1161, /* 0x6A j: jungseong a */ 0x1175, /* 0x6B k: jungseong i */ 0x1169, /* 0x6C l: jungseong o */ 0x11AB, /* 0x6D m: jongseong nieun */ 0x11BA, /* 0x6E n: jongseong sieus */ 0x116D, /* 0x6F o: jungseong yo */ 0x1172, /* 0x70 p: jungseong yu */ 0x1106, /* 0x71 q: choseong mieum */ 0x1105, /* 0x72 r: choseong lieul */ 0x110C, /* 0x73 s: choseong jieuj */ 0x1112, /* 0x74 t: choseong hieuh */ 0x1163, /* 0x75 u: jungseong ya */ 0x11A8, /* 0x76 v: jongseong kiyok */ 0x1109, /* 0x77 w: choseong sieus */ 0x11AE, /* 0x78 x: jongseong dieud */ 0x1167, /* 0x79 y: jungseong yeo */ 0x11BD, /* 0x7A z: jongseong jieuj */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical bar */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; K3_Semoe_2014_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x1169, /* 0x27 apostrophe: jungseong o */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x11B9, /* 0x31 1: jongseong bieub-sieus */ 0x11AE, /* 0x32 2: jongseong dieud */ 0x11B8, /* 0x33 3: jongseong bieub */ 0x116D, /* 0x34 4: jungseong yo */ 0x1163, /* 0x35 5: jungseong ya */ 0x119E, /* 0x36 6: jungseong alae_a */ 0x11A2, /* 0x37 7: jungseong ssang_alae_a */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x0034, /* 0x3A colon: 4 */ 0x11BB, /* 0x3B semicolon: jongseong ssang_sieus */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: euals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x2606, /* 0x41 A: ☆ white star */ 0x003B, /* 0x42 B: semicolon */ 0x300C, /* 0x43 C: 「 left corner bracket */ 0x25B2, /* 0x44 D: ▲ black up-pointing triangle */ 0x2192, /* 0x45 E: → rightwards arrow */ 0x00B7, /* 0x46 F: middle dot */ 0x003A, /* 0x47 G: colon */ 0x0030, /* 0x48 H: 0 */ 0x0037, /* 0x49 I: 7 */ 0x0031, /* 0x4A J: 1 */ 0x0032, /* 0x4B K: 2 */ 0x0033, /* 0x4C L: 3 */ 0x0022, /* 0x4D M: quotatioin mark */ 0x0027, /* 0x4E N: apostrophe */ 0x0038, /* 0x4F O: 8 */ 0x0039, /* 0x50 P: 9 */ 0x2661, /* 0x51 Q: ♡ white heart suit */ 0x2194, /* 0x52 R: ↔ left right arrow */ 0x25BD, /* 0x53 S: ▽ white down-pointing triangle */ 0x203B, /* 0x54 T: ※ reference mark */ 0x0036, /* 0x55 U: 6 */ 0x300D, /* 0x56 V: 」 right corner bracket */ 0x2190, /* 0x57 W: ← leftwards arrow */ 0x25CE, /* 0x58 X: ◎ bullseye */ 0x0035, /* 0x59 Y: 5 */ 0x25A1, /* 0x5A Z: □ white square */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x1166, /* 0x62 b: jungseong e */ 0x116E, /* 0x63 c: jungseong u */ 0x1175, /* 0x64 d: jungseong i */ 0x11AF, /* 0x65 e: jongseong lieul */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1102, /* 0x68 h: choseong nieun */ 0x1106, /* 0x69 i: choseong mieum */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1112, /* 0x6D m: choseong hieuh */ 0x1109, /* 0x6E n: choseong sieus */ 0x1107, /* 0x6F o: choseong bieub */ 0x116E, /* 0x70 p: jungseong u */ 0x11C2, /* 0x71 q: jongseong hieuh */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1167, /* 0x74 t: jungseong yeo */ 0x1103, /* 0x75 u: choseong dieud */ 0x1169, /* 0x76 v: jungseong o */ 0x11BA, /* 0x77 w: jongseong sieus */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1105, /* 0x79 y: choseong lieul */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; K3_Semoe_2014_sublayout = [ 0x0000, /* 0x21 exclam: */ 0x0000, /* 0x22 quotedbl */ 0x0000, /* 0x23 numbersign */ 0x0000, /* 0x24 dollar */ 0x0000, /* 0x25 percent */ 0x0000, /* 0x26 ampersand */ 0x0000, /* 0x27 apostrophe */ 0x0000, /* 0x28 parenleft */ 0x0000, /* 0x29 parenright */ 0x0000, /* 0x2A asterisk */ 0x0000, /* 0x2B plus */ 0x0000, /* 0x2C comma */ 0x0000, /* 0x2D minus */ 0x0000, /* 0x2E period */ 0x0000, /* 0x2F slash */ 0x0000, /* 0x30 0 */ 0x11B3, /* 0x31 1: jongseong lieul-sieus */ 0x11C0, /* 0x32 2: jongseong tieut */ 0x11C1, /* 0x33 3: jongseong pieup */ 0x0000, /* 0x34 4 */ 0x0000, /* 0x35 5 */ 0x0000, /* 0x36 6 */ 0x0000, /* 0x37 7 */ 0x0000, /* 0x38 8 */ 0x0000, /* 0x39 9 */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x0000, /* 0x40 at */ 0x0000, /* 0x41 A */ 0x0000, /* 0x42 B */ 0x0000, /* 0x43 C */ 0x0000, /* 0x44 D */ 0x0000, /* 0x45 E */ 0x0000, /* 0x46 F */ 0x0000, /* 0x47 G */ 0x0000, /* 0x48 H */ 0x0000, /* 0x49 I */ 0x0000, /* 0x4A J */ 0x0000, /* 0x4B K */ 0x0000, /* 0x4C L */ 0x0000, /* 0x4D M */ 0x0000, /* 0x4E N */ 0x0000, /* 0x4F O */ 0x0000, /* 0x50 P */ 0x0000, /* 0x51 Q */ 0x0000, /* 0x52 R */ 0x0000, /* 0x53 S */ 0x0000, /* 0x54 T */ 0x0000, /* 0x55 U */ 0x0000, /* 0x56 V */ 0x0000, /* 0x57 W */ 0x0000, /* 0x58 X */ 0x0000, /* 0x59 Y */ 0x0000, /* 0x5A Z */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x11B8, /* 0x61 a: jongseong bieub */ 0x0000, /* 0x62 b */ 0x0000, /* 0x63 c */ 0x0000, /* 0x64 d */ 0x11BD, /* 0x65 e: jongseong jieuj */ 0x0000, /* 0x66 f */ 0x0000, /* 0x67 g */ 0x0000, /* 0x68 h */ 0x0000, /* 0x69 i */ 0x0000, /* 0x6A j */ 0x0000, /* 0x6B k */ 0x0000, /* 0x6C l */ 0x0000, /* 0x6D m */ 0x0000, /* 0x6E n */ 0x0000, /* 0x6F o */ 0x0000, /* 0x70 p */ 0x11B6, /* 0x71 q: jongseong lieul-hieuh */ 0x0000, /* 0x72 r */ 0x11AD, /* 0x73 s: jongseong nieun-hieuh */ 0x0000, /* 0x74 t */ 0x0000, /* 0x75 u */ 0x0000, /* 0x76 v */ 0x11BE, /* 0x77 w: jongseong chieuch */ 0x11BF, /* 0x78 x: jongseong kieuk */ 0x0000, /* 0x79 y */ 0x11AE, /* 0x7A z: jongseong dieud */ 0x0000, /* 0x7B braceleft */ 0x0000, /* 0x7C bar */ 0x0000, /* 0x7D braceright */ 0x0000 /* 0x7E asciitilde */ ]; K3_Semoe_2015_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x116E, /* 0x27 apostrophe: jungseong u */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x11BB, /* 0x3B semicolon: jongseong ssang_sieus */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: euals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x2190, /* 0x41 A: ← leftwards arrow */ 0x0027, /* 0x42 B: apostrophe */ 0x300C, /* 0x43 C: 「 left corner bracket */ 0x2192, /* 0x44 D: → rightwards arrow */ 0x2661, /* 0x45 E: ♡ white heart suit */ 0x25CB, /* 0x46 F: ○ */ 0x00D7, /* 0x47 G: × */ 0x3008, /* 0x48 H: 〈 */ 0x2026, /* 0x49 I: … */ 0x3009, /* 0x4A J: 〉 */ 0x00B7, /* 0x4B K: · */ 0x003B, /* 0x4C L: colon */ 0x300F, /* 0x4D M: 』 */ 0x300E, /* 0x4E N: 『 */ 0x25B3, /* 0x4F O: △ white up-pointing triangle */ 0x25BD, /* 0x50 P: ▽ white down-pointing triangle */ 0x2194, /* 0x51 Q: ↔ left right arrow */ 0x2606, /* 0x52 R: ☆ white star */ 0x2193, /* 0x53 S: ↓ */ 0x203B, /* 0x54 T: ※ reference mark */ 0x300B, /* 0x55 U: 》 */ 0x300D, /* 0x56 V: 」 right corner bracket */ 0x2191, /* 0x57 W: ↑ */ 0x25CE, /* 0x58 X: ◎ bullseye */ 0x300A, /* 0x59 Y: 《 */ 0x25A1, /* 0x5A Z: □ white square */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x11AF, /* 0x65 e: jongseong lieul */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1109, /* 0x68 h: choseong sieus */ 0x1103, /* 0x69 i: choseong dieud */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1105, /* 0x6D m: choseong lieul */ 0x1112, /* 0x6E n: choseong hieuh */ 0x1107, /* 0x6F o: choseong pieup */ 0x1169, /* 0x70 p: jungseong o */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1167, /* 0x74 t: jungseong yeo */ 0x1102, /* 0x75 u: choseong nieun */ 0x1169, /* 0x76 v: jungseong o */ 0x11B8, /* 0x77 w: jongseong pieup */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1106, /* 0x79 y: choseong mieum */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; K3_Semoe_2015_sublayout = [ 0x0000, /* 0x21 exclam */ 0x0000, /* 0x22 quotedbl */ 0x0000, /* 0x23 numbersign */ 0x0000, /* 0x24 dollar */ 0x0000, /* 0x25 percent */ 0x0000, /* 0x26 ampersand */ 0x0000, /* 0x27 apostrophe */ 0x0000, /* 0x28 parenleft */ 0x0000, /* 0x29 parenright */ 0x0000, /* 0x2A asterisk */ 0x0000, /* 0x2B plus */ 0x0000, /* 0x2C comma */ 0x0000, /* 0x2D minus */ 0x0000, /* 0x2E period */ 0x0000, /* 0x2F slash */ 0x0000, /* 0x30 0 */ 0x0000, /* 0x31 1 */ 0x0000, /* 0x32 2 */ 0x0000, /* 0x33 3 */ 0x0000, /* 0x34 4 */ 0x0000, /* 0x35 5 */ 0x0000, /* 0x36 6 */ 0x0000, /* 0x37 7 */ 0x0000, /* 0x38 8 */ 0x0000, /* 0x39 9 */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x0000, /* 0x40 at */ 0x0000, /* 0x41 A */ 0x0000, /* 0x42 B */ 0x0000, /* 0x43 C */ 0x0000, /* 0x44 D */ 0x0000, /* 0x45 E */ 0x0000, /* 0x46 F */ 0x0000, /* 0x47 G */ 0x0000, /* 0x48 H */ 0x0000, /* 0x49 I */ 0x0000, /* 0x4A J */ 0x0000, /* 0x4B K */ 0x0000, /* 0x4C L */ 0x0000, /* 0x4D M */ 0x0000, /* 0x4E N */ 0x0000, /* 0x4F O */ 0x0000, /* 0x50 P */ 0x0000, /* 0x51 Q */ 0x0000, /* 0x52 R */ 0x0000, /* 0x53 S */ 0x0000, /* 0x54 T */ 0x0000, /* 0x55 U */ 0x0000, /* 0x56 V */ 0x0000, /* 0x57 W */ 0x0000, /* 0x58 X */ 0x0000, /* 0x59 Y */ 0x0000, /* 0x5A Z */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x11B6, /* 0x61 a: jongseong lieul-hieuh */ 0x1172, /* 0x62 b: jungseong yu */ 0x1168, /* 0x63 c: jungseong ye */ 0x0000, /* 0x64 d */ 0x11BD, /* 0x65 e: jongseong jieuj */ 0x0000, /* 0x66 f */ 0x0000, /* 0x67 g */ 0x0000, /* 0x68 h */ 0x0000, /* 0x69 i */ 0x0000, /* 0x6A j */ 0x0000, /* 0x6B k */ 0x0000, /* 0x6C l */ 0x0000, /* 0x6D m */ 0x0000, /* 0x6E n */ 0x0000, /* 0x6F o */ 0x0000, /* 0x70 p */ 0x11BE, /* 0x71 q: jongseong chieuch */ 0x1163, /* 0x72 r: jongseong ya */ 0x11C2, /* 0x73 s: jongseong hieuh */ 0x1164, /* 0x74 t: jungseong yae */ 0x0000, /* 0x75 u */ 0x116D, /* 0x76 v: jungseong yo */ 0x11C1, /* 0x77 w: jongseong pieup */ 0x11BF, /* 0x78 x: jongseong kieuk */ 0x0000, /* 0x79 y */ 0x11AE, /* 0x7A z: jongseong dieud */ 0x0000, /* 0x7B braceleft */ 0x0000, /* 0x7C bar */ 0x0000, /* 0x7D braceright */ 0x0000 /* 0x7E asciitilde */ ]; K3_Semoe_2016_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x005B, /* 0x27 apostrophe: left bracket */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002C, /* 0x2C comma: comma */ 0x002D, /* 0x2D minus: minus sign */ 0x002E, /* 0x2E period: period */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x11BB, /* 0x3B semicolon: jongseong ssang_sieus */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: euals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x2190, /* 0x41 A: ← leftwards arrow */ 0x00B0, /* 0x42 B: ° */ 0x300C, /* 0x43 C: 「 left corner bracket */ 0x2192, /* 0x44 D: → rightwards arrow */ 0x2661, /* 0x45 E: ♡ white heart suit */ 0x25CB, /* 0x46 F: ○ */ 0x00D7, /* 0x47 G: × */ 0x00B7, /* 0x48 H: · */ 0x2015, /* 0x49 I: ― */ -100, /* 0x4A J: */ 0x0027, /* 0x4B K: apostrophe */ 0x003B, /* 0x4C L: colon */ 0x300F, /* 0x4D M: 』 */ 0x300E, /* 0x4E N: 『 */ 0x25B3, /* 0x4F O: △ white up-pointing triangle */ 0x25BD, /* 0x50 P: ▽ white down-pointing triangle */ 0x2194, /* 0x51 Q: ↔ left right arrow */ 0x2606, /* 0x52 R: ☆ white star */ 0x2193, /* 0x53 S: ↓ */ 0x203B, /* 0x54 T: ※ reference mark */ 0x3009, /* 0x55 U: 〉 */ 0x300D, /* 0x56 V: 」 right corner bracket */ 0x2191, /* 0x57 W: ↑ */ 0x25CE, /* 0x58 X: ◎ bullseye */ 0x3008, /* 0x59 Y: 〈 */ 0x25A1, /* 0x5A Z: □ white square */ 0x1169, /* 0x5B bracketleft: jungseong o */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x11AF, /* 0x65 e: jongseong lieul */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1112, /* 0x68 h: choseong hieuh */ 0x1103, /* 0x69 i: choseong dieud */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1105, /* 0x6D m: choseong lieul */ 0x1109, /* 0x6E n: choseong sieus */ 0x1107, /* 0x6F o: choseong pieup */ 0x116E, /* 0x70 p: jungseong u */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1167, /* 0x74 t: jungseong yeo */ 0x1102, /* 0x75 u: choseong nieun */ 0x1169, /* 0x76 v: jungseong o */ 0x11B8, /* 0x77 w: jongseong pieup */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1106, /* 0x79 y: choseong mieum */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; K3_Semoe_2016_sublayout = [ 0x0000, /* 0x21 exclam */ 0x0000, /* 0x22 quotedbl */ 0x0000, /* 0x23 numbersign */ 0x0000, /* 0x24 dollar */ 0x0000, /* 0x25 percent */ 0x0000, /* 0x26 ampersand */ 0x0000, /* 0x27 apostrophe */ 0x0000, /* 0x28 parenleft */ 0x0000, /* 0x29 parenright */ 0x0000, /* 0x2A asterisk */ 0x0000, /* 0x2B plus */ 0x0000, /* 0x2C comma */ 0x0000, /* 0x2D minus */ 0x0000, /* 0x2E period */ 0x0000, /* 0x2F slash */ 0x0000, /* 0x30 0 */ 0x0000, /* 0x31 1 */ 0x0000, /* 0x32 2 */ 0x0000, /* 0x33 3 */ 0x0000, /* 0x34 4 */ 0x0000, /* 0x35 5 */ 0x0000, /* 0x36 6 */ 0x0000, /* 0x37 7 */ 0x0000, /* 0x38 8 */ 0x0000, /* 0x39 9 */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x0000, /* 0x40 at */ 0x0000, /* 0x41 A */ 0x0000, /* 0x42 B */ 0x0000, /* 0x43 C */ 0x0000, /* 0x44 D */ 0x0000, /* 0x45 E */ 0x0000, /* 0x46 F */ 0x0000, /* 0x47 G */ 0x0000, /* 0x48 H */ 0x0000, /* 0x49 I */ 0x0000, /* 0x4A J */ 0x0000, /* 0x4B K */ 0x0000, /* 0x4C L */ 0x0000, /* 0x4D M */ 0x0000, /* 0x4E N */ 0x0000, /* 0x4F O */ 0x0000, /* 0x50 P */ 0x0000, /* 0x51 Q */ 0x0000, /* 0x52 R */ 0x0000, /* 0x53 S */ 0x0000, /* 0x54 T */ 0x0000, /* 0x55 U */ 0x0000, /* 0x56 V */ 0x0000, /* 0x57 W */ 0x0000, /* 0x58 X */ 0x0000, /* 0x59 Y */ 0x0000, /* 0x5A Z */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x11B6, /* 0x61 a: jongseong lieul-hieuh */ 0x1172, /* 0x62 b: jungseong yu */ 0x1168, /* 0x63 c: jungseong ye */ 0x0000, /* 0x64 d */ 0x11BD, /* 0x65 e: jongseong jieuj */ 0x0000, /* 0x66 f */ 0x0000, /* 0x67 g */ 0x0000, /* 0x68 h */ 0x0000, /* 0x69 i */ 0x0000, /* 0x6A j */ 0x0000, /* 0x6B k */ 0x0000, /* 0x6C l */ 0x0000, /* 0x6D m */ 0x0000, /* 0x6E n */ 0x0000, /* 0x6F o */ 0x0000, /* 0x70 p */ 0x11BE, /* 0x71 q: jongseong chieuch */ 0x1163, /* 0x72 r: jongseong ya */ 0x11C2, /* 0x73 s: jongseong hieuh */ 0x1164, /* 0x74 t: jungseong yae */ 0x0000, /* 0x75 u */ 0x116D, /* 0x76 v: jungseong yo */ 0x11C1, /* 0x77 w: jongseong pieup */ 0x11BF, /* 0x78 x: jongseong kieuk */ 0x0000, /* 0x79 y */ 0x11AE, /* 0x7A z: jongseong dieud */ 0x0000, /* 0x7B braceleft */ 0x0000, /* 0x7C bar */ 0x0000, /* 0x7D braceright */ 0x0000 /* 0x7E asciitilde */ ]; K3_Semoe_2017_layout = [ 0x0021, /* 0x21 exclam: exclamation mark */ 0x0022, /* 0x22 quotedbl: quotatioin mark */ 0x0023, /* 0x23 numbersign: number sign */ 0x0024, /* 0x24 dollar: dollar sign */ 0x0025, /* 0x25 percent: percent sign */ 0x0026, /* 0x26 ampersand: ampersand */ 0x002C, /* 0x27 apostrophe: comma */ 0x0028, /* 0x28 parenleft: left parenthesis */ 0x0029, /* 0x29 parenright: right parenthesis */ 0x002A, /* 0x2A asterisk: asterisk */ 0x002B, /* 0x2B plus: plus sign */ 0x002E, /* 0x2C comma: period */ 0x002D, /* 0x2D minus: minus sign */ 0x1169, /* 0x2E period: jungseong o */ 0x002F, /* 0x2F slash: slash */ 0x0030, /* 0x30 0: 0 */ 0x0031, /* 0x31 1: 1 */ 0x0032, /* 0x32 2: 2 */ 0x0033, /* 0x33 3: 3 */ 0x0034, /* 0x34 4: 4 */ 0x0035, /* 0x35 5: 5 */ 0x0036, /* 0x36 6: 6 */ 0x0037, /* 0x37 7: 7 */ 0x0038, /* 0x38 8: 8 */ 0x0039, /* 0x39 9: 9 */ 0x003A, /* 0x3A colon: colon */ 0x11BB, /* 0x3B semicolon: jongseong ssang_sieus */ 0x003C, /* 0x3C less: less-than sign */ 0x003D, /* 0x3D equal: euals sign */ 0x003E, /* 0x3E greater: greater-than sign */ 0x003F, /* 0x3F question: question mark */ 0x0040, /* 0x40 at: commertial at */ 0x2190, /* 0x41 A: ← leftwards arrow */ 0x00B0, /* 0x42 B: ° */ 0x300C, /* 0x43 C: 「 left corner bracket */ 0x2192, /* 0x44 D: → rightwards arrow */ 0x2661, /* 0x45 E: ♡ white heart suit */ 0x25CB, /* 0x46 F: ○ */ 0x00D7, /* 0x47 G: × */ 0x00B7, /* 0x48 H: · */ 0x2015, /* 0x49 I: ― */ 0x0000, /* 0x4A J: */ 0x0027, /* 0x4B K: apostrophe */ 0x003B, /* 0x4C L: colon */ 0x300F, /* 0x4D M: 』 */ 0x300E, /* 0x4E N: 『 */ 0x25B3, /* 0x4F O: △ white up-pointing triangle */ 0x25BD, /* 0x50 P: ▽ white down-pointing triangle */ 0x2194, /* 0x51 Q: ↔ left right arrow */ 0x2606, /* 0x52 R: ☆ white star */ 0x2193, /* 0x53 S: ↓ */ 0x203B, /* 0x54 T: ※ reference mark */ 0x3009, /* 0x55 U: 〉 */ 0x300D, /* 0x56 V: 」 right corner bracket */ 0x2191, /* 0x57 W: ↑ */ 0x25CE, /* 0x58 X: ◎ bullseye */ 0x3008, /* 0x59 Y: 〈 */ 0x25A1, /* 0x5A Z: □ white square */ 0x005B, /* 0x5B bracketleft: left bracket */ 0x005C, /* 0x5C backslash: backslash */ 0x005D, /* 0x5D bracketright: right bracket */ 0x005E, /* 0x5E asciicircum: circumflex accent */ 0x005F, /* 0x5F underscore: underscore */ 0x0060, /* 0x60 quoteleft: grave accent */ 0x11BC, /* 0x61 a: jongseong ieung */ 0x116E, /* 0x62 b: jungseong u */ 0x1166, /* 0x63 c: jungseong e */ 0x1175, /* 0x64 d: jungseong i */ 0x11AF, /* 0x65 e: jongseong lieul */ 0x1161, /* 0x66 f: jungseong a */ 0x1173, /* 0x67 g: jungseong eu */ 0x1112, /* 0x68 h: choseong hieuh */ 0x1103, /* 0x69 i: choseong dieud */ 0x110B, /* 0x6A j: choseong ieung */ 0x1100, /* 0x6B k: choseong gieug */ 0x110C, /* 0x6C l: choseong jieuj */ 0x1105, /* 0x6D m: choseong lieul */ 0x1109, /* 0x6E n: choseong sieus */ 0x1107, /* 0x6F o: choseong pieup */ 0x116E, /* 0x70 p: jungseong u */ 0x11BA, /* 0x71 q: jongseong sieus */ 0x1165, /* 0x72 r: jungseong eo */ 0x11AB, /* 0x73 s: jongseong nieun */ 0x1167, /* 0x74 t: jungseong yeo */ 0x1102, /* 0x75 u: choseong nieun */ 0x1169, /* 0x76 v: jungseong o */ 0x11B8, /* 0x77 w: jongseong pieup */ 0x11A8, /* 0x78 x: jongseong gieug */ 0x1106, /* 0x79 y: choseong mieum */ 0x11B7, /* 0x7A z: jongseong mieum */ 0x007B, /* 0x7B braceleft: left brace */ 0x007C, /* 0x7C bar: vertical line(bar) */ 0x007D, /* 0x7D braceright: right brace */ 0x007E /* 0x7E asciitilde: tilde */ ]; K3_Semoe_2017_sublayout = [ 0x0000, /* 0x21 exclam */ 0x0000, /* 0x22 quotedbl */ 0x0000, /* 0x23 numbersign */ 0x0000, /* 0x24 dollar */ 0x0000, /* 0x25 percent */ 0x0000, /* 0x26 ampersand */ 0x0000, /* 0x27 apostrophe */ 0x0000, /* 0x28 parenleft */ 0x0000, /* 0x29 parenright */ 0x0000, /* 0x2A asterisk */ 0x0000, /* 0x2B plus */ 0x0000, /* 0x2C comma */ 0x0000, /* 0x2D minus */ 0x0000, /* 0x2E period */ 0x0000, /* 0x2F slash */ 0x0000, /* 0x30 0 */ 0x0000, /* 0x31 1 */ 0x0000, /* 0x32 2 */ 0x0000, /* 0x33 3 */ 0x0000, /* 0x34 4 */ 0x0000, /* 0x35 5 */ 0x0000, /* 0x36 6 */ 0x0000, /* 0x37 7 */ 0x0000, /* 0x38 8 */ 0x0000, /* 0x39 9 */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x0000, /* 0x40 at */ 0x0000, /* 0x41 A */ 0x0000, /* 0x42 B */ 0x0000, /* 0x43 C */ 0x0000, /* 0x44 D */ 0x0000, /* 0x45 E */ 0x0000, /* 0x46 F */ 0x0000, /* 0x47 G */ 0x0000, /* 0x48 H */ 0x0000, /* 0x49 I */ 0x0000, /* 0x4A J */ 0x0000, /* 0x4B K */ 0x0000, /* 0x4C L */ 0x0000, /* 0x4D M */ 0x0000, /* 0x4E N */ 0x0000, /* 0x4F O */ 0x0000, /* 0x50 P */ 0x0000, /* 0x51 Q */ 0x0000, /* 0x52 R */ 0x0000, /* 0x53 S */ 0x0000, /* 0x54 T */ 0x0000, /* 0x55 U */ 0x0000, /* 0x56 V */ 0x0000, /* 0x57 W */ 0x0000, /* 0x58 X */ 0x0000, /* 0x59 Y */ 0x0000, /* 0x5A Z */ 0x0000, /* 0x3A colon */ 0x0000, /* 0x3B semicolon */ 0x0000, /* 0x3C less */ 0x0000, /* 0x3D equal */ 0x0000, /* 0x3E greater */ 0x0000, /* 0x3F question */ 0x11B6, /* 0x61 a: jongseong lieul-hieuh */ 0x1172, /* 0x62 b: jungseong yu */ 0x1168, /* 0x63 c: jungseong ye */ 0x0000, /* 0x64 d */ 0x11BD, /* 0x65 e: jongseong jieuj */ 0x0000, /* 0x66 f */ 0x0000, /* 0x67 g */ 0x0000, /* 0x68 h */ 0x0000, /* 0x69 i */ 0x0000, /* 0x6A j */ 0x0000, /* 0x6B k */ 0x0000, /* 0x6C l */ 0x0000, /* 0x6D m */ 0x0000, /* 0x6E n */ 0x0000, /* 0x6F o */ 0x0000, /* 0x70 p */ 0x11BE, /* 0x71 q: jongseong chieuch */ 0x1163, /* 0x72 r: jongseong ya */ 0x11C2, /* 0x73 s: jongseong hieuh */ 0x1164, /* 0x74 t: jungseong yae */ 0x0000, /* 0x75 u */ 0x116D, /* 0x76 v: jungseong yo */ 0x11C1, /* 0x77 w: jongseong pieup */ 0x11BF, /* 0x78 x: jongseong kieuk */ 0x0000, /* 0x79 y */ 0x11AE, /* 0x7A z: jongseong dieud */ 0x0000, /* 0x7B braceleft */ 0x0000, /* 0x7C bar */ 0x0000, /* 0x7D braceright */ 0x0000 /* 0x7E asciitilde */ ]; } // input_additional_keyboard_layout_info() function input_additional_combination_table_info() { //K4_1969_Typewriter_combination_table = hangeul_combination_table_default.concat([ K4_1969_Typewriter_combination_table = hangeul_combination_table_default.concat([ [0x11631175,0x1164], /* jungseong ya + jungseong i = jungseong yae */ [0x11633163,0x3152], /* jungseong ya + hangeul letter i = hangeul letter yae */ //[0x31513163,0x3152], /* hangeul letter ya + hangeul letter i = hangeul letter yae */ [0x11671175,0x1168], /* jungseong yeo + jungseong i = jungseong ye */ [0x11673163,0x3156], /* jungseong yeo + hangeul letter i = hangeul letter ye */ //[0x31553163,0x3156], /* hangeul letter yeo + hangeul letter i = hangeul letter ye */ [0x1169314F,0x3158], /* jungseong o + hangeul letter a = hangeul letter wa */ [0x11693150,0x3159], /* jungseong o + hangeul letter ae = hangeul letter wae */ [0x11693163,0x315A], /* jungseong o + hangeul letter i = hangeul letter oe */ [0x116E3153,0x315D], /* jungseong u + hangeul letter eo = hangeul letter weo */ [0x116E3154,0x315E], /* jungseong u + hangeul letter e = hangeul letter we */ [0x116E3163,0x315F], /* jungseong u + hangeul letter i = hangeul letter wi */ //0x1171], /* 0x32 2, jungseong wi*/ //0x116C], /* 0x33 3, jungseong oe*/ //116A jungseong wa //116F jungseong weo [0x31613163,0x3156], /* hangeul letter eu + hangeul letter i = hangeul letter eui */ ]); K3_Oesol_Typewriter_combination_table = [ [0x11001100,0x1101], /* choseong gieug + gieug = ssang_gieug */ [0x11031103,0x1104], /* choseong dieud + dieud = ssang_dieud */ [0x11071107,0x1108], /* choseong bieup + bieup = ssang_bieup */ [0x11091109,0x110A], /* choseong sieus + sieus = ssang_sieus */ [0x110C110C,0x110D], /* choseong jieuj + jieuj = ssang_jieuj */ [0x11691161,0x116A], /* jungseong o + a = wa */ [0x11691162,0x116B], /* jungseong o + ae = wae */ [0x11691175,0x116C], /* jungseong o + i = oe */ [0x116E1165,0x116F], /* jungseong u + eo = weo */ [0x116E1166,0x1170], /* jungseong u + e = we */ [0x116E1175,0x1171], /* jungseong u + i = wi */ [0x11731175,0x1174] /* jungseong eu + i = eui */ ]; Gaon38A_combination_table = hangeul_combination_table_default.concat([ [0x11611175,0x1162], /* jungseong a + i = ae */ [0x11631175,0x1164], /* jungseong ya + i = yae */ [0x11651175,0x1166], /* jungseong eo + i = e */ [0x11671175,0x1168], /* jungseong yeo + i = ye */ [0x116A1175,0x116B], /* jungseong wa + i = wae */ [0x116F1175,0x1170], /* jungseong weo + i = we */ ]); Gaon38Ay_combination_table = hangeul_combination_table_full.concat([ [0x110C113C,0x114E], /* jieuj + ap_sieus = ap_jieuj */ [0x110C113E,0x1150], /* jieuj + dwit-sieus = dwit-jieuj */ [0x110D113C,0x114F], /* ssang_jieuj + ap_sieus = ssang_ap_jieuj */ [0x110D113E,0x1151], /* ssang_jieuj + dwit-sieus = ssang_dwit-jieuj */ [0x110E113C,0x1154], /* chieuch + ap_sieus = ap_chieuch */ [0x110E113E,0x1155], /* chieuch + dwit-sieus = dwit-chieuch */ [0x11991175,0xD7BE], /* i-ya + i = i-yae */ [0xD7BF1175,0xD7C0], /* i-yeo + i = i-ye */ [0xD7BA1175,0xD7BB], /* eu-eo + i = eu-e */ [0x117F1175,0x1180], /* o-eo + i = o-e */ [0x11A61175,0x11A7], /* o-ya + i = o-yae */ [0xD7B21175,0xD7B3], /* yo-a + i = yo-ae */ [0x11841175,0x1185], /* yo-ya + i = yo-yae */ [0x11891175,0x118A], /* u-a + i = u-ae */ [0xD7B51175,0x118C], /* u-yeo + i = u-ye */ [0x118F1175,0x1190], /* yu-eo + i = yu-e */ [0x118E1175,0xD7B7], /* yu-a + i = yu-ae */ [0x119F1175,0xD7C6], /* alae_a-eo + i = alae_a-e */ ]); K3_Galmadeuli_Gong3_combination_table = hangeul_combination_table_default.concat([ [0x11751162,0x1164], /* jungseong i + ae = yae */ [0x11621162,0x1164], /* jungseong ae + ae = yae */ ]); K3_2015_additional_fortis_combination_table = [ // 3-2015 자판의 첫소리 된소리 추가 조합 [0x1100110B,0x1101], /* choseong gieug + ieung = ssang_gieug */ [0x110B1100,0x1101], /* choseong ieung + gieug = ssang_gieug */ [0x11031106,0x1104], /* choseong dieud + mieum = ssang_dieud */ [0x11061103,0x1104], /* choseong mieum + dieud = ssang_dieud */ [0x1107110C,0x1108], /* choseong bieub + jieuj = ssang_bieub */ [0x110C1107,0x1108], /* choseong jieuj + bieub = ssang_bieub */ [0x11091112,0x110A], /* choseong sieus + hieuh = ssang_sieus */ [0x11121109,0x110A], /* choseong hieuh + sieus = ssang_sieus */ [0x110C1100,0x110D], /* choseong jieuj + gieug = ssang_jieuj */ [0x1100110C,0x110D] /* choseong gieug + jieuj = ssang_jieuj */ ]; K3_reverse_compound_sound_combination_table = [ // 겹받침 거꿀차례 조합 [0x11A811B7,0x11A9], /* jongseong gieug + mieum = ssang_gieug */ [0x11B711A8,0x11A9], /* jongseong mieum + gieug = ssang_gieug */ [0x11BA11A8,0x11AA], /* jongseong sieus + gieug = gieug-sieus */ [0x11BD11AB,0x11AC], /* jongseong jieuj + nieun = jieun-cieuj */ [0x11C211AB,0x11AD], /* jongseong hieuh + nieun = nieun-hieuh */ [0x11A811AF,0x11B0], /* jongseong gieug + lieul = lieul-gieug */ [0x11B711AF,0x11B1], /* jongseong mieum + lieul = lieul-mieum */ [0x11B811AF,0x11B2], /* jongseong bieub + lieul = lieul-bieub */ [0x11BA11AF,0x11B3], /* jongseong sieus + lieul = lieul-sieus */ [0x11C011AF,0x11B4], /* jongseong tieut + lieul = lieul-tieut */ [0x11C111AF,0x11B5], /* jongseong pieup + lieul = lieul-pieup */ [0x11C211AF,0x11B6], /* jongseong hieuh + lieul = lieul-hieuh */ [0x11BA11B8,0x11B9] /* jongseong sieus + bieub = bieub-sieus */ ]; K3_2015_combination_table = K3_Galmadeuli_Gong3_combination_table.slice(0); K3_2015_additional_combination_table = K3_2015_additional_fortis_combination_table.concat(K3_reverse_compound_sound_combination_table); K3_2015y_combination_table = hangeul_combination_table_full.slice(0); K3_2015y_combination_table.unshift( [0x11BC11A8,0x11EC], /* jongseong ieung + gieug = yesieung-gieug */ [0x11BC11A9,0x11ED], /* jongseong ieung + ssang_gieug = yesieung-ssang_gieug */ [0x11BC11B7,0xD7F5], /* jongseong ieung + mieum = yesieung-mieum */ [0x11BC11BA,0x11F1], /* jongseong ieung + sieus = yesieung-sieus */ [0x11BC11BC,0x11EE], /* jongseong ieung + ieung = ssang_yesieung */ [0x11BC11BF,0x11EF], /* jongseong ieung + kieuk = yesieung-kieuk */ [0x11BC11C2,0xD7F6], /* jongseong ieung + hieuh = yesieung-hieuh */ [0x11BC11EB,0x11F2], /* jongseong ieung + yeolin_sieus = yesieung-yeolin_sieus */ [0x11BC11F0,0x11EE], /* jongseong ieung + yesieung = ssang_yesieung */ [0x11F011BC,0x11EE] /* jongseong yesieung + ieung = ssang_yesieung */ ); K3_D1_combination_table = K3_Galmadeuli_Gong3_combination_table.slice(0); K3_D1_additional_combination_table = [ [0x1100110B,0x1101], /* choseong gieug + ieung = ssang_gieug */ [0x110B1100,0x1101], /* choseong ieung + gieug = ssang_gieug */ [0x11001103,0x1104], /* choseong gieug + dieud = ssang_dieud */ [0x11031100,0x1104], /* choseong dieud + gieug = ssang_dieud */ [0x1107110B,0x1108], /* choseong bieub + ieung = ssang_bieub */ [0x110B1107,0x1108], /* choseong ieung + bieub = ssang_bieub */ [0x11001109,0x110A], /* choseong gieug + sieus = ssang_sieus */ [0x11091100,0x110A], /* choseong sieus + gieug = ssang_sieus */ [0x110C110B,0x110D], /* choseong jieuj + ieung = ssang_jieuj */ [0x110B110C,0x110D], /* choseong ieung + jieuj = ssang_jieuj */ ]; K3_D1_y_combination_table = hangeul_combination_table_full.slice(0); K3_D1_y_combination_table.unshift( [0x11651165,0x119E], /* jungseong eo + eo = alae_a */ [0x11741165,0x11A2] /* jungseong eui + eo = ssang_alae_a */ ); K3_Sin3_2015_combination_table = hangeul_combination_table_default.concat(K3_2015_additional_combination_table); K3_2015M_combination_table = K3_Galmadeuli_Gong3_combination_table.slice(0); // K3_2015M_combination_table.unshift(K3_2015_additional_compound_sound_combination_table); K3_2015M_combination_table.unshift( [0x11621165,0x1164] /* jungseong ae + eo = yae */ ); K3_Sin3_Gongdong_additional_combination_table = [ [0x1100110B,0x1101], /* choseong gieug + ieung = ssang_gieug */ [0x11061103,0x1104], /* choseong mieum + dieud = ssang_dieud */ [0x1107110C,0x1108], /* choseong bieub + jieuj = ssang_bieub */ [0x1107110B,0x1108], /* choseong bieub + ieung = ssang_bieub */ [0x11121109,0x110A], /* choseong hieuh + sieus = ssang_sieus */ [0x1109110C,0x110A], /* choseong sieus + jieuj = ssang_sieus */ [0x110C1100,0x110D] /* choseong jieuj + gieug = ssang_jieuj */ ]; K3_Sin3_Gongdong_abbreviation_table = [ {phonemes: [0x1100,0x11BA], chars: [0xAC12]}, /* ㄱ *ㅅ : 값 */ {phonemes: [0x1102,0x11BA], chars: [0xB299]}, /* ㄴ *ㅅ : 늙 */ {phonemes: [0x1103,0x11BA], chars: [0xB2ED]}, /* ㄷ *ㅅ : 닭 */ {phonemes: [0x1105,0x11BA], chars: [0xB97C]}, /* ㄹ *ㅅ : 를 */ {phonemes: [0x1106,0x11BA], chars: [0xB9CE]}, /* ㅁ *ㅅ : 많 */ {phonemes: [0x1107,0x11BA], chars: [0xBC23]}, /* ㅂ *ㅅ : 밝 */ {phonemes: [0x1109,0x11BA], chars: [0xC0B6]}, /* ㅅ *ㅅ : 삶 */ {phonemes: [0x110B,0x11BA], chars: [0xC54A]}, /* ㅇ *ㅅ : 않 */ {phonemes: [0x110C,0x11BA], chars: [0xC796]}, /* ㅈ *ㅅ : 잖 */ {phonemes: [0x110E,0x11BA], chars: [0xCC2E]}, /* ㅊ *ㅅ : 찮 */ {phonemes: [0x1112,0x11BA], chars: [0xD759]}, /* ㅎ *ㅅ : 흙 */ {phonemes: [0x1100,0x1109], chars: [0xAC19]}, /* ㄱ ㅅ : 같 */ {phonemes: [0x1100,0x110C], chars: [0xACA0]}, /* ㄱ ㅈ : 겠 */ {phonemes: [0x1106,0x1109], chars: [0xB9D1]}, /* ㅁ ㅅ : 맑 */ {phonemes: [0x110B,0x1100], chars: [0xC788]}, /* ㅇ ㄱ : 있 */ {phonemes: [0x110B,0x1107], chars: [0xC785]}, /* ㅇ ㅂ : 입 */ {phonemes: [0x110B,0x110B], chars: [0xC600]}, /* ㅇ ㅇ : 였 */ {phonemes: [0x110B,0x1109], chars: [0xC77D]}, /* ㅇ ㅅ : 읽 */ {phonemes: [0x110B,0x110C], chars: [0xC5C6]}, /* ㅇ ㅈ : 없 */ {phonemes: [0x110B,0x1112], chars: [0xC783]}, /* ㅇ ㅎ : 잃 */ {phonemes: [0x110C,0x110B], chars: [0xC815]}, /* ㅈ ㅇ : 정 */ {phonemes: [0x1109,0x1107], chars: [0xC2B5]}, /* ㅅ ㅂ : 습 */ {phonemes: [0x1112,0x1107], chars: [0xD569]} /* ㅎ ㅂ : 합 */ ]; K3_Sin3_Cham_combination_table = hangeul_combination_table_default.concat([ [0x110B1100,0x1101], /* choseong ieung + gieug = ssang_gieug */ [0x110B1103,0x1104], /* choseong ieung + dieud = ssang_dieud */ [0x110B1107,0x1108], /* choseong ieung + bieub = ssang_bieub */ [0x110B1109,0x110A], /* choseong ieung + sieus = ssang_sieus */ [0x11001109,0x110D], /* choseong gieun + sieus = ssang_jieuj */ ]); K3_Sin3_Cham_additional_combination_table = [ [0x11751161,0x1163], /* jungseong i + a = ya */ [0x11A811B7,0x11A9], /* jongseong gieug + mieum = ssang_gieug */ [0x11A811BB,0x11AA], /* jongseong gieug + ssang_sieus = gieug-sieus */ [0x11BA11A8,0x11AA], /* jongseong sieus + gieug = gieug-sieus */ [0x11A811AB,0x11AC], /* jongseong gieug + nieun = lieul-jieuj */ [0x11BD11AB,0x11AC], /* jongseong jieuj + nieun = lieul-jieuj */ [0x11AB11BC,0x11AD], /* jongseong nieun + ieung = nieun-hieuh */ [0x11BC11AB,0x11AD], /* jongseong ieung + nieun = nieun-hieuh */ [0x11C211AB,0x11AD], /* jongseong hieuh + nieun = nieun-hieuh */ [0x11A811AF,0x11B0], /* jongseong gieug + lieul = lieul-gieug */ [0x11B711AF,0x11B1], /* jongseong mieum + lieul = lieul-mieum */ [0x11AB11B7,0x11B2], /* jongseong nieun + mieum = lieul-bieub */ [0x11B711AB,0x11B2], /* jongseong mieum + nieun = lieul-bieub */ [0x11B811AF,0x11B2], /* jongseong bieub + lieul = lieul-bieub */ [0x11AF11BB,0x11B3], /* jongseong lieul + ssang_sieus = lieul-sieus */ [0x11BB11AF,0x11B3], /* jongseong ssang_sieus + lieul = lieul-sieus */ [0x11C011AF,0x11B4], /* jongseong tieut + lieul = lieul-tieut */ [0x11C111AF,0x11B5], /* jongseong pieup + lieul = lieul-pieup */ [0x11BC11AF,0x11B6], /* jongseong ieung + lieul = lieul-hieuh */ [0x11AF11BC,0x11B6], /* jongseong lieul + ieung = lieul-hieuh */ [0x11C211AF,0x11B6], /* jongseong hieuh + lieul = lieul-hieuh */ [0x11BC11B7,0x11B6], /* jongseong ieung + mieum = lieul-hieuh */ [0x11BA11B8,0x11B9], /* jongseong sieus + bieub = bieub-sieus */ [0x11A811B8,0x11BF], /* jongseong gieug + bieub = kieuk */ //[0x11091100,0x11AA], /* choseong sieus + gieug = jongseong gieug-sieus */ //[0x110C1102,0x11AC], /* choseong jieuj + nieun = jongseong nieun-jieuj */ //[0x11021112,0x11AD], /* choseong nieun + hieuh = jongseong nieun-hieuh */ //[0x11051100,0x11AF], /* choseong lieul + gieug = jongseong lieul-gieug */ //[0x11051106,0x11B1], /* choseong lieul + mieum = jongseong lieul-mieum */ //[0x11051109,0x11B4], /* choseong lieul + sieus = jongseong lieul-sieus */ //[0x11051110,0x11B4], /* choseong lieul + tieut = jongseong lieul-tieut */ //[0x11051111,0x11C1], /* choseong lieul + pieup = jongseong lieul-pieup */ //[0x11051112,0x11B6], /* choseong lieul + hieuh = jongseong lieul-hieuh */ //[0x11071109,0x11B9], /* choseong bieub + sieus = jongseong bieub-sieus */ ]; K3_18Na_combination_table = hangeul_combination_table_default.concat([ [0x11AB11BC,0x11AD], /* jongseong nieun + ieung = jongseong nieun-hieuh */ [0x11AF11AB,0x11B4], /* jongseong lieul + nieun = jongseong lieul-tieut */ [0x11AF11BC,0x11B6], /* jongseong lieul + ieung = jongseong lieul-hieuh */ [0x11B211B8,0x11B5], /* jongseong lieul-bieub + bieub = jongseong lieul-pieup */ [0x11BD11BD,0x11BE] /* jongseong jieuj + jieuj = jongseong chieuch */ ]); K3_LGG_OH_combination_table = [ //첫 된소리 조합 규칙 [0x11001100, 0x1101], /* choseong kiyeok ㄱ + kiyeok ㄱ = ssangkiyeok ㄲ */ [0x11031100, 0x1104], /* choseong tikeut ㄷ + kiyeok ㄱ = ssangtikeut ㄸ */ [0x11071100, 0x1108], /* choseong pieup ㅂ + kiyeok ㄱ = ssangpieup ㅃ */ [0x11091100, 0x110A], /* choseong sios ㅅ + kiyeok ㄱ = ssangsios ㅆ */ [0x110C1100, 0x110D], /* choseong cieuc ㅈ + kiyeok ㄱ = ssangcieuc ㅉ */ //첫 거센소리 조합 규칙 [0x1100110B, 0x110F], /* choseong kiyeok ㄱ + ieung ㅇ = khiyeok ㅋ */ [0x1103110B, 0x1110], /* choseong tikeut ㄷ + ieung ㅇ = thieuth ㅌ */ [0x1107110B, 0x1111], /* choseong pieup ㅂ + ieung ㅇ = phieup ㅍ */ [0x110C110B, 0x110E], /* choseong cieuc ㅈ + ieung ㅇ = chieuc ㅊ */ [0x110B110B, 0x1112], /* choseong ieung ㅇ + ieung ㅇ = hieuh ㅎ */ //가 조합 규칙 [0x11691161, 0x116A], /* jungseong o ㅗ + a ㅏ = wa ㅘ */ [0x11691162, 0x116B], /* jungseong o ㅗ + ae ㅐ = wae ㅙ */ [0x11691175, 0x116C], /* jungseong o ㅗ + i ㅣ = oe ㅚ */ [0x116E1165, 0x116F], /* jungseong u ㅜ + eo ㅓ = weo ㅝ */ [0x116E1166, 0x1170], /* jungseong u ㅜ + e ㅔ = we ㅞ */ [0x116E1175, 0x1171], /* jungseong u ㅜ + i ㅣ = wi ㅟ */ [0x11631163, 0x1164], /* jungseong ya ㅑ + ya ㅑ = yae ㅒ */ //끝 치환 규칙 [0x11AB11AB, 0x11C0], /* jongseong nieun ㄴ + nieun ㄴ = thieuth ㅌ */ [0x11AF11AF, 0x11BF], /* jongseong rieul ㄹ + rieul ㄹ = khieukh ㅋ */ [0x11B711B7, 0x11AE], /* jongseong mieum ㅁ + mieum ㅁ = tigeut ㄷ */ [0x11B811B8, 0x11C1], /* jongseong pieup ㅂ + pieup ㅂ = phieuph ㅍ */ [0x11BA11BA, 0x11BD], /* jongseong sios ㅅ + sios ㅅ = cieuc ㅈ */ [0x11BB11BB, 0x11BE], /* jongseong ssangsios ㅆ + ssangsios ㅆ = chieuch ㅊ */ [0x11BC11BC, 0x11C2], /* jongseong ieung ㅇ + ieung ㅇ = hieuh ㅎ */ //끝 겹받침 조합 규칙 [0x11A811A8, 0x11A9], /* jongseong kiyeok ㄱ + kiyeok ㄱ = ssangkiyeok ㄲ */ [0x11A811BA, 0x11AA], /* jongseong kiyeok ㄱ + sios ㅅ = kiyeok-sois ㄳ */ [0x11AB11BA, 0x11AC], /* jongseong nieun ㄴ + sios ㅅ = nieun-cieuc ㄵ */ [0x11AB11BC, 0x11AD], /* jongseong nieun ㄴ + ieung ㅇ = nieun-hieuh ㄶ */ [0x11AF11A8, 0x11B0], /* jongseong rieul ㄹ + kiyeok ㄱ = rieul-kiyeok ㄺ */ [0x11AF11B7, 0x11B1], /* jongseong rieul ㄹ + mieum ㅁ = rieul-mieum ㄻ */ [0x11AF11B8, 0x11B2], /* jongseong rieul ㄹ + pieup ㅂ = rieul-pieup ㄼ */ [0x11AF11BA, 0x11B3], /* jongseong rieul ㄹ + sios ㅅ = rieul-sios ㄽ */ [0x11AF11AB, 0x11B4], /* jongseong rieul ㄹ + nieun ㄴ = rieul-thieuth ㄾ */ [0x11B211B8, 0x11B5], /* jongseong rieul-pieup ㄼ + pieup ㅂ = rieul-phieuph ㄿ */ [0x11AF11BC, 0x11B6], /* jongseong rieul ㄹ + ieung ㅇ = rieul-hieuh ㅀ */ [0x11B811BA, 0x11B9], /* jongseong pieup ㅂ + sios ㅅ = pieup-sios ㅄ */ ]; K3_Semoe_2014_combination_table = [ {phonemes: [0x1169,0x1161,0x1175], char: 0x116B}, /* jungseong o + a + i = wae */ {phonemes: [0x110B,0x1100], char: 0x1101}, /* choseong ieung + gieug = ssang_gieug */ {phonemes: [0x110B,0x1103], char: 0x1104}, /* choseong ieung + dieud = ssang_dieud */ {phonemes: [0x110B,0x1106], char: 0x1104}, /* choseong mieum + ieung = ssang_dieud */ {phonemes: [0x110B,0x1107], char: 0x1108}, /* choseong ieung + bieub = ssang_bieub */ {phonemes: [0x110B,0x1109], char: 0x110A}, /* choseong ieung + sieus = ssang_sieus */ {phonemes: [0x110B,0x110C], char: 0x110D}, /* choseong ieung + jieuj = ssang_jieuj */ {phonemes: [0x1112,0x1100], char: 0x110F}, /* choseong hieuh + gieug = kieuk */ {phonemes: [0x1112,0x1103], char: 0x1110}, /* choseong hieuh + dieud = tieut */ {phonemes: [0x1112,0x1107], char: 0x1111}, /* choseong hieuh + bieub = pieup */ {phonemes: [0x1112,0x1109], char: 0x110E}, /* choseong hieuh + sieus = chieuch */ {phonemes: [0x1112,0x110C], char: 0x110E}, /* choseong hieuh + jieuj = chieuch */ {phonemes: [0x1161,0x1175], char: 0x1162}, /* jungseong a + i = ae */ {phonemes: [0x1161,0x1165], char: 0x116D}, /* jungseong a + eo = yo */ {phonemes: [0x1161,0x1166], char: 0x1163}, /* jungseong a + e = ya */ {phonemes: [0x1161,0x116E], char: 0x1172}, /* jungseong a + u = yu */ {phonemes: [0x1165,0x1161], char: 0x116D}, /* jungseong eo + a = yo */ {phonemes: [0x1165,0x1167], char: 0x1164}, /* jungseong eo + yeo = yae */ {phonemes: [0x1166,0x1175], char: 0x1168}, /* jungseong e + i = ye */ {phonemes: [0x1169,0x1175], char: 0x116C}, /* jungseong o + i = oe */ {phonemes: [0x1169,0x1161], char: 0x116A}, /* jungseong o + a = wa */ {phonemes: [0x1169,0x1166], char: 0x1168}, /* jungseong o + e = ye */ {phonemes: [0x1169,0x1169], char: 0x116D}, /* jungseong o + o = yo */ {phonemes: [0x1169,0x116E], char: 0x116D}, /* jungseong o + u = yo */ {phonemes: [0x116A,0x1175], char: 0x116B}, /* jungseong wa + i = wae */ {phonemes: [0x1169,0x1167], char: 0x1164}, /* jungseong o + yeo = yae */ {phonemes: [0x116F,0x1175], char: 0x1170}, /* jungseong weo + i = we */ {phonemes: [0x116E,0x1165], char: 0x116F}, /* jungseong u + eo = weo */ {phonemes: [0x116E,0x1166], char: 0x1170}, /* jungseong u + e = we */ {phonemes: [0x116E,0x1175], char: 0x1171}, /* jungseong u + i = wi */ {phonemes: [0x1173,0x1175], char: 0x1174}, /* jungseong eu + i = eui */ {phonemes: [0x1175,0x119E], char: 0x11A1}, /* jungseong i + alae_a = alae_ae */ {phonemes: [0x11A8,0x11BA], char: 0x11AA}, /* jongseong gieug + sieus = gieug-sieus */ {phonemes: [0x11AB,0x11AB], char: 0x11AD}, /* jongseong nieun + nieun = nieun-hieuh */ {phonemes: [0x11AB,0x11AF], char: 0x11AC}, /* jongseong nieun + lieul = nieun-jieuj */ {phonemes: [0x11AB,0x11BB], char: 0x11AD}, /* jongseong nieun + ssang_sieus = nieun-hieuh */ {phonemes: [0x11AB,0x11B7], char: 0x11C0}, /* jongseong nieun + mieum = tieut */ {phonemes: [0x11AB,0x11C2], char: 0x11AD}, /* jongseong nieun + hieuh = nieun-hieuh */ {phonemes: [0x11AE,0x11AF], char: 0x11B4}, /* jongseong dieud + lieul = lieul-tieut */ {phonemes: [0x11AE,0x11B8], char: 0x11B5}, /* jongseong dieud + bieub = lieul-pieup */ {phonemes: [0x11AE,0x11BB], char: 0x11C0}, /* jongseong dieud + ssang_sieus = tikeut */ {phonemes: [0x11AE,0x11C2], char: 0x11C0}, /* jongseong dieud + hieuh = tieut */ {phonemes: [0x11AF,0x11A8], char: 0x11B0}, /* jongseong lieul + gieug = lieul-gieug */ {phonemes: [0x11AF,0x11B7], char: 0x11B1}, /* jongseong lieul + mieum = lieul-mieum */ {phonemes: [0x11AF,0x11B8], char: 0x11B5}, /* jongseong lieul + bieub = lieul-pieup */ {phonemes: [0x11AF,0x11BA], char: 0x11B9}, /* jongseong lieul + sieus = bieub-sieus */ {phonemes: [0x11AF,0x11BC], char: 0x11B2}, /* jongseong lieul + ieung = lieul-bieub */ {phonemes: [0x11B7,0x11A8], char: 0x11B0}, /* jongseong mieum + gieug = lieul-gieug */ {phonemes: [0x11B7,0x11BB], char: 0x11AE}, /* jongseong mieum + ssang_sieus = dieud */ {phonemes: [0x11B7,0x11C2], char: 0x11B4}, /* jongseong mieum + hieuh = lieul-tieut */ {phonemes: [0x11B8,0x11BA], char: 0x11B9}, /* jongseong bieub + sieus = bieub-sieus */ {phonemes: [0x11B8,0x11C2], char: 0x11C1}, /* jongseong bieub + hieuh = pieup */ {phonemes: [0x11B9,0x11BB], char: 0x11B3}, /* jongseong bieub-sieus + ssang_sieus = lieul-sieus */ {phonemes: [0x11BA,0x11BB], char: 0x11BE}, /* jongseong sieus + ssang_sieus = chieuch */ {phonemes: [0x11BA,0x11BC], char: 0x11BB}, /* jongseong sieus + ieung = ssang_sieus */ {phonemes: [0x11BA,0x11C2], char: 0x11B3}, /* jongseong sieus + hieuh = lieul-sieus */ {phonemes: [0x11BB,0x11A8], char: 0x11BF}, /* jongseong ssang_sieus + gieug = kieuk */ {phonemes: [0x11BB,0x11AB], char: 0x11C2}, /* jongseong ssang_sieus + nieun = hieuh */ {phonemes: [0x11BB,0x11AF], char: 0x11BD}, /* jongseong ssang_sieus + lieul = jieuj */ {phonemes: [0x11BB,0x11B7], char: 0x11AE}, /* jongseong ssang_sieus + mieum = dieud */ {phonemes: [0x11BB,0x11B8], char: 0x11C1}, /* jongseong ssang_sieus + bieub = pieup */ {phonemes: [0x11BB,0x11BA], char: 0x11BE}, /* jongseong ssang_sieus + sieus = chieuch */ {phonemes: [0x11BB,0x11BC], char: 0x11B8}, /* jongseong ssang_sieus + ieung = bieub */ {phonemes: [0x11BB,0x11C2], char: 0x11B6}, /* jongseong ssang_sieus + hieuh = lieul-hieuh */ {phonemes: [0x11BC,0x11A8], char: 0x11A9}, /* jongseong ieung + gieug = ssang_egieug */ {phonemes: [0x11BC,0x11AB], char: 0x11C1}, /* jongseong ieung + nieun = pieup */ {phonemes: [0x11BC,0x11B7], char: 0x11B4}, /* jongseong ieung + mieum = lieul-tikeut */ {phonemes: [0x11BC,0x11B8], char: 0x11B5}, /* jongseong ieung + bieub = lieul-pieup */ {phonemes: [0x11BC,0x11BA], char: 0x11BB}, /* jongseong ieung + sieus = ssang_sieus */ {phonemes: [0x11BC,0x11BB], char: 0x11B6}, /* jongseong ieung + ssang_sieus = lieul-hieuh */ {phonemes: [0x11BC,0x11C2], char: 0x11B5}, /* jongseong ieung + hieuh = lieul-pieup */ {phonemes: [0x11C2,0x11A8], char: 0x11BF}, /* jongseong hieuh + gieug = kieuk */ {phonemes: [0x11C2,0x11AE], char: 0x11C0}, /* jongseong hieuh + dieud = tieut */ {phonemes: [0x11C2,0x11B8], char: 0x11C1} /* jongseong hieuh + bieub = pieup */ ]; K3_Semoe_2015_combination_table = [ {phonemes: [0x1169,0x1161,0x1175], char: 0x116A}, /* jungseong o + a + i = wae */ {phonemes: [0x116E,0x1165,0x1175], char: 0x1170}, /* jungseong u + eo + i = we */ {phonemes: [0x110B,0x1100], char: 0x1101}, /* choseong ieung + gieug = ssang_gieug */ {phonemes: [0x110B,0x1103], char: 0x1104}, /* choseong ieung + dieud = ssang_dieud */ {phonemes: [0x110B,0x1107], char: 0x1108}, /* choseong ieung + bieub = ssang_bieub */ {phonemes: [0x110B,0x1109], char: 0x110A}, /* choseong ieung + sieus = ssang_sieus */ {phonemes: [0x110B,0x110C], char: 0x110D}, /* choseong ieung + jieuj = ssang_jieuj */ {phonemes: [0x1112,0x1100], char: 0x110F}, /* choseong hieuh + gieug = kieuk */ {phonemes: [0x1112,0x1103], char: 0x1110}, /* choseong hieuh + dieud = tieut */ {phonemes: [0x1112,0x1107], char: 0x1111}, /* choseong hieuh + bieub = pieup */ {phonemes: [0x1112,0x110C], char: 0x110E}, /* choseong hieuh + jieuj = chieuch */ {phonemes: [0x1161,0x1175], char: 0x1162}, /* jungseong a + i = ae */ {phonemes: [0x1161,0x1165], char: 0x116D}, /* jungseong a + eo = yo */ {phonemes: [0x1165,0x1161], char: 0x116D}, /* jungseong eo + a = yo */ {phonemes: [0x1165,0x1167], char: 0x1164}, /* jungseong eo + yeo = yae */ {phonemes: [0x1169,0x1175], char: 0x116C}, /* jungseong o + i = oe */ {phonemes: [0x1169,0x1161], char: 0x116A}, /* jungseong o + a = wa */ {phonemes: [0x1169,0x1166], char: 0x1168}, /* jungseong o + e = ye */ {phonemes: [0x1169,0x1165], char: 0x1163}, /* jungseong o + eo = ya */ {phonemes: [0x1169,0x1169], char: 0x116D}, /* jungseong o + o = yo */ {phonemes: [0x1169,0x116E], char: 0x1172}, /* jungseong o + u = yu */ {phonemes: [0x116A,0x1175], char: 0x116B}, /* jungseong wa + i = wae */ {phonemes: [0x1169,0x1167], char: 0x1164}, /* jungseong o + yeo = yae */ {phonemes: [0x116F,0x1175], char: 0x1170}, /* jungseong weo + i = we */ {phonemes: [0x116E,0x1165], char: 0x116F}, /* jungseong u + eo = weo */ {phonemes: [0x116E,0x1166], char: 0x1170}, /* jungseong u + e = we */ {phonemes: [0x116E,0x1175], char: 0x1171}, /* jungseong u + i = wi */ {phonemes: [0x1173,0x1175], char: 0x1174}, /* jungseong eu + i = eui */ {phonemes: [0x119E,0x119E], char: 0x11A2}, /* jungseong alae_a + alae_a = ssang_alae_a */ {phonemes: [0x11A8,0x11BA], char: 0x11AA}, /* jongseong gieug + sieus = gieug-sieus */ {phonemes: [0x11AB,0x11AB], char: 0x11AD}, /* jongseong nieun + nieun = nieun-hieuh */ {phonemes: [0x11AB,0x11AF], char: 0x11AC}, /* jongseong nieun + lieul = nieun-jieuj */ {phonemes: [0x11AB,0x11B7], char: 0x11C0}, /* jongseong nieun + mieum = tieut */ {phonemes: [0x11AB,0x11C2], char: 0x11AD}, /* jongseong nieun + hieuh = nieun-hieuh */ {phonemes: [0x11AF,0x11A8], char: 0x11B0}, /* jongseong lieul + gieug = lieul-gieug */ {phonemes: [0x11AF,0x11B7], char: 0x11B1}, /* jongseong lieul + mieum = lieul-mieum */ {phonemes: [0x11AF,0x11B8], char: 0x11B2}, /* jongseong lieul + bieub = lieul-bieub */ {phonemes: [0x11AF,0x11BA], char: 0x11B3}, /* jongseong lieul + sieus = lieul-sieus */ {phonemes: [0x11B7,0x11A8], char: 0x11B0}, /* jongseong mieum + gieug = lieul-gieug */ {phonemes: [0x11B8,0x11BA], char: 0x11B9}, /* jongseong bieub + sieus = bieub-sieus */ {phonemes: [0x11BB,0x11A8], char: 0x11BF}, /* jongseong ssang_sieus + gieug = kieuk */ {phonemes: [0x11BB,0x11AB], char: 0x11C2}, /* jongseong ssang_sieus + nieun = hieuh */ {phonemes: [0x11BB,0x11AF], char: 0x11BD}, /* jongseong ssang_sieus + lieul = jieuj */ {phonemes: [0x11BB,0x11B7], char: 0x11AE}, /* jongseong ssang_sieus + mieum = dieud */ {phonemes: [0x11BB,0x11B8], char: 0x11C1}, /* jongseong ssang_sieus + bieub = pieup */ {phonemes: [0x11BB,0x11BA], char: 0x11BE}, /* jongseong ssang_sieus + sieus = chieuch */ {phonemes: [0x11BB,0x11BC], char: 0x11B6}, /* jongseong ssang_sieus + ieung = lieul-hieuh */ {phonemes: [0x11BC,0x11A8], char: 0x11A9}, /* jongseong ieung + gieug = ssang_egieug */ {phonemes: [0x11BC,0x11AB], char: 0x11AD}, /* jongseong ieung + nieun = nieun-hieuh */ {phonemes: [0x11BC,0x11B7], char: 0x11B4}, /* jongseong ieung + mieum = lieul-tikeut */ {phonemes: [0x11BC,0x11B8], char: 0x11B5}, /* jongseong ieung + bieub = lieul-pieup */ {phonemes: [0x11BC,0x11BA], char: 0x11BB}, /* jongseong ieung + sieus = ssang_sieus */ {phonemes: [0x11BC,0x11BB], char: 0x11B6}, /* jongseong ieung + ssang_sieus = lieul-hieuh */ {phonemes: [0x11C2,0x11A8], char: 0x11BF}, /* jongseong hieuh + gieug = kieuk */ {phonemes: [0x11C2,0x11AE], char: 0x11C0}, /* jongseong hieuh + dieud = tieut */ {phonemes: [0x11C2,0x11B8], char: 0x11C1} /* jongseong hieuh + bieub = pieup */ ]; K3_Semoe_2016_combination_table = [ {phonemes: [0x1169,0x1161,0x1175], char: 0x116B}, /* jungseong o + a + i = wae */ {phonemes: [0x11A8,0x11B7,0x11BB], char: 0x11AA}, /* jongseong gieug + mieum + ssang_sieus = gieug-sieus */ {phonemes: [0x11AF,0x11B8,0x11BB], char: 0x11C0}, /* jongseong lieul + bieub + ssang_sieus = tieut */ {phonemes: [0x11AF,0x11BA,0x11BB], char: 0x11A9}, /* jongseong lieul + sieus + ssang_sieus = ssang_egieug */ {phonemes: [0x11B8,0x11BA,0x11BB], char: 0x11B1}, /* jongseong bieub + sieus + ssang_sieus = lieul-mieum */ {phonemes: [0x110B,0x1100], char: 0x1101}, /* choseong ieung + gieug = ssang_gieug */ {phonemes: [0x110B,0x1103], char: 0x1104}, /* choseong ieung + dieud = ssang_dieud */ {phonemes: [0x110B,0x1107], char: 0x1108}, /* choseong ieung + bieub = ssang_bieub */ {phonemes: [0x110B,0x1109], char: 0x110A}, /* choseong ieung + sieus = ssang_sieus */ {phonemes: [0x110B,0x110C], char: 0x110D}, /* choseong ieung + jieuj = ssang_jieuj */ {phonemes: [0x1112,0x1100], char: 0x110F}, /* choseong hieuh + gieug = kieuk */ {phonemes: [0x1112,0x1103], char: 0x1110}, /* choseong hieuh + dieud = tieut */ {phonemes: [0x1112,0x1107], char: 0x1111}, /* choseong hieuh + bieub = pieup */ {phonemes: [0x1112,0x110C], char: 0x110E}, /* choseong hieuh + jieuj = chieuch */ {phonemes: [0x1161,0x1175], char: 0x1162}, /* jungseong a + i = ae */ {phonemes: [0x1161,0x1165], char: 0x116D}, /* jungseong a + eo = yo */ {phonemes: [0x1165,0x1161], char: 0x116D}, /* jungseong eo + a = yo */ {phonemes: [0x1169,0x1175], char: 0x116C}, /* jungseong o + i = oe */ {phonemes: [0x1169,0x1161], char: 0x116A}, /* jungseong o + a = wa */ {phonemes: [0x1169,0x1166], char: 0x1168}, /* jungseong o + e = ye */ {phonemes: [0x1169,0x1165], char: 0x1163}, /* jungseong o + eo = ya */ {phonemes: [0x1169,0x1169], char: 0x116D}, /* jungseong o + o = yo */ {phonemes: [0x1169,0x116E], char: 0x1172}, /* jungseong o + u = yu */ {phonemes: [0x116A,0x1175], char: 0x116B}, /* jungseong wa + i = wae */ {phonemes: [0x1169,0x1167], char: 0x1164}, /* jungseong o + yeo = yae */ {phonemes: [0x116F,0x1175], char: 0x1170}, /* jungseong weo + i = we */ {phonemes: [0x116E,0x1165], char: 0x116F}, /* jungseong u + eo = weo */ {phonemes: [0x116E,0x1166], char: 0x1170}, /* jungseong u + e = we */ {phonemes: [0x116E,0x1175], char: 0x1171}, /* jungseong u + i = wi */ {phonemes: [0x1173,0x1175], char: 0x1174}, /* jungseong eu + i = eui */ {phonemes: [0x11A8,0x11BA], char: 0x11AA}, /* jongseong gieug + sieus = gieug-sieus */ {phonemes: [0x11AB,0x11AB], char: 0x11AD}, /* jongseong nieun + nieun = nieun-hieuh */ {phonemes: [0x11AB,0x11AF], char: 0x11AC}, /* jongseong nieun + lieul = nieun-jieuj */ {phonemes: [0x11AB,0x11B7], char: 0x11C0}, /* jongseong nieun + mieum = tieut */ {phonemes: [0x11AB,0x11C2], char: 0x11AD}, /* jongseong nieun + hieuh = nieun-hieuh */ {phonemes: [0x11AF,0x11A8], char: 0x11B0}, /* jongseong lieul + gieug = lieul-gieug */ {phonemes: [0x11AF,0x11B7], char: 0x11B1}, /* jongseong lieul + mieum = lieul-mieum */ {phonemes: [0x11AF,0x11B8], char: 0x11B2}, /* jongseong lieul + bieub = lieul-bieub */ {phonemes: [0x11AF,0x11BA], char: 0x11B3}, /* jongseong lieul + sieus = lieul-sieus */ {phonemes: [0x11AF,0x11BC], char: 0x11A8}, /* jongseong lieul + ieung = gieug */ {phonemes: [0x11B7,0x11A8], char: 0x11B0}, /* jongseong mieum + gieug = lieul-gieug */ {phonemes: [0x11B8,0x11BA], char: 0x11B9}, /* jongseong bieub + sieus = bieub-sieus */ {phonemes: [0x11BB,0x11A8], char: 0x11BF}, /* jongseong ssang_sieus + gieug = kieuk */ {phonemes: [0x11BB,0x11AB], char: 0x11C2}, /* jongseong ssang_sieus + nieun = hieuh */ {phonemes: [0x11BB,0x11AF], char: 0x11BD}, /* jongseong ssang_sieus + lieul = jieuj */ {phonemes: [0x11BB,0x11B7], char: 0x11AE}, /* jongseong ssang_sieus + mieum = dieud */ {phonemes: [0x11BB,0x11B8], char: 0x11C1}, /* jongseong ssang_sieus + bieub = pieup */ {phonemes: [0x11BB,0x11BA], char: 0x11BE}, /* jongseong ssang_sieus + sieus = chieuch */ {phonemes: [0x11BB,0x11BC], char: 0x11B6}, /* jongseong ssang_sieus + ieung = lieul-hieuh */ {phonemes: [0x11BC,0x11A8], char: 0x11A9}, /* jongseong ieung + gieug = ssang_egieug */ {phonemes: [0x11BC,0x11AB], char: 0x11AD}, /* jongseong ieung + nieun = nieun-hieuh */ {phonemes: [0x11BC,0x11B7], char: 0x11B4}, /* jongseong ieung + mieum = lieul-tikeut */ {phonemes: [0x11BC,0x11B8], char: 0x11B5}, /* jongseong ieung + bieub = lieul-pieup */ {phonemes: [0x11BC,0x11BA], char: 0x11BB}, /* jongseong ieung + sieus = ssang_sieus */ {phonemes: [0x11BC,0x11BB], char: 0x11B6}, /* jongseong ieung + ssang_sieus = lieul-hieuh */ {phonemes: [0x11C2,0x11A8], char: 0x11BF}, /* jongseong hieuh + gieug = kieuk */ {phonemes: [0x11C2,0x11AE], char: 0x11C0}, /* jongseong hieuh + dieud = tieut */ {phonemes: [0x11C2,0x11B8], char: 0x11C1} /* jongseong hieuh + bieub = pieup */ ]; K3_Semoe_2017_combination_table = [ {phonemes: [0x1169,0x1161,0x1175], char: 0x116B}, /* jungseong o + a + i = wae */ {phonemes: [0x11A8,0x11B7,0x11BB], char: 0x11AA}, /* jongseong gieug + mieum + ssang_sieus = gieug-sieus */ {phonemes: [0x11AF,0x11B8,0x11BB], char: 0x11C0}, /* jongseong lieul + bieub + ssang_sieus = tieut */ {phonemes: [0x11AF,0x11BA,0x11BB], char: 0x11A9}, /* jongseong lieul + sieus + ssang_sieus = ssang_egieug */ {phonemes: [0x11B8,0x11BA,0x11BB], char: 0x11B1}, /* jongseong bieub + sieus + ssang_sieus = lieul-mieum */ {phonemes: [0x110B,0x1100], char: 0x1101}, /* choseong ieung + gieug = ssang_gieug */ {phonemes: [0x110B,0x1103], char: 0x1104}, /* choseong ieung + dieud = ssang_dieud */ {phonemes: [0x110B,0x1107], char: 0x1108}, /* choseong ieung + bieub = ssang_bieub */ {phonemes: [0x110B,0x1109], char: 0x110A}, /* choseong ieung + sieus = ssang_sieus */ {phonemes: [0x110B,0x110C], char: 0x110D}, /* choseong ieung + jieuj = ssang_jieuj */ {phonemes: [0x1112,0x1100], char: 0x110F}, /* choseong hieuh + gieug = kieuk */ {phonemes: [0x1112,0x1103], char: 0x1110}, /* choseong hieuh + dieud = tieut */ {phonemes: [0x1112,0x1107], char: 0x1111}, /* choseong hieuh + bieub = pieup */ {phonemes: [0x1112,0x110C], char: 0x110E}, /* choseong hieuh + jieuj = chieuch */ {phonemes: [0x1161,0x1175], char: 0x1162}, /* jungseong a + i = ae */ {phonemes: [0x1161,0x1165], char: 0x116D}, /* jungseong a + eo = yo */ {phonemes: [0x1165,0x1161], char: 0x116D}, /* jungseong eo + a = yo */ {phonemes: [0x1169,0x1175], char: 0x116C}, /* jungseong o + i = oe */ {phonemes: [0x1169,0x1161], char: 0x116A}, /* jungseong o + a = wa */ {phonemes: [0x1169,0x1166], char: 0x1168}, /* jungseong o + e = ye */ {phonemes: [0x1169,0x1165], char: 0x1163}, /* jungseong o + eo = ya */ {phonemes: [0x1169,0x1169], char: 0x116D}, /* jungseong o + o = yo */ {phonemes: [0x1169,0x116E], char: 0x1172}, /* jungseong o + u = yu */ {phonemes: [0x116A,0x1175], char: 0x116B}, /* jungseong wa + i = wae */ {phonemes: [0x1169,0x1167], char: 0x1164}, /* jungseong o + yeo = yae */ {phonemes: [0x116F,0x1175], char: 0x1170}, /* jungseong weo + i = we */ {phonemes: [0x116E,0x1165], char: 0x116F}, /* jungseong u + eo = weo */ {phonemes: [0x116E,0x1166], char: 0x1170}, /* jungseong u + e = we */ {phonemes: [0x116E,0x1175], char: 0x1171}, /* jungseong u + i = wi */ {phonemes: [0x1173,0x1175], char: 0x1174}, /* jungseong eu + i = eui */ {phonemes: [0x11A8,0x11BA], char: 0x11AA}, /* jongseong gieug + sieus = gieug-sieus */ {phonemes: [0x11AB,0x11AB], char: 0x11AD}, /* jongseong nieun + nieun = nieun-hieuh */ {phonemes: [0x11AB,0x11AF], char: 0x11AC}, /* jongseong nieun + lieul = nieun-jieuj */ {phonemes: [0x11AB,0x11B7], char: 0x11C0}, /* jongseong nieun + mieum = tieut */ {phonemes: [0x11AB,0x11C2], char: 0x11AD}, /* jongseong nieun + hieuh = nieun-hieuh */ {phonemes: [0x11AF,0x11A8], char: 0x11B0}, /* jongseong lieul + gieug = lieul-gieug */ {phonemes: [0x11AF,0x11B7], char: 0x11B1}, /* jongseong lieul + mieum = lieul-mieum */ {phonemes: [0x11AF,0x11B8], char: 0x11B2}, /* jongseong lieul + bieub = lieul-bieub */ {phonemes: [0x11AF,0x11BA], char: 0x11B3}, /* jongseong lieul + sieus = lieul-sieus */ {phonemes: [0x11AF,0x11BC], char: 0x11A8}, /* jongseong lieul + ieung = gieug */ {phonemes: [0x11B7,0x11A8], char: 0x11B0}, /* jongseong mieum + gieug = lieul-gieug */ {phonemes: [0x11B8,0x11BA], char: 0x11B9}, /* jongseong bieub + sieus = bieub-sieus */ {phonemes: [0x11BB,0x11A8], char: 0x11BF}, /* jongseong ssang_sieus + gieug = kieuk */ {phonemes: [0x11BB,0x11AB], char: 0x11C2}, /* jongseong ssang_sieus + nieun = hieuh */ {phonemes: [0x11BB,0x11AF], char: 0x11BD}, /* jongseong ssang_sieus + lieul = jieuj */ {phonemes: [0x11BB,0x11B7], char: 0x11AE}, /* jongseong ssang_sieus + mieum = dieud */ {phonemes: [0x11BB,0x11B8], char: 0x11C1}, /* jongseong ssang_sieus + bieub = pieup */ {phonemes: [0x11BB,0x11BA], char: 0x11BE}, /* jongseong ssang_sieus + sieus = chieuch */ {phonemes: [0x11BB,0x11BC], char: 0x11B6}, /* jongseong ssang_sieus + ieung = lieul-hieuh */ {phonemes: [0x11BC,0x11A8], char: 0x11A9}, /* jongseong ieung + gieug = ssang_egieug */ {phonemes: [0x11BC,0x11AB], char: 0x11AD}, /* jongseong ieung + nieun = nieun-hieuh */ {phonemes: [0x11BC,0x11B7], char: 0x11B4}, /* jongseong ieung + mieum = lieul-tikeut */ {phonemes: [0x11BC,0x11B8], char: 0x11B5}, /* jongseong ieung + bieub = lieul-pieup */ {phonemes: [0x11BC,0x11BA], char: 0x11BB}, /* jongseong ieung + sieus = ssang_sieus */ {phonemes: [0x11BC,0x11BB], char: 0x11B6}, /* jongseong ieung + ssang_sieus = lieul-hieuh */ {phonemes: [0x11C2,0x11A8], char: 0x11BF}, /* jongseong hieuh + gieug = kieuk */ {phonemes: [0x11C2,0x11AE], char: 0x11C0}, /* jongseong hieuh + dieud = tieut */ {phonemes: [0x11C2,0x11B8], char: 0x11C1} /* jongseong hieuh + bieub = pieup */ ]; K3_Semoe_2017_moachigi_multikey_abbreviation_table = [ {keys: ['J','K'], chars: [-1]}, /* 기호 확장 상태 ① */ {keys: ['J','L'], chars: [-2]}, /* 기호 확장 상태 ② */ {keys: ['J',':'], chars: [-3]}, /* 기호 확장 상태 ③ */ {keys: ['k','u'], chars: [0xADF8,0xB7EC,0xB098]}, /* 그러나 */ {keys: ['u','w'], chars: [0x11B8,0xB2C8,0xB2E4]}, /* ㅂ니다 */ {keys: [',','u','w'], chars: [0x11B8,0xB2C8,0xB2E4,0x2E,0x20]}, /* ㅂ니다. */ {keys: ['j','w'], chars: [0xC785,0xB2E4,0x2E,0x20]}, /* 입니다. */ {keys: ['h','w'], chars: [0xD569,0xB2C8,0xB2E4,0x2E,0x20]}, /* 합니다. */ {keys: ['h','l','s'], chars: [0xD558,0xC9C0,0xB9CC,0x20]}, /* 하지만 */ {keys: ['m','n'], chars: [0x21,0x20]}, /* ! */ {keys: ['i','u'], chars: [0x2C,0x20]}, /* , */ {keys: ['i','o'], chars: [0x2E,0x20]}, /* . */ {keys: ['u','y'], chars: [0x3F,0x20]}, /* ? */ /* 첫소리 ㄱ 조합 */ {keys: ['k','x'], chars: [0xAD6D,0xAC00]}, /* 국가 */ {keys: ['k','s','x'], chars: [0xAD6D,0xBBFC]}, /* 국민 */ {keys: ['e','k','s'], chars: [0xAC74,0xBB3C]}, /* 건물 */ {keys: ['a','k','s'], chars: [0xACF5,0xAC04]}, /* 공간 */ {keys: ['e','k'], chars: [0xACB0,0xACFC]}, /* 결과 */ {keys: ['k','x','z'], chars: [0xACB0,0xAD6D]}, /* 결국 */ {keys: ['e','k','x'], chars: [0xACB0,0xAD6D]}, /* 결국 */ {keys: ['k','z'], chars: [0xAC1C,0xB150]}, /* 개념 */ {keys: ['c','g','k'], chars: [0xACC4,0xAE09]}, /* 계급 */ {keys: ['k','q'], chars: [0xADF8,0xAC83]}, /* 그것 */ {keys: ['a','k'], chars: [0xAD11,0xACE0]}, /* 광고 */ {keys: [';','j','k','w'], chars: [0xAE4A,0xC774]}, /* 깊이 */ {keys: ['g','k','t'], chars: [0xADF8,0xB140]}, /* 그녀 */ {keys: ['b','f','k'], chars: [0xAD6C,0xB098]}, /* 구나 */ {keys: ['f','k','p'], chars: [0xAD6C,0xB098]}, /* 구나 */ {keys: ['b','f','k','x'], chars: [0xAD6D,0xB0B4]}, /* 국내 */ {keys: ['f','k','p','x'], chars: [0xAD6D,0xB0B4]}, /* 국내 */ {keys: ['g','k','v'], chars: [0xADF8,0xACF3]}, /* 그곳 */ {keys: ['.','g','k'], chars: [0xADF8,0xACF3]}, /* 그곳 */ {keys: ['c','k','t'], chars: [0xACBD,0xACC4]}, /* 경계 */ {keys: ['c','d','k'], chars: [0xAE30,0xACC4]}, /* 기계 */ {keys: ['f','g','k','s'], chars: [0xADF8,0xB9CC]}, /* 그만 */ {keys: ['j','k','x'], chars: [0xAD50,0xC721]}, /* 교육 */ {keys: ['j','k','s','x'], chars: [0xC57D,0xAC04]}, /* 약간 */ {keys: ['j','k','s'], chars: [0xC778,0xAC04]}, /* 인간 */ {keys: ['a','j','k','s'], chars: [0xACF5,0xC5F0]}, /* 공연 */ {keys: ['e','j','k'], chars: [0xC5BC,0xAD74]}, /* 얼굴 */ {keys: ['a','j','k','w'], chars: [0xACF5,0xC5C5]}, /* 공업 */ {keys: ['j','k','w'], chars: [0xAE30,0xC5C5]}, /* 기업 */ {keys: ['j','k','q'], chars: [0xC774,0xAC83]}, /* 이것 */ {keys: ['a','j','k'], chars: [0xACBD,0xC6B0]}, /* 경우 */ {keys: ['j','k','s','z'], chars: [0xAC19,0xC774,0x20]}, /* 같이 */ {keys: ['b','j','k','t'], chars: [0xC5F0,0xAD6C]}, /* 연구 */ {keys: ['j','k','p','t'], chars: [0xC5F0,0xAD6C]}, /* 연구 */ {keys: ['b','e','j','k','t'], chars: [0xACA8,0xC6B8]}, /* 겨울 */ {keys: ['e','j','k','p','t'], chars: [0xACA8,0xC6B8]}, /* 겨울 */ {keys: ['g','j','k','t'], chars: [0xC5F0,0xADF9]}, /* 연극 */ {keys: ['g','j','k','s','t'], chars: [0xC758,0xACAC]}, /* 의견 */ {keys: ['b','f','j','k'], chars: [0xC694,0xAD6C]}, /* 요구 */ {keys: ['f','j','k','p'], chars: [0xC694,0xAD6C]}, /* 요구 */ {keys: ['b','e','f','j','k'], chars: [0xAC1C,0xC6D4]}, /* 개월 */ {keys: ['e','f','j','k','p'], chars: [0xAC1C,0xC6D4]}, /* 개월 */ {keys: ['c','j','k','r'], chars: [0xAED8,0xC11C,0x20]}, /* 께서 */ {keys: ['c','d','j','k','z'], chars: [0xAC8C,0xC784]}, /* 게임 */ {keys: ['f','g','j','k'], chars: [0xC744,0xAE4C,0x3F,0x20]}, /* 을까? */ {keys: ['e','f','g','j','k'], chars: [0xAC00,0xC744]}, /* 가을 */ {keys: ['f','g','j','k','z'], chars: [0xAC00,0xB054]}, /* 가끔 */ {keys: ['g','j','k','r'], chars: [0xAC70,0xC758,0x20]}, /* 거의 */ {keys: ['k','l','x'], chars: [0xC911,0xAD6D]}, /* 중국 */ {keys: ['k','l','s'], chars: [0xCE5C,0xAD6C]}, /* 친구 */ {keys: ['a','k','l','s'], chars: [0xAD1C,0xCC2E]}, /* 괜찮 */ {keys: ['e','k','l'], chars: [0xACBD,0xCC30]}, /* 경찰 */ {keys: ['k','l','z'], chars: [0xC9C0,0xAE08]}, /* 지금 */ {keys: ['k','l','w'], chars: [0xAC11,0xC790,0xAE30,0x20]}, /* 갑자기 */ {keys: ['k','l','q'], chars: [0xAC70,0xC9D3]}, /* 거짓 */ {keys: ['a','k','l'], chars: [0xAC00,0xC7A5,0x20]}, /* 가장 */ {keys: ['f','k','l'], chars: [0xC790,0xAE30]}, /* 자기 */ {keys: ['f','k','l','x'], chars: [0xC791,0xAC00]}, /* 작가 */ {keys: ['f','k','l','r','z'], chars: [0xAC10,0xC815]}, /* 감정 */ {keys: ['a','f','k','l','s'], chars: [0xC7A5,0xAD00]}, /* 장관 */ {keys: ['d','f','k','l'], chars: [0xAC00,0xC9C0]}, /* 가지 */ {keys: ['a','d','f','k','l'], chars: [0xACBD,0xC7C1]}, /* 경쟁 */ {keys: ['k','l','r','x'], chars: [0xAC71,0xC815]}, /* 걱정 */ {keys: ['k','l','r','s'], chars: [0xC870,0xAC74]}, /* 조건 */ {keys: ['k','l','r','z'], chars: [0xAC80,0xCC30]}, /* 검찰 */ {keys: ['a','k','l','r'], chars: [0xAC00,0xC815]}, /* 가정 */ {keys: ['c','k','l'], chars: [0xAD6C,0xCCB4,0xC801]}, /* 구체적 */ {keys: ['k','l','t'], chars: [0xAC00,0xC838]}, /* 가져 */ {keys: ['e','k','l','t'], chars: [0xACB0,0xC815]}, /* 결정 */ {keys: ['a','k','l','t'], chars: [0xACBD,0xC81C]}, /* 경제 */ {keys: ['c','k','l','v'], chars: [0xCCB4,0xACC4]}, /* 체계 */ {keys: ['k','l','v'], chars: [0xCD5C,0xACE0]}, /* 최고 */ {keys: ['k','l','v','x'], chars: [0xAC00,0xC871]}, /* 가족 */ {keys: ['k','l','s','v'], chars: [0xAE30,0xC874]}, /* 기존 */ {keys: ['a','k','l','v'], chars: [0xAC01,0xC885]}, /* 각종 */ {keys: ['.','f','k','l'], chars: [0xACFC,0xC815]}, /* 과정 */ {keys: ['f','k','l','v'], chars: [0xACFC,0xC815]}, /* 과정 */ {keys: ['.','f','k','l','s'], chars: [0xAD00,0xC810]}, /* 관점 */ {keys: ['f','k','l','s','v'], chars: [0xAD00,0xC810]}, /* 관점 */ {keys: ['.','a','f','k','l'], chars: [0xAC15,0xC870]}, /* 강조 */ {keys: ['a','f','k','l','v'], chars: [0xAC15,0xC870]}, /* 강조 */ {keys: ['d','k','l','s','v'], chars: [0xCD5C,0xADFC,0x20]}, /* 최근 */ {keys: ['.','d','k','l','s'], chars: [0xCD5C,0xADFC,0x20]}, /* 최근 */ {keys: ['.','k','l','v'], chars: [0xC885,0xAD50]}, /* 종교 */ {keys: ['f','k','l','r'], chars: [0xC885,0xAD50]}, /* 종교 */ {keys: ['a','f','k','l','r'], chars: [0xAD50,0xC7A5]}, /* 교장 */ {keys: ['.','a','k','l','v'], chars: [0xAD50,0xC7A5]}, /* 교장 */ {keys: ['b','k','l'], chars: [0xAD6C,0xC870]}, /* 구조 */ {keys: ['b','k','l','r'], chars: [0xC804,0xAD6D]}, /* 전국 */ {keys: ['k','l','p','r'], chars: [0xC804,0xAD6D]}, /* 전국 */ {keys: ['b','k','l','s'], chars: [0xAE30,0xC900]}, /* 기준 */ {keys: ['b','k','l','r','s'], chars: [0xC815,0xAD8C]}, /* 정권 */ {keys: ['k','l','p','r','s'], chars: [0xC815,0xAD8C]}, /* 정권 */ {keys: ['b','c','k','l','x'], chars: [0xAD6D,0xC81C]}, /* 국제 */ {keys: ['c','k','l','p','x'], chars: [0xAD6D,0xC81C]}, /* 국제 */ {keys: ['d','k','l','v','x'], chars: [0xADC0,0xC871]}, /* 귀족 */ {keys: ['.','d','k','l','x'], chars: [0xADC0,0xC871]}, /* 귀족 */ {keys: ['b','k','l','v'], chars: [0xADDC,0xC815]}, /* 규정 */ {keys: ['k','l','p','v'], chars: [0xADDC,0xC815]}, /* 규정 */ {keys: ['.','b','k','l'], chars: [0xADDC,0xC815]}, /* 규정 */ {keys: ['d','g','k','l'], chars: [0xADF8,0xB807,0xC9C0]}, /* 그렇지 */ {keys: ['g','k','l','s'], chars: [0xADFC,0xCC98]}, /* 근처 */ {keys: ['g','k','l','z'], chars: [0xC790,0xAE08]}, /* 자금 */ {keys: ['d','k','l'], chars: [0xAE4C,0xC9C0,0x20]}, /* 까지 */ {keys: ['c','f','k','l'], chars: [0xACFC,0xC81C]}, /* 과제 */ /* 첫소리 ㄴ 조합 */ {keys: ['e','u'], chars: [0xADF8,0xB0A0]}, /* 그날 */ {keys: ['u','z'], chars: [0xB0A8,0xC131]}, /* 남성 */ {keys: ['b','f','u'], chars: [0xB204,0xB098]}, /* 누나 */ {keys: ['f','p','u'], chars: [0xB204,0xB098]}, /* 누나 */ {keys: ['g','r','u'], chars: [0xB290,0xB0D0,0x3F,0x20]}, /* 느냐? */ {keys: ['i','s','u'], chars: [0xB294,0xB370]}, /* 는데 */ {keys: ['a','i','u'], chars: [0xB178,0xB3D9]}, /* 노동 */ {keys: ['f','i','u'], chars: [0xB098,0xD0C0]}, /* 나타 */ {keys: ['i','r','u'], chars: [0xB354,0xB2C8,0x20]}, /* 더니 */ {keys: ['i','s','t','u'], chars: [0xB144,0xB300]}, /* 년대 */ {keys: ['i','u','v'], chars: [0xB610,0xB294,0x20]}, /* 또는 */ {keys: ['a','i','u','v'], chars: [0xB3D9,0xB124]}, /* 동네 */ {keys: ['d','i','u'], chars: [0xB2C8,0xB2E4,0x2E,0x20]}, /* 니다. */ /* 첫소리 ㄷ 조합 */ {keys: ['i','x'], chars: [0xB300,0xD559]}, /* 대학 */ {keys: ['i','s'], chars: [0xB2E4,0xB978,0x20]}, /* 다른 */ {keys: ['i','w'], chars: [0xB300,0xB2F5]}, /* 대답 */ {keys: ['i','q'], chars: [0xB4EF,0xC774,0x20]}, /* 듯이 */ {keys: ['a','i'], chars: [0xB2E4,0xC591]}, /* 다양 */ {keys: ['b','f','i'], chars: [0xBB34,0xB300]}, /* 무대 */ {keys: ['f','i','p'], chars: [0xBB34,0xB300]}, /* 무대 */ {keys: ['c','f','i','s'], chars: [0xB2E8,0xCCB4]}, /* 단체 */ {keys: ['f','g','i'], chars: [0xB9CC,0xB4E4]}, /* 만들 */ {keys: ['i','k','x'], chars: [0xAE30,0xB3C5,0xAD50]}, /* 기독교 */ {keys: ['i','k','s'], chars: [0xAC00,0xC6B4,0xB370]}, /* 가운데 */ {keys: ['i','k','x','z'], chars: [0xAE4C,0xB2ED]}, /* 까닭 */ {keys: ['e','i','k','x'], chars: [0xAE4C,0xB2ED]}, /* 까닭 */ {keys: ['i','k','z'], chars: [0xAC10,0xB3C5]}, /* 감독 */ {keys: ['a','i','k'], chars: [0xACF5,0xB3D9]}, /* 공동 */ {keys: ['f','i','k'], chars: [0xB2E4,0xAC00]}, /* 다가 */ {keys: ['c','i','k','s'], chars: [0xB2E8,0xACC4]}, /* 단계 */ {keys: ['e','i','k'], chars: [0xAC08,0xB4F1]}, /* 갈등 */ {keys: ['f','g','i','k'], chars: [0xADF8,0xB54C]}, /* 그때 */ {keys: ['g','i','k','r','s'], chars: [0xAC70,0xB4E0]}, /* 거든 */ {keys: ['i','k','r','s'], chars: [0xB358,0xAC00,0x3F,0x20]}, /* 던가? */ {keys: ['c','i','k','v'], chars: [0xACC4,0xB2E8]}, /* 계단 */ {keys: ['d','f','i','k','v'], chars: [0xACE0,0xB300]}, /* 고대 */ {keys: ['.','d','f','i','k'], chars: [0xACE0,0xB300]}, /* 고대 */ {keys: ['a','i','k','v'], chars: [0xACE0,0xB3D9]}, /* 고통 */ {keys: ['f','i','k','v'], chars: [0xB2E4,0xACE0]}, /* 다고 */ {keys: ['.','f','i','k'], chars: [0xB2E4,0xACE0]}, /* 다고 */ {keys: ['d','i','k','v'], chars: [0xAE30,0xB3C4]}, /* 기도 */ {keys: ['.','d','i','k'], chars: [0xAE30,0xB3C4]}, /* 기도 */ {keys: ['g','i','k'], chars: [0xADF8,0xB300]}, /* 그대 */ {keys: ['g','i','k','s'], chars: [0xADFC,0xB370,0x20]}, /* 근데 */ {keys: ['d','i','k'], chars: [0xAE30,0xB2E4]}, /* 기다 */ {keys: ['i','j','x'], chars: [0xB354,0xC6B1,0x20]}, /* 더욱 */ {keys: ['i','j','s'], chars: [0xB54C,0xBB38]}, /* 때문 */ {keys: ['a','i','j','s'], chars: [0xB3D9,0xC548]}, /* 동안 */ {keys: ['e','i','j','x'], chars: [0xB3C5,0xC77C]}, /* 독일 */ {keys: ['i','j','x','z'], chars: [0xB3C5,0xC77C]}, /* 독일 */ {keys: ['i','z'], chars: [0xB2E4,0xC74C]}, /* 다음 */ {keys: ['a','i','j'], chars: [0xC6B4,0xB3D9]}, /* 운동 */ {keys: [';','i','j','s'], chars: [0xC5B4,0xB5BB]}, /* 어떻 */ {keys: ['c','g','i','j'], chars: [0xC740,0xB370]}, /* 은데 */ {keys: ['c','f','i','j'], chars: [0xC5D0,0xB2E4,0x20]}, /* 에다 */ {keys: ['f','g','j'], chars: [0xC544,0xB4E4]}, /* 아들 */ {keys: ['i','m','x'], chars: [0xB3C4,0xB85D,0x20]}, /* 도록 */ {keys: ['e','i','m'], chars: [0xB2EC,0xB9AC]}, /* 달리 */ {keys: ['f','i','m'], chars: [0xB530,0xB77C,0x20]}, /* 따라 */ {keys: ['e','f','i','m'], chars: [0xB2EC,0xB77C]}, /* 달라 */ {keys: ['d','f','i','m'], chars: [0xB300,0xB85C,0x20]}, /* 대로 */ {keys: ['i','m','r'], chars: [0xB530,0xB77C,0xC11C,0x20]}, /* 따라서 */ {keys: ['c','i','m'], chars: [0xD154,0xB808,0xBE44,0xC804]}, /* 텔레비전 */ {keys: ['i','m','v'], chars: [0xB77C,0xB3C4,0x20]}, /* 라도 */ {keys: ['f','i','m','r'], chars: [0xB354,0xB77C,0x2E,0x20]}, /* 더라. */ {keys: ['g','i','m'], chars: [0xADF8,0xB300,0xB85C,0x20]}, /* 그대로 */ {keys: ['d','i','m'], chars: [0xB2E4,0xB9AC]}, /* 다리 */ {keys: ['g','i','m','r'], chars: [0xB4DC,0xB7EC]}, /* 드러 */ {keys: ['i','n','x'], chars: [0xC18D,0xB3C4]}, /* 속도 */ {keys: ['i','n','s'], chars: [0xB2F9,0xC2E0]}, /* 당신 */ {keys: ['a','i','n'], chars: [0xB2F9,0xC2DC]}, /* 당시 */ {keys: ['f','i','n'], chars: [0xC2DC,0xB2E4,0x2E,0x20]}, /* 시다. */ {keys: ['a','f','i','n'], chars: [0xB300,0xC0C1]}, /* 대상 */ {keys: ['d','f','i','n'], chars: [0xB2E4,0xC2DC,0x20]}, /* 다시 */ {keys: ['a','d','f','i','n'], chars: [0xB3D9,0xC0DD]}, /* 동생 */ {keys: ['i','n','v'], chars: [0xB3C4,0xC2DC]}, /* 도시 */ {keys: ['a','i','n','v'], chars: [0xB3D9,0xC2DC]}, /* 동시 */ {keys: ['b','i','n'], chars: [0xC218,0xB3C4]}, /* 수도 */ {keys: ['d','i','n'], chars: [0xC2DC,0xB300]}, /* 시대 */ {keys: ['d','i','n','s'], chars: [0xB300,0xC2E0]}, /* 대신 */ {keys: ['d','f','i','l','x'], chars: [0xB300,0xCC45]}, /* 대책 */ {keys: ['i','l','s'], chars: [0xB4E0,0xC9C0,0x20]}, /* 든지 */ {keys: ['i','l','w'], chars: [0xC9D1,0xB2E8]}, /* 집단 */ {keys: ['f','i','l','v'], chars: [0xCCAD,0xC640,0xB300]}, /* 청와대 */ {keys: ['.','f','i','l'], chars: [0xCCAD,0xC640,0xB300]}, /* 청와대 */ {keys: ['f','i','l'], chars: [0xC790,0xB3D9,0xCC28]}, /* 자동차 */ {keys: ['f','i','l','s'], chars: [0xB2E8,0xC9C0]}, /* 단지 */ {keys: ['d','f','i','l'], chars: [0xC81C,0xB300,0xB85C,0x20]}, /* 제대로 */ {keys: ['i','l','r','s'], chars: [0xC804,0xB3D9]}, /* 전통 */ {keys: ['a','i','l','r'], chars: [0xC815,0xB2F9]}, /* 정당 */ {keys: ['c','i','l'], chars: [0xC81C,0xB3C4]}, /* 제도 */ {keys: ['i','l','v'], chars: [0xD1A0,0xC9C0]}, /* 토지 */ {keys: ['b','i','l'], chars: [0xD22C,0xC7C1]}, /* 투쟁 */ {keys: ['a','b','i','l'], chars: [0xB300,0xC911]}, /* 대중 */ {keys: ['b','f','i','l'], chars: [0xD22C,0xC790]}, /* 투자 */ {keys: ['f','i','l','p'], chars: [0xD22C,0xC790]}, /* 투자 */ {keys: ['g','i','l','x'], chars: [0xD2B9,0xC9D5]}, /* 특징 */ {keys: ['a','g','i','l'], chars: [0xB4F1,0xC7A5]}, /* 등장 */ {keys: ['d','i','l'], chars: [0xC9C0,0xB3C4]}, /* 지도 */ {keys: ['c','f','i','l'], chars: [0xB300,0xCCB4]}, /* 대체 */ /* 첫소리 ㄹ 조합 */ {keys: ['m','s'], chars: [0xC774,0xB7F0,0x20]}, /* 이런 */ {keys: ['e','m'], chars: [0xB2EC,0xB7EC]}, /* 달러 */ {keys: ['m','z'], chars: [0xC5EC,0xB984]}, /* 여름 */ {keys: ['k','m','x'], chars: [0xAD8C,0xB825]}, /* 권력 */ {keys: ['k','m','s'], chars: [0xADF8,0xB7F0,0x20]}, /* 그런 */ {keys: ['e','k','m'], chars: [0xC774,0xB370,0xC62C,0xB85C,0xAE30]}, /* 이데올로기 */ {keys: ['k','m','z'], chars: [0xADF8,0xB7A8]}, /* 그램 */ {keys: ['k','m','w'], chars: [0xADF8,0xB8F9]}, /* 그룹 */ {keys: [';','k','m','s'], chars: [0xADF8,0xB807]}, /* 그렇 */ {keys: ['f','k','m'], chars: [0xADF8,0xB7EC,0xB098,0x20]}, /* 그러나 */ {keys: ['f','k','m','x'], chars: [0xAC00,0xB77D]}, /* 가락 */ {keys: ['d','f','k','m'], chars: [0xADF8,0xB798]}, /* 그래 */ {keys: ['k','m','t','v'], chars: [0xACE0,0xB824]}, /* 고려 */ {keys: ['.','k','m','t'], chars: [0xACE0,0xB824]}, /* 고려 */ {keys: ['k','m','r'], chars: [0xADF8,0xB798,0xC11C,0x20]}, /* 그래서 */ {keys: ['k','m','r','z'], chars: [0xADF8,0xB7FC,0x20]}, /* 그럼 */ {keys: ['c','k','m'], chars: [0xADF8,0xB7F0,0xB370,0x20]}, /* 그런데 */ {keys: ['k','m','t'], chars: [0xB824,0xACE0,0x20]}, /* 려고 */ {keys: ['k','m','v'], chars: [0xADF8,0xB9AC,0xACE0,0x20]}, /* 그리고 */ {keys: ['k','m','v','x'], chars: [0xAE30,0xB85D]}, /* 기록 */ {keys: ['f','k','m','v'], chars: [0xB77C,0xACE0]}, /* 라고 */ {keys: ['.','f','k','m'], chars: [0xB77C,0xACE0]}, /* 라고 */ {keys: ['k','m','s','t'], chars: [0xAD00,0xB828]}, /* 관련 */ {keys: ['f','k','m','s','v'], chars: [0xAD00,0xB9AC]}, /* 관리 */ {keys: ['.','f','k','m','s'], chars: [0xAD00,0xB9AC]}, /* 관리 */ {keys: ['b','k','m'], chars: [0xACE0,0xAD6C,0xB824]}, /* 고구려 */ {keys: ['g','k','m'], chars: [0xADF8,0xB798,0xB3C4,0x20]}, /* 그래도 */ {keys: ['d','g','k','m'], chars: [0xADF8,0xB9AC]}, /* 그리 */ {keys: ['b','g','k','m'], chars: [0xAD6C,0xB984]}, /* 구름 */ {keys: ['g','k','m','p'], chars: [0xAD6C,0xB984]}, /* 구름 */ {keys: ['d','k','m','z'], chars: [0xADF8,0xB9BC]}, /* 그림 */ {keys: ['f','g','k','m'], chars: [0xAC00,0xB974]}, /* 가르 */ {keys: ['m','s','y'], chars: [0xBB3C,0xB860,0x20]}, /* 물론 */ {keys: ['e','m','y'], chars: [0xBA40,0xB9AC,0x20]}, /* 멀리 */ {keys: ['a','m','y'], chars: [0xBA85,0xB839]}, /* 명령 */ {keys: ['f','m','s','y'], chars: [0xB9C8,0xB828]}, /* 마련 */ {keys: ['e','f','m','y'], chars: [0xC57C,0xB9D0,0xB85C,0x20]}, /* 야말로 */ {keys: ['m','r','y'], chars: [0xBA38,0xB9AC]}, /* 머리 */ {keys: ['m','t','y'], chars: [0xB824,0xBA74,0x20]}, /* 려면 */ {keys: ['m','s','t','y'], chars: [0xB77C,0xBA74]}, /* 라면 */ {keys: ['b','m','y'], chars: [0xC544,0xBB34,0xB7F0,0x20]}, /* 아무런 */ {keys: ['b','e','m','y'], chars: [0xBB3C,0xB9AC]}, /* 물리 */ {keys: ['b','d','m','y'], chars: [0xBB34,0xB9AC]}, /* 무리 */ {keys: ['d','m','p','y'], chars: [0xBB34,0xB9AC]}, /* 무리 */ {keys: ['g','m','y'], chars: [0xBBC0,0xB85C,0x20]}, /* 므로 */ {keys: ['m','o','x'], chars: [0xBE44,0xB85D,0x20]}, /* 비록 */ {keys: ['e','m','o'], chars: [0xBE68,0xB9AC,0x20]}, /* 빨리 */ {keys: ['m','o','z'], chars: [0xBC14,0xB78C]}, /* 바람 */ {keys: ['m','o','q'], chars: [0xBE44,0xB86F]}, /* 비롯 */ {keys: ['a','m','o'], chars: [0xD504,0xB791,0xC2A4]}, /* 프랑스 */ {keys: ['f','m','o'], chars: [0xBC14,0xB77C]}, /* 바라 */ {keys: ['m','o','r','w'], chars: [0xBC95,0xB960]}, /* 법률 */ {keys: ['e','m','o','t'], chars: [0xBCC4,0xB85C,0x20]}, /* 별로 */ {keys: ['m','o','v'], chars: [0xBC14,0xB85C,0x20]}, /* 바로 */ {keys: ['l','m','s'], chars: [0xC804,0xB7B5]}, /* 전략 */ {keys: ['l','m','z'], chars: [0xCC98,0xB7FC,0x20]}, /* 처럼 */ {keys: ['a','l','m'], chars: [0xC885,0xB958]}, /* 종류 */ {keys: ['f','l','m'], chars: [0xC790,0xB8CC]}, /* 자료 */ {keys: ['d','f','l','m'], chars: [0xC7AC,0xB8CC]}, /* 재료 */ {keys: ['l','m','r'], chars: [0xCC98,0xB9AC]}, /* 처리 */ {keys: ['c','l','m','v'], chars: [0xCC28,0xB840]}, /* 차례 */ {keys: ['b','l','m'], chars: [0xC8FC,0xB85C,0x20]}, /* 주로 */ {keys: ['d','l','m'], chars: [0xC790,0xB9AC]}, /* 자리 */ {keys: ['a','h','m'], chars: [0xD6CC,0xB96D]}, /* 훌륭 */ {keys: ['f','h','m'], chars: [0xD558,0xB8E8]}, /* 하루 */ {keys: ['h','m','r'], chars: [0xD5C8,0xB9AC]}, /* 허리 */ {keys: ['g','m','o'], chars: [0xD504,0xB85C]}, /* 프로 */ {keys: ['d','h','m'], chars: [0xCC28,0xB77C,0xB9AC,0x20]}, /* 차라리 */ {keys: ['m','u','x'], chars: [0xB178,0xB825]}, /* 노력 */ {keys: ['a','m','u'], chars: [0xB2A5,0xB825]}, /* 능력 */ {keys: ['f','m','u'], chars: [0xB098,0xB77C]}, /* 나라 */ {keys: ['d','f','m','u'], chars: [0xB0B4,0xB824]}, /* 내려 */ {keys: ['m','u','v'], chars: [0xB178,0xB798]}, /* 노래 */ {keys: ['m','u','z'], chars: [0xB098,0xB984]}, /* 나름 */ /* 첫소리 ㅁ 조합 */ {keys: ['x','y'], chars: [0xBBF8,0xAD6D]}, /* 미국 */ {keys: ['s','x','y'], chars: [0xB9CC,0xC57D]}, /* 만약 */ {keys: ['s','y'], chars: [0xBB38,0xD654]}, /* 문화 */ {keys: ['e','s','y'], chars: [0xBB3C,0xAC74]}, /* 물건 */ {keys: ['a','s','y'], chars: [0xB9CE,0xC774,0x20]}, /* 많이 */ {keys: [';','y','z'], chars: [0xBBFF,0xC74C]}, /* 믿음 */ {keys: ['e','y'], chars: [0xBB3C,0xC9C8]}, /* 물질 */ {keys: ['y','z'], chars: [0xB9C8,0xC74C]}, /* 마음 */ {keys: ['w','y'], chars: [0xC5C5,0xBB34]}, /* 업무 */ {keys: ['q','y'], chars: [0xBB34,0xC5C7]}, /* 무엇 */ {keys: [';','y'], chars: [0xB9DB,0xC788]}, /* 맛있 */ {keys: ['a','y'], chars: [0xBAA8,0xC591]}, /* 모양 */ {keys: ['b','f','y'], chars: [0xB9E4,0xC6B0,0x20]}, /* 매우 */ {keys: ['f','p','y'], chars: [0xB9E4,0xC6B0,0x20]}, /* 매우 */ {keys: ['g','v','y'], chars: [0xBAA8,0xB4E0,0x20]}, /* 모든 */ {keys: ['.','g','y'], chars: [0xBAA8,0xB4E0,0x20]}, /* 모든 */ {keys: ['c','d','y'], chars: [0xBA54,0xC77C]}, /* 메일 */ {keys: ['f','g','y'], chars: [0xB9C8,0xC744]}, /* 마을 */ {keys: ['o','x','y'], chars: [0xBAA9,0xD45C]}, /* 목표 */ {keys: ['a','o','y'], chars: [0xBD84,0xBA85]}, /* 분명 */ {keys: ['o','s','y'], chars: [0xBC18,0xBA74]}, /* 반면 */ {keys: ['b','o','y'], chars: [0xBD80,0xBAA8]}, /* 부모 */ {keys: ['l','x','y'], chars: [0xBBFC,0xC871]}, /* 민족 */ {keys: ['l','s','x','y'], chars: [0xB9CC,0xC871]}, /* 만족 */ {keys: ['l','s','y'], chars: [0xC9C0,0xB9CC,0x20]}, /* 지만 */ {keys: ['e','l','y'], chars: [0xC815,0xB9D0]}, /* 정말 */ {keys: ['l','y','z'], chars: [0xB9C8,0xCE68,0x20]}, /* 마침 */ {keys: ['f','l','s','y'], chars: [0xB9C8,0xCC2C,0xAC00,0xC9C0]}, /* 마찬가지 */ {keys: ['f','l','x','y'], chars: [0xB9C8,0xC9C0,0xB9C9]}, /* 마지막 */ {keys: ['d','f','l','y'], chars: [0xC7AC,0xBBF8]}, /* 재미 */ {keys: ['l','r','y'], chars: [0xB9C8,0xC800,0x20]}, /* 마저 */ {keys: ['l','r','s','y'], chars: [0xBA3C,0xC800]}, /* 먼저 */ {keys: ['c','l','y'], chars: [0xBB38,0xC81C]}, /* 문제 */ {keys: ['l','t','y'], chars: [0xBA70,0xCE60]}, /* 며칠 */ {keys: ['l','s','t','y'], chars: [0xC790,0xBA74,0x20]}, /* 자면 */ {keys: ['l','v','x','y'], chars: [0xBAA9,0xC801]}, /* 목적 */ {keys: ['b','l','y'], chars: [0xC8FC,0xBBFC]}, /* 주민 */ {keys: ['b','l','r','s','y'], chars: [0xC804,0xBB38]}, /* 전문 */ {keys: ['l','p','r','s','y'], chars: [0xC804,0xBB38]}, /* 전문 */ {keys: ['d','l','y'], chars: [0xB9C8,0xCE58,0x20]}, /* 마치 */ {keys: ['d','l','s','y'], chars: [0xBBFC,0xC8FC]}, /* 민주 */ {keys: ['d','e','l','y'], chars: [0xC9C8,0xBB38]}, /* 질문 */ {keys: ['a','b','l','y'], chars: [0xBBFC,0xC911]}, /* 민중 */ {keys: ['e','u','y'], chars: [0xB208,0xBB3C]}, /* 눈물 */ {keys: ['a','u','y'], chars: [0xB18D,0xBBFC]}, /* 농민 */ {keys: ['b','f','u','y'], chars: [0xB098,0xBB34]}, /* 나무 */ {keys: ['f','p','u','y'], chars: [0xB098,0xBB34]}, /* 나무 */ {keys: ['r','u','y'], chars: [0xB108,0xBB34,0x20]}, /* 너무 */ {keys: ['b','u','y'], chars: [0xC8FC,0xBA38,0xB2C8]}, /* 주머니 */ /* 첫소리 ㅂ 조합 */ {keys: ['a','o','x'], chars: [0xBC16,0xC5D0,0x20]}, /* 밖에 */ {keys: ['o','s'], chars: [0xBD80,0xBD84]}, /* 부분 */ {keys: [';','o','z'], chars: [0xBC1B,0xC544]}, /* 받아 */ {keys: ['e','o'], chars: [0xBC1C,0xD45C]}, /* 발표 */ {keys: ['e','o','w'], chars: [0xBD88,0xBC95]}, /* 불법 */ {keys: ['o','w'], chars: [0xBC29,0xBC95]}, /* 방법 */ {keys: ['e','k','o'], chars: [0xAC1C,0xBC1C]}, /* 개발 */ {keys: ['a','k','o'], chars: [0xACF5,0xBD80]}, /* 공부 */ {keys: ['e','f','k','o'], chars: [0xBC1C,0xACAC]}, /* 발견 */ {keys: ['k','o','r'], chars: [0xCEE4,0xD53C]}, /* 커피 */ {keys: ['a','k','o','t'], chars: [0xD3C9,0xAC00]}, /* 평가 */ {keys: ['k','o','s','v'], chars: [0xAE30,0xBCF8]}, /* 기본 */ {keys: ['a','k','o','v'], chars: [0xACF5,0xD3EC]}, /* 공포 */ {keys: ['f','k','o','v'], chars: [0xBD88,0xACFC]}, /* 불과 */ {keys: ['.','f','k','o'], chars: [0xBD88,0xACFC]}, /* 불과 */ {keys: ['b','k','o'], chars: [0xBD88,0xAD6C]}, /* 불구 */ {keys: ['b','e','k','o'], chars: [0xBD88,0xAD50]}, /* 불교 */ {keys: ['d','k','o'], chars: [0xAE30,0xBD84]}, /* 기분 */ {keys: ['i','o','x'], chars: [0xD2B9,0xBCC4]}, /* 특별 */ {keys: ['i','o','s'], chars: [0xB300,0xBD80,0xBD84]}, /* 대부분 */ {keys: ['e','i','o'], chars: [0xBC1C,0xB2EC]}, /* 발달 */ {keys: ['i','o','z'], chars: [0xB2F4,0xBC30]}, /* 담배 */ {keys: ['a','i','o'], chars: [0xBC14,0xD0D5]}, /* 바탕 */ {keys: ['f','i','o'], chars: [0xBC14,0xB2E4]}, /* 바다 */ {keys: ['g','i','o','s'], chars: [0xBC18,0xB4DC,0xC2DC,0x20]}, /* 반드시 */ {keys: ['d','f','i','o','s'], chars: [0xBC18,0xB300]}, /* 반대 */ {keys: ['i','o','r'], chars: [0xD37C,0xC13C,0xD2B8]}, /* 퍼센트 */ {keys: ['i','o','v'], chars: [0xBCF4,0xB2E4,0x20]}, /* 보다 */ {keys: ['f','i','o','s','v'], chars: [0xBC18,0xB3C4]}, /* 반도 */ {keys: ['.','f','i','o','s'], chars: [0xBC18,0xB3C4]}, /* 반도 */ {keys: ['f','i','o','r'], chars: [0xB300,0xD45C]}, /* 대표 */ {keys: ['.','i','o','v'], chars: [0xB300,0xD45C]}, /* 대표 */ {keys: ['b','i','o'], chars: [0xBD80,0xD130,0x20]}, /* 부터 */ {keys: ['g','i','o'], chars: [0xBD80,0xB4DC]}, /* 부드 */ {keys: ['d','g','i','o'], chars: [0xD2F0,0xBE0C,0xC774]}, /* 티브이 */ {keys: ['j','o','x'], chars: [0xD30C,0xC545]}, /* 파악 */ {keys: ['j','o','s'], chars: [0xC774,0xBC88,0x20]}, /* 이번 */ {keys: ['e','j','o','s'], chars: [0xC77C,0xBC18]}, /* 일반 */ {keys: ['a','j','o','s'], chars: [0xBC29,0xC548]}, /* 방안 */ {keys: ['e','j','o'], chars: [0xC77C,0xBCF8]}, /* 일본 */ {keys: ['j','o','q'], chars: [0xBC97,0xC5B4]}, /* 벗어 */ {keys: ['a','j','o'], chars: [0xBCD1,0xC6D0]}, /* 병원 */ {keys: ['b','f','j','o'], chars: [0xBC30,0xC6B0]}, /* 배우 */ {keys: ['f','j','o','p'], chars: [0xBC30,0xC6B0]}, /* 배우 */ {keys: ['n','o','x'], chars: [0xC0C8,0xBCBD]}, /* 새벽 */ {keys: ['n','o','s'], chars: [0xBD80,0xC0B0]}, /* 부산 */ {keys: ['e','n','o'], chars: [0xC0B4,0xD3B4]}, /* 살펴 */ {keys: ['n','o','q'], chars: [0xBE44,0xC2B7]}, /* 비슷 */ {keys: ['a','n','o'], chars: [0xBC29,0xC2DD]}, /* 방식 */ {keys: ['e','f','n','o'], chars: [0xBC1C,0xC0DD]}, /* 발생 */ {keys: ['a','f','n','o'], chars: [0xBC29,0xC1A1]}, /* 방송 */ {keys: ['n','o','r','s'], chars: [0xC120,0xBC30]}, /* 선배 */ {keys: ['e','n','o','r'], chars: [0xBC8C,0xC368,0x20]}, /* 벌써 */ {keys: ['n','o','v'], chars: [0xC18C,0xBE44]}, /* 소비 */ {keys: ['b','n','o'], chars: [0xBD80,0xC0C1]}, /* 부상 */ {keys: ['b','n','o','s'], chars: [0xBD84,0xC11D]}, /* 분석 */ {keys: ['g','n','o','r'], chars: [0xBC84,0xC2A4]}, /* 버스 */ {keys: ['l','o','x'], chars: [0xC791,0xD488]}, /* 작품 */ {keys: ['l','o','s'], chars: [0xC8FC,0xBCC0]}, /* 주변 */ {keys: ['e','l','o'], chars: [0xBC1C,0xC804]}, /* 발전 */ {keys: ['l','o','z'], chars: [0xBC29,0xCE68]}, /* 방침 */ {keys: ['a','l','o'], chars: [0xC815,0xBD80]}, /* 정부 */ {keys: ['f','l','o'], chars: [0xC544,0xBC84,0xC9C0]}, /* 아버지 */ {keys: ['d','f','l','o'], chars: [0xC9C0,0xBC30]}, /* 지배 */ {keys: ['d','f','l','o','x'], chars: [0xBC31,0xC81C]}, /* 백제 */ {keys: ['l','o','r','s'], chars: [0xBC88,0xC9F8]}, /* 번째 */ {keys: ['e','f','l','o','r'], chars: [0xC7AC,0xBC8C]}, /* 재벌 */ {keys: ['l','o','v'], chars: [0xC815,0xBCF4]}, /* 정보 */ {keys: ['c','l','o'], chars: [0xC81C,0xD488]}, /* 제품 */ {keys: ['l','o','s','t'], chars: [0xD3B8,0xC9C0]}, /* 편지 */ {keys: ['b','l','o','x'], chars: [0xBD80,0xC871]}, /* 부족 */ {keys: ['l','o','s','v'], chars: [0xC790,0xBCF8]}, /* 자본 */ {keys: ['b','l','o','s'], chars: [0xC900,0xBE44]}, /* 준비 */ {keys: ['b','f','l','o'], chars: [0xBD80,0xC790]}, /* 부자 */ {keys: ['f','l','o','p'], chars: [0xBD80,0xC790]}, /* 부자 */ {keys: ['b','l','o','r'], chars: [0xBD80,0xC815]}, /* 부정 */ {keys: ['l','o','p','r'], chars: [0xBD80,0xC815]}, /* 부정 */ {keys: ['a','f','l','o'], chars: [0xC9C0,0xBC29]}, /* 지방 */ {keys: ['o','u','z'], chars: [0xB0A8,0xD3B8]}, /* 남편 */ /* 첫소리 ㅅ 조합 */ {keys: ['n','x'], chars: [0xC2DD,0xC0AC]}, /* 식사 */ {keys: ['n','s'], chars: [0xC120,0xC0DD]}, /* 선생 */ {keys: ['a','n','s'], chars: [0xC0DD,0xC0B0]}, /* 생산 */ {keys: ['e','n'], chars: [0xC0AC,0xC2E4]}, /* 사실 */ {keys: ['n','z'], chars: [0xC0AC,0xB78C]}, /* 사람 */ {keys: ['n','w'], chars: [0xC2B5,0xB2C8,0xB2E4,0x2E,0x20]}, /* 습니다. */ {keys: ['a','n'], chars: [0xC138,0xC0C1]}, /* 세상 */ {keys: ['b','f','n'], chars: [0xC218,0xC0AC]}, /* 수사 */ {keys: ['f','n','p'], chars: [0xC218,0xC0AC]}, /* 수사 */ {keys: ['c','f','n'], chars: [0xC138,0xC694]}, /* 세요 */ {keys: ['k','n','x'], chars: [0xC0DD,0xAC01]}, /* 생각 */ {keys: ['k','n','s'], chars: [0xC2DC,0xAC04]}, /* 시간 */ {keys: ['e','k','n'], chars: [0xAE30,0xC220]}, /* 기술 */ {keys: ['k','n','x','z'], chars: [0xC0BC,0xAD6D]}, /* 삼국 */ {keys: ['k','n','z'], chars: [0xAD00,0xC2EC]}, /* 관심 */ {keys: ['a','k','n'], chars: [0xC131,0xACA9]}, /* 성격 */ {keys: ['f','k','n','s'], chars: [0xC0AC,0xAC74]}, /* 사건 */ {keys: ['f','k','n','z'], chars: [0xAC10,0xC0AC]}, /* 감사 */ {keys: ['a','f','k','n'], chars: [0xC0C1,0xAD00]}, /* 상관 */ {keys: ['f','k','n'], chars: [0xAE30,0xC0AC]}, /* 기사 */ {keys: ['d','f','k','n','x'], chars: [0xC2DC,0xAC01]}, /* 시각 */ {keys: ['a','d','f','k','n'], chars: [0xAE30,0xC0C1]}, /* 기상 */ {keys: ['k','n','r'], chars: [0xACE0,0xC11C,0x20]}, /* 고서 */ {keys: ['k','n','r','s'], chars: [0xC120,0xAC70]}, /* 선거 */ {keys: ['e','k','n','r'], chars: [0xAC74,0xC124]}, /* 건설 */ {keys: ['a','k','n','r'], chars: [0xC131,0xACBD]}, /* 성경 */ {keys: ['c','k','n'], chars: [0xC138,0xACC4]}, /* 세계 */ {keys: ['e','k','n','t'], chars: [0xACB0,0xC2EC]}, /* 결심 */ {keys: ['a','k','n','t'], chars: [0xACBD,0xC0C1]}, /* 경상 */ {keys: ['k','n','v','x'], chars: [0xACC4,0xC18D]}, /* 계속 */ {keys: ['k','n','v'], chars: [0xC0AC,0xACE0]}, /* 사고 */ {keys: ['a','k','n','v'], chars: [0xC131,0xACF5]}, /* 성공 */ {keys: ['a','f','k','n','v'], chars: [0xACF5,0xC0AC]}, /* 공사 */ {keys: ['.','a','f','k','n'], chars: [0xACF5,0xC0AC]}, /* 공사 */ {keys: ['f','k','n','r'], chars: [0xAD50,0xC218]}, /* 교수 */ {keys: ['.','k','n','v'], chars: [0xAD50,0xC218]}, /* 교수 */ {keys: ['a','b','k','n'], chars: [0xAD6C,0xC131]}, /* 구성 */ {keys: ['f','k','n','r'], chars: [0xAD50,0xC0AC]}, /* 교사 */ {keys: ['.','k','n','v'], chars: [0xAD50,0xC0AC]}, /* 교사 */ {keys: ['b','k','n','s'], chars: [0xC21C,0xAC04]}, /* 순간 */ {keys: ['b','k','n','v'], chars: [0xC218,0xACE0]}, /* 수고 */ {keys: ['k','n','p','v'], chars: [0xC218,0xACE0]}, /* 수고 */ {keys: ['.','b','k','n'], chars: [0xC218,0xACE0]}, /* 수고 */ {keys: ['g','k','n'], chars: [0xC4F0,0xB808,0xAE30]}, /* 쓰레기 */ {keys: ['e','g','k','n'], chars: [0xAE00,0xC384]}, /* 글쎄 */ {keys: ['g','k','n','z'], chars: [0xAC00,0xC2B4]}, /* 가슴 */ {keys: ['c','d','k','n'], chars: [0xC138,0xAE30]}, /* 세기 */ {keys: ['d','k','n','s'], chars: [0xC2E0,0xACBD]}, /* 신경 */ {keys: ['d','k','n','z'], chars: [0xC2EC,0xAC01]}, /* 심각 */ {keys: ['c','k','n','r'], chars: [0xC5D0,0xAC8C,0xC11C,0x20]}, /* 에게서 */ {keys: ['c','k','n','v'], chars: [0xC2DC,0xACC4]}, /* 시계 */ {keys: ['.','c','k','n'], chars: [0xC2DC,0xACC4]}, /* 시계 */ {keys: ['n','s','u'], chars: [0xC544,0xB098,0xC6B4,0xC11C]}, /* 아나운서 */ {keys: ['n','u','z'], chars: [0xC120,0xC0DD,0xB2D8]}, /* 선생님 */ {keys: ['a','n','u'], chars: [0xAC00,0xB2A5,0xC131]}, /* 가능성 */ {keys: ['f','n','u'], chars: [0xC0AC,0xB0B4]}, /* 사내 */ {keys: ['d','f','n','u','z'], chars: [0xB0C4,0xC0C8]}, /* 냄새 */ {keys: ['n','t','u'], chars: [0xC18C,0xB140]}, /* 소녀 */ {keys: ['n','s','t','u'], chars: [0xC18C,0xB144]}, /* 소년 */ {keys: ['n','s','u','v'], chars: [0xC190,0xB2D8]}, /* 손님 */ {keys: ['a','n','u','v'], chars: [0xB18D,0xC0AC]}, /* 농사 */ {keys: ['b','n','u','v'], chars: [0xB274,0xC2A4]}, /* 뉴스 */ {keys: ['.','b','n','u'], chars: [0xB274,0xC2A4]}, /* 뉴스 */ {keys: ['m','n','x'], chars: [0xC138,0xB825]}, /* 세력 */ {keys: ['m','n','s'], chars: [0xC2E0,0xB77C]}, /* 신라 */ {keys: ['a','m','n'], chars: [0xC0AC,0xB791]}, /* 사랑 */ {keys: ['f','m','n'], chars: [0xC0AC,0xB77C]}, /* 사라 */ {keys: ['d','f','m','n'], chars: [0xC0C8,0xB85C,0x20]}, /* 새로 */ {keys: ['m','n','r','v'], chars: [0xC11C,0xB85C,0x20]}, /* 서로 */ {keys: ['.','m','n','r'], chars: [0xC11C,0xB85C,0x20]}, /* 서로 */ {keys: ['m','n','r'], chars: [0xB85C,0xC368,0x20]}, /* 로써 */ {keys: ['m','n','s','t'], chars: [0xC18C,0xB828]}, /* 소련 */ {keys: ['m','n','v'], chars: [0xB85C,0xC11C,0x20]}, /* 로서 */ {keys: ['b','m','n'], chars: [0xC218,0xB85D,0x20]}, /* 수록 */ {keys: ['b','d','m','n'], chars: [0xC218,0xB9AC]}, /* 수리 */ {keys: ['d','m','n','p'], chars: [0xC218,0xB9AC]}, /* 수리 */ {keys: ['g','m','n'], chars: [0xC2A4,0xC2A4,0xB85C]}, /* 스스로 */ {keys: ['d','m','n'], chars: [0xC18C,0xB9AC]}, /* 소리 */ {keys: ['n','x','y'], chars: [0xBAA9,0xC18C,0xB9AC]}, /* 목소리 */ {keys: ['n','s','y'], chars: [0xBB34,0xC2A8,0x20]}, /* 무슨 */ {keys: ['e','n','s','y'], chars: [0xC120,0xBB3C]}, /* 선물 */ {keys: ['e','n','y'], chars: [0xB9D0,0xC500]}, /* 말씀 */ {keys: ['n','x','y','z'], chars: [0xBAA9,0xC228]}, /* 목숨 */ {keys: ['n','w','y'], chars: [0xBAA8,0xC2B5]}, /* 모습 */ {keys: ['a','n','y'], chars: [0xC0DD,0xBA85]}, /* 생명 */ {keys: ['e','n','r','y'], chars: [0xC124,0xBA85]}, /* 설명 */ {keys: ['n','s','t','y'], chars: [0xBA74,0xC11C,0x20]}, /* 면서 */ {keys: ['b','d','n','y'], chars: [0xBB34,0xC2DC]}, /* 무시 */ {keys: ['d','n','p','y'], chars: [0xBB34,0xC2DC]}, /* 무시 */ {keys: ['d','n','y'], chars: [0xC2DC,0xBBFC]}, /* 시민 */ {keys: ['d','n','x','y'], chars: [0xC2DD,0xBB3C]}, /* 식물 */ {keys: ['d','n','s','y'], chars: [0xC2E0,0xBB38]}, /* 신문 */ {keys: ['h','n','x'], chars: [0xD559,0xC0DD]}, /* 학생 */ {keys: ['h','n','s'], chars: [0xD604,0xC0C1]}, /* 현상 */ {keys: ['e','h','n'], chars: [0xD6E8,0xC52C,0x20]}, /* 훨씬 */ {keys: ['e','h','n','x'], chars: [0xD655,0xC2E4]}, /* 확실 */ {keys: ['h','n','x','z'], chars: [0xD655,0xC2E4]}, /* 확실 */ {keys: ['h','n','z'], chars: [0xC2DC,0xD5D8]}, /* 시험 */ {keys: ['a','h','n'], chars: [0xC0DD,0xD65C]}, /* 생활 */ {keys: ['f','h','n'], chars: [0xC0AC,0xD68C]}, /* 사회 */ {keys: ['d','f','h','n'], chars: [0xC0C1,0xD0DC]}, /* 상태 */ {keys: ['a','d','f','h','n'], chars: [0xD589,0xC0AC]}, /* 행사 */ {keys: ['c','h','n','s'], chars: [0xC13C,0xD2F0]}, /* 센티 */ {keys: ['h','n','s','t'], chars: [0xD604,0xC2E4]}, /* 현실 */ {keys: ['a','h','n','x'], chars: [0xD615,0xC2DD]}, /* 형식 */ {keys: ['a','f','h','n','v'], chars: [0xC0C1,0xD669]}, /* 상황 */ {keys: ['.','a','f','h','n'], chars: [0xC0C1,0xD669]}, /* 상황 */ {keys: ['d','h','n','v'], chars: [0xD68C,0xC0AC]}, /* 회사 */ {keys: ['.','d','h','n'], chars: [0xD68C,0xC0AC]}, /* 회사 */ {keys: ['c','h','n','v'], chars: [0xC218,0xD61C]}, /* 수혜 */ {keys: ['.','c','h','n'], chars: [0xC218,0xD61C]}, /* 수혜 */ {keys: ['d','e','h','n'], chars: [0xC2E4,0xCC9C]}, /* 실천 */ {keys: ['j','n','x'], chars: [0xC5ED,0xC0AC]}, /* 역사 */ {keys: ['j','n','s','x'], chars: [0xC778,0xC2DD]}, /* 인식 */ {keys: ['j','n','s'], chars: [0xC6B0,0xC120,0x20]}, /* 우선 */ {keys: ['a','j','n','s'], chars: [0xC778,0xC0DD]}, /* 인생 */ {keys: ['e','j','n'], chars: [0xC11C,0xC6B8]}, /* 서울 */ {keys: ['e','j','n','z'], chars: [0xC5F4,0xC2EC]}, /* 열심 */ {keys: ['j','n','s','w'], chars: [0xC0B0,0xC5C5]}, /* 산업 */ {keys: ['a','j','n'], chars: [0xC774,0xC0C1]}, /* 이상 */ {keys: ['b','f','j','n','z'], chars: [0xC2F8,0xC6C0]}, /* 싸움 */ {keys: ['f','j','n','p','z'], chars: [0xC2F8,0xC6C0]}, /* 싸움 */ {keys: ['c','e','j','n','r'], chars: [0xC138,0xC6D4]}, /* 세월 */ {keys: ['f','g','j','n'], chars: [0xC758,0xC0AC]}, /* 의사 */ {keys: ['c','j','n','r'], chars: [0xC5D0,0xC11C,0x20]}, /* 에서 */ {keys: ['l','n','x'], chars: [0xC2DC,0xC791]}, /* 시작 */ {keys: ['l','n','s'], chars: [0xC790,0xC2E0]}, /* 자신 */ {keys: ['e','l','n','s'], chars: [0xC9C4,0xC2E4]}, /* 진실 */ {keys: ['e','l','n','r'], chars: [0xC9C8,0xC11C]}, /* 질서 */ {keys: ['l','n','z'], chars: [0xC911,0xC2EC]}, /* 중심 */ {keys: ['a','l','n'], chars: [0xC2DC,0xC7A5]}, /* 시장 */ {keys: ['f','l','n'], chars: [0xC790,0xC2DD]}, /* 자식 */ {keys: ['f','l','n','z'], chars: [0xC7A0,0xC2DC]}, /* 잠시 */ {keys: ['a','f','l','n'], chars: [0xC131,0xC7A5]}, /* 성장 */ {keys: ['d','f','l','n'], chars: [0xC7AC,0xC0B0]}, /* 재산 */ {keys: ['a','d','f','l','n'], chars: [0xC9C0,0xC0C1]}, /* 지상 */ {keys: ['l','n','r'], chars: [0xC544,0xC800,0xC528]}, /* 아저씨 */ {keys: ['l','n','r','s'], chars: [0xC870,0xC120]}, /* 조선 */ {keys: ['a','l','n','s'], chars: [0xC815,0xC2E0]}, /* 정신 */ {keys: ['c','l','n'], chars: [0xC790,0xC138]}, /* 자세 */ {keys: ['l','n','v'], chars: [0xC870,0xC0AC]}, /* 조사 */ {keys: ['a','f','l','n','r'], chars: [0xC0AC,0xC815]}, /* 사정 */ {keys: ['b','l','n'], chars: [0xC218,0xC900]}, /* 수준 */ {keys: ['d','l','n','x'], chars: [0xC9C0,0xC2DD]}, /* 지식 */ {keys: ['d','l','n','s'], chars: [0xC0AC,0xC9C4]}, /* 사진 */ {keys: ['d','e','l','n'], chars: [0xC2E4,0xC81C]}, /* 실제 */ {keys: ['c','d','l','n'], chars: [0xC81C,0xC2DC]}, /* 제시 */ /* 첫소리 ㅇ 조합 */ {keys: ['j','x'], chars: [0xC74C,0xC545]}, /* 음악 */ {keys: ['j','s'], chars: [0xC5B4,0xB5A4,0x20]}, /* 어떤 */ {keys: ['e','j','s'], chars: [0xC778,0xBB3C]}, /* 인물 */ {keys: ['e','j'], chars: [0xC77C,0xC5B4]}, /* 일어 */ {keys: ['j','x','z'], chars: [0xC6C0,0xC9C1]}, /* 움직 */ {keys: ['j','z'], chars: [0xC74C,0xC2DD]}, /* 음식 */ {keys: ['j','q','w'], chars: [0xC5C6,0xC774,0x20]}, /* 없이 */ {keys: ['j','q','z'], chars: [0xC6C3,0xC74C]}, /* 웃음 */ {keys: ['a','j'], chars: [0xC5EC,0xC131]}, /* 여성 */ {keys: [';','j','s'], chars: [0xC774,0xB807]}, /* 이렇 */ {keys: ['b','j','t'], chars: [0xC601,0xAD6D]}, /* 영국 */ {keys: ['j','p','t'], chars: [0xC601,0xAD6D]}, /* 영국 */ {keys: ['b','s','t','y'], chars: [0xC6B4,0xBA85]}, /* 운명 */ {keys: ['p','s','t','y'], chars: [0xC6B4,0xBA85]}, /* 운명 */ {keys: ['g','j','t'], chars: [0xC73C,0xBA70,0x20]}, /* 으며 */ {keys: ['g','j','s','t'], chars: [0xC73C,0xBA74,0x20]}, /* 으면 */ {keys: ['b','f','j'], chars: [0xC544,0xBB34]}, /* 아무 */ {keys: ['f','j','p'], chars: [0xC544,0xBB34]}, /* 아무 */ {keys: ['c','g','j'], chars: [0xC740,0xD61C]}, /* 은혜 */ {keys: ['c','f','j'], chars: [0xC5D0,0xC694,0x2E,0x20]}, /* 에요. */ {keys: ['c','j','r'], chars: [0xC608,0xC694,0x2E,0x20]}, /* 예요. */ {keys: ['c','d','j','l'], chars: [0xC774,0xC81C]}, /* 이제 */ {keys: ['j','l','r','t'], chars: [0xC5EC,0xC804]}, /* 여전 */ {keys: ['m','r','t'], chars: [0xC5EC,0xB7EC,0x20]}, /* 여러 */ {keys: ['j','r','t'], chars: [0xC601,0xC5B4]}, /* 영어 */ {keys: ['g','j','r'], chars: [0xC758,0xC6D0]}, /* 의원 */ {keys: ['j','m','s'], chars: [0xC774,0xB860]}, /* 이론 */ {keys: ['e','j','m'], chars: [0xC54C,0xB824]}, /* 알려 */ {keys: ['j','m','z'], chars: [0xC774,0xB984]}, /* 이름 */ {keys: ['j','m','w'], chars: [0xC720,0xB7FD]}, /* 유럽 */ {keys: ['f','j','m'], chars: [0xC544,0xB77C]}, /* 아라 */ {keys: ['f','j','m','s'], chars: [0xC774,0xB780]}, /* 이란 */ {keys: ['d','f','j','m'], chars: [0xC544,0xB798]}, /* 아래 */ {keys: ['j','m','r','s'], chars: [0xC5B8,0xB860]}, /* 언론 */ {keys: ['e','j','m','r'], chars: [0xB9AC,0xC5BC]}, /* 리얼 */ {keys: ['j','m','v'], chars: [0xC624,0xB798]}, /* 오래 */ {keys: ['e','j','m','v'], chars: [0xC62C,0xB77C]}, /* 올라 */ {keys: ['b','j','m'], chars: [0xC6B0,0xB9AC]}, /* 우리 */ {keys: ['b','j','m','r','s'], chars: [0xC6D0,0xB9AC]}, /* 원리 */ {keys: ['j','m','p','r','s'], chars: [0xC6D0,0xB9AC]}, /* 원리 */ {keys: ['b','j','m','v'], chars: [0xC778,0xB958]}, /* 인류 */ {keys: ['g','j','m'], chars: [0xC73C,0xB85C,0x20]}, /* 으로 */ {keys: ['g','j','m','s'], chars: [0xC5BC,0xB978,0x20]}, /* 얼른 */ {keys: ['f','g','j','m'], chars: [0xC544,0xB984]}, /* 아름 */ {keys: ['b','f','m'], chars: [0xC544,0xBB34,0xB9AC,0x20]}, /* 아무리 */ {keys: ['f','m','p'], chars: [0xC544,0xBB34,0xB9AC,0x20]}, /* 아무리 */ {keys: ['d','j','m','s'], chars: [0xC5B4,0xB9B0,0x20]}, /* 어린 */ {keys: ['g','j','m','r'], chars: [0xC5B4,0xB978]}, /* 어른 */ {keys: ['h','j','x'], chars: [0xC5ED,0xD560]}, /* 역할 */ {keys: ['h','j','s','x'], chars: [0xD655,0xC778]}, /* 확인 */ {keys: ['h','j','s'], chars: [0xCC28,0xC6D0]}, /* 차원 */ {keys: ['e','h','j'], chars: [0xC62C,0xD574]}, /* 올해 */ {keys: ['h','j','z'], chars: [0xCC45,0xC784]}, /* 책임 */ {keys: ['c','h','j'], chars: [0xC5C5,0xCCB4]}, /* 업체 */ {keys: ['a','h','j'], chars: [0xC601,0xD654]}, /* 영화 */ {keys: ['f','h','j'], chars: [0xC544,0xD30C,0xD2B8]}, /* 아파트 */ {keys: ['f','h','j','s'], chars: [0xB610,0xD55C,0x20]}, /* 또한 */ {keys: ['d','f','h','j'], chars: [0xCC28,0xC774]}, /* 차이 */ {keys: ['a','d','f','h','j'], chars: [0xD589,0xC704]}, /* 행위 */ {keys: ['h','j','r','z'], chars: [0xC704,0xD5D8]}, /* 위험 */ {keys: ['h','j','t'], chars: [0xC5EC,0xC804,0xD788]}, /* 여전히 */ {keys: ['h','j','t','w'], chars: [0xC704,0xD611]}, /* 위협 */ {keys: ['a','h','j','r'], chars: [0xC5C4,0xCCAD,0x20]}, /* 엄청 */ {keys: ['a','h','j','t'], chars: [0xC601,0xD5A5]}, /* 영향 */ {keys: ['h','j','v'], chars: [0xC624,0xD788,0xB824,0x20]}, /* 오히려 */ {keys: ['h','j','v','x'], chars: [0xD639,0xC740,0x20]}, /* 혹은 */ {keys: ['a','h','j','v'], chars: [0xB3D9,0xC77C]}, /* 통일 */ {keys: ['f','h','j','s','v'], chars: [0xC644,0xC804,0xD788,0x20]}, /* 완전히 */ {keys: ['.','f','h','j','s'], chars: [0xC644,0xC804,0xD788,0x20]}, /* 완전히 */ {keys: ['d','h','j','v'], chars: [0xD68C,0xC758]}, /* 회의 */ {keys: ['.','d','h','j'], chars: [0xD68C,0xC758]}, /* 회의 */ {keys: ['b','h','j'], chars: [0xC774,0xD6C4]}, /* 이후 */ {keys: ['b','h','j','r','s'], chars: [0xC6D0,0xD55C]}, /* 원한 */ {keys: ['h','j','p','r','s'], chars: [0xC6D0,0xD55C]}, /* 원한 */ {keys: ['b','d','h','j','s'], chars: [0xC704,0xC6D0,0xD68C]}, /* 위원회 */ {keys: ['d','h','j','p','s'], chars: [0xC704,0xC6D0,0xD68C]}, /* 위원회 */ {keys: ['b','h','j','v'], chars: [0xC624,0xD6C4]}, /* 오후 */ {keys: ['.','b','h','j'], chars: [0xC624,0xD6C4]}, /* 오후 */ {keys: ['h','j','p','v'], chars: [0xC624,0xD6C4]}, /* 오후 */ {keys: ['g','h','j','s'], chars: [0xC740,0xD589]}, /* 은행 */ {keys: ['d','h','j'], chars: [0xC774,0xD574]}, /* 이해 */ {keys: ['j','r','s','u'], chars: [0xC5B8,0xB2C8]}, /* 언니 */ {keys: ['e','j','u'], chars: [0xC5BC,0xB9C8,0xB098,0x20]}, /* 얼마나 */ {keys: ['j','u','z'], chars: [0xB118,0xC5B4]}, /* 넘어 */ {keys: ['a','j','u'], chars: [0xB0B4,0xC6A9]}, /* 내용 */ {keys: ['f','j','u'], chars: [0xC774,0xB098,0x20]}, /* 이나 */ {keys: ['e','f','j','u'], chars: [0xC774,0xB0A0]}, /* 이날 */ {keys: ['d','f','j','u'], chars: [0xC544,0xB0B4]}, /* 아내 */ {keys: ['j','r','u'], chars: [0xC5B4,0xBA38,0xB2C8]}, /* 어머니 */ {keys: ['j','u','v'], chars: [0xC624,0xB298]}, /* 오늘 */ {keys: ['a','j','u','v'], chars: [0xB18D,0xC5C5]}, /* 농업 */ {keys: ['e','g','j','u'], chars: [0xB298,0xC5B4]}, /* 늘어 */ {keys: ['d','g','j','u'], chars: [0xC73C,0xB2C8]}, /* 으니 */ {keys: ['d','j','u'], chars: [0xB2C8,0xAE4C]}, /* 니까 */ {keys: ['d','e','j','u'], chars: [0xB0B4,0xC77C]}, /* 내일 */ {keys: ['d','j','u','z'], chars: [0xB290,0xB08C]}, /* 느낌 */ /* 첫소리 ㅈ~ㅎ 조합 */ {keys: ['l','x'], chars: [0xC870,0xC9C1]}, /* 조직 */ {keys: ['a','l','x'], chars: [0xC9C1,0xC7A5]}, /* 직장 */ {keys: ['l','w','x'], chars: [0xC9C1,0xC811,0x20]}, /* 직접 */ {keys: ['l','s'], chars: [0xC874,0xC7AC]}, /* 존재 */ {keys: ['a','l','s'], chars: [0xC804,0xC7C1]}, /* 전쟁 */ {keys: ['e','l'], chars: [0xC2DC,0xC808]}, /* 시절 */ {keys: ['l','z'], chars: [0xC870,0xAE08]}, /* 조금 */ {keys: ['l','q'], chars: [0xC800,0xAC83]}, /* 저것 */ {keys: ['a','l'], chars: [0xC815,0xB3C4]}, /* 정도 */ {keys: [';','l','s'], chars: [0xC88B,0xC544]}, /* 좋아 */ {keys: ['b','f','l'], chars: [0xC790,0xC8FC]}, /* 자주 */ {keys: ['f','l','p'], chars: [0xC790,0xC8FC]}, /* 자주 */ {keys: ['a','b','f','l'], chars: [0xC8FC,0xC7A5]}, /* 주장 */ {keys: ['a','f','l','p'], chars: [0xC8FC,0xC7A5]}, /* 주장 */ {keys: ['c','f','l'], chars: [0xC790,0xCCB4]}, /* 자체 */ {keys: ['c','l','r'], chars: [0xC804,0xCCB4]}, /* 전체 */ {keys: ['c','d','l'], chars: [0xC9C0,0xD61C]}, /* 지혜 */ {keys: ['j','l','x'], chars: [0xC9C0,0xC5ED]}, /* 지역 */ {keys: ['a','j','l','x'], chars: [0xC791,0xC6A9]}, /* 작용 */ {keys: ['j','l','s'], chars: [0xC790,0xC5F0]}, /* 자연 */ {keys: ['a','j','l','s'], chars: [0xC778,0xC815]}, /* 인정 */ {keys: ['e','j','l'], chars: [0xC81C,0xC77C]}, /* 제일 */ {keys: ['j','l','x','z'], chars: [0xC8FD,0xC74C]}, /* 죽음 */ {keys: ['a','j','l','w'], chars: [0xC785,0xC7A5]}, /* 입장 */ {keys: ['j','l','z'], chars: [0xC544,0xCE68]}, /* 아침 */ {keys: ['j','l','w'], chars: [0xC791,0xC5C5]}, /* 작업 */ {keys: ['a','j','l'], chars: [0xC911,0xC694]}, /* 중요 */ {keys: [';','e','j','l'], chars: [0xCC3E,0xC544]}, /* 찾아 */ {keys: ['b','f','j','l'], chars: [0xC544,0xC8FC,0x20]}, /* 아주 */ {keys: ['f','j','l','p'], chars: [0xC544,0xC8FC,0x20]}, /* 아주 */ {keys: ['a','b','f','j','l'], chars: [0xC911,0xC559]}, /* 중앙 */ {keys: ['a','f','j','l','p'], chars: [0xC911,0xC559]}, /* 중앙 */ {keys: ['b','g','j','l'], chars: [0xC8FC,0xC758]}, /* 주의 */ {keys: ['g','j','l','p'], chars: [0xC8FC,0xC758]}, /* 주의 */ {keys: ['b','g','j','l'], chars: [0xC8FC,0xC758]}, /* 주의 */ {keys: ['g','j','l','p'], chars: [0xC8FC,0xC758]}, /* 주의 */ {keys: ['c','j','l','r'], chars: [0xC5B4,0xC81C]}, /* 어제 */ {keys: ['c','j','l','r','s'], chars: [0xC5B8,0xC81C]}, /* 언제 */ {keys: ['a','c','j','l','r'], chars: [0xC608,0xC815]}, /* 예정 */ {keys: ['f','g','j','l','z'], chars: [0xC694,0xC998]}, /* 요즘 */ {keys: ['g','j','l','r'], chars: [0xC815,0xC758]}, /* 정의 */ {keys: ['h','l','x'], chars: [0xC815,0xCC45]}, /* 정책 */ {keys: ['a','h','l','x'], chars: [0xC815,0xD655]}, /* 정확 */ {keys: ['a','h','l','s'], chars: [0xD604,0xC7A5]}, /* 현장 */ {keys: ['e','h','l','s'], chars: [0xCD9C,0xC2E0]}, /* 출신 */ {keys: ['h','l','z'], chars: [0xCC98,0xC74C]}, /* 처음 */ {keys: ['h','l','w'], chars: [0xC870,0xD569]}, /* 조합 */ {keys: ['a','h','l'], chars: [0xC815,0xCE58]}, /* 정치 */ {keys: ['h','l','r','t'], chars: [0xC804,0xD600,0x20]}, /* 전혀 */ {keys: ['h','k','x'], chars: [0xAC1C,0xD601]}, /* 개혁 */ {keys: ['h','k','s'], chars: [0xD658,0xACBD]}, /* 환경 */ {keys: ['e','h','k','s'], chars: [0xACB0,0xD63C]}, /* 결혼 */ {keys: ['h','k','z'], chars: [0xCEF4,0xD4E8,0xD130]}, /* 컴퓨터 */ {keys: ['a','k','r','t'], chars: [0xACBD,0xD5D8]}, /* 경험 */ {keys: ['c','f','h','k'], chars: [0xD55C,0xACC4]}, /* 한계 */ {keys: ['c','d','h','k'], chars: [0xCF00,0xC774]}, /* 케이 */ {keys: ['c','d','h','k','x'], chars: [0xACC4,0xD68D]}, /* 계획 */ {keys: ['h','k','r','t'], chars: [0xACBD,0xD5A5]}, /* 경향 */ {keys: ['f','g','h','k'], chars: [0xD55C,0xAE00]}, /* 한글 */ {keys: ['h','i','x'], chars: [0xD2B9,0xD788,0x20]}, /* 특히 */ {keys: ['a','i','n','x'], chars: [0xD2B9,0xC131]}, /* 특성 */ {keys: ['h','i','s','x'], chars: [0xB300,0xD55C,0xBBFC,0xAD6D]}, /* 대한민국 */ {keys: ['h','i','s'], chars: [0xB300,0xD55C]}, /* 대한 */ {keys: ['a','h','i','s'], chars: [0xB3D9,0xC2E0]}, /* 통신 */ {keys: ['e','h','i'], chars: [0xD65C,0xB3D9]}, /* 활동 */ {keys: ['h','i','z'], chars: [0xD68C,0xB2F4]}, /* 회담 */ {keys: ['a','i','m'], chars: [0xB300,0xB3D9,0xB839]}, /* 대통령 */ {keys: ['c','f','h','i'], chars: [0xD55C,0xD14C,0x20]}, /* 한테 */ {keys: ['a','h','o','x'], chars: [0xD589,0xBCF5]}, /* 행복 */ {keys: ['h','o','s'], chars: [0xBD81,0xD55C]}, /* 북한 */ {keys: ['e','h','o'], chars: [0xD544,0xC694]}, /* 필요 */ {keys: ['n','o','z'], chars: [0xC0C1,0xD488]}, /* 상품 */ {keys: ['a','h','o'], chars: [0xD45C,0xC815]}, /* 표정 */ {keys: ['h','o','r','t'], chars: [0xD45C,0xD604]}, /* 표현 */ {keys: ['l','u','x'], chars: [0xC791,0xB144]}, /* 작년 */ {keys: ['l','s','u'], chars: [0xB294,0xC9C0,0x20]}, /* 는지 */ {keys: ['l','u','z'], chars: [0xC8FC,0xB2D8]}, /* 주님 */ {keys: ['a','l','u'], chars: [0xB098,0xC911]}, /* 나중 */ {keys: ['f','l','s','u'], chars: [0xC9C0,0xB09C,0x20]}, /* 지난 */ {keys: ['l','r','u'], chars: [0xC800,0xB141]}, /* 저녁 */ {keys: ['d','l','u'], chars: [0xC9C0,0xB098]}, /* 지나 */ {keys: ['h','x'], chars: [0xD559,0xAD50]}, /* 학교 */ {keys: ['h','s'], chars: [0xD55C,0xAD6D]}, /* 한국 */ {keys: ['e','h'], chars: [0xD560,0xBA38,0xB2C8]}, /* 할머니 */ {keys: ['h','z'], chars: [0xD568,0xAED8,0x20]}, /* 함께 */ {keys: ['a','h'], chars: [0xD56D,0xC0C1,0x20]}, /* 항상 */ {keys: ['h','x','y'], chars: [0xBB38,0xD559]}, /* 문학 */ {keys: ['h','y','z'], chars: [0xB9CC,0xD07C,0x20]}, /* 만큼 */ {keys: ['a','h','y'], chars: [0xD76C,0xB9DD]}, /* 희망 */ {keys: ['g','h','x','y'], chars: [0xCE21,0xBA74]}, /* 측면 */ {keys: ['d','h','y'], chars: [0xBBF8,0xD130]}, /* 미터 */ {keys: ['h','u','x'], chars: [0xD559,0xB144]}, /* 학년 */ {keys: ['e','h','u'], chars: [0xD558,0xB298]}, /* 하늘 */ {keys: ['h','u','z'], chars: [0xD558,0xB098,0xB2D8]}, /* 하나님 */ {keys: ['f','h','u'], chars: [0xD558,0xB098]}, /* 하나 */ {keys: ['a','h','r','u'], chars: [0xCCAD,0xC18C,0xB144]}, /* 청소년 */ //160823 약어 추가 {keys: ['a','b','f','j','l','s'], chars: [0xC704,0xC6D0,0xC7A5]}, /* 위원장 */ {keys: ['a','f','j','l','p','s'], chars: [0xC704,0xC6D0,0xC7A5]}, /* 위원장 */ {keys: ['a','h','i'], chars: [0xD589,0xB3D9]}, /* 행동 */ {keys: ['a','i','k','s'], chars: [0xADF8,0xB3D9,0xC548]}, /* 그동안 */ {keys: ['j','n','w'], chars: [0xC0AC,0xC5C5]}, /* 사업 */ {keys: ['d','f','l','s','u'], chars: [0xC9C0,0xB09C,0xD574]}, /* 지난해 */ {keys: ['d','k','n'], chars: [0xC2DC,0xAE30]}, /* 시기 */ {keys: ['g','k','m','r'], chars: [0xADF8,0xB7EC]}, /* 그러 */ {keys: ['e','f','l','o'], chars: [0xD560,0xC544,0xBC84,0xC9C0]}, /* 할아버지 */ {keys: ['a','f','k','l'], chars: [0xACF5,0xC7A5]}, /* 공장 */ {keys: ['i','j','z'], chars: [0xB3C4,0xC6C0]}, /* 도움 */ {keys: ['f','j','u','v'], chars: [0xC624,0xB298,0xB0A0]}, /* 오늘날 */ {keys: ['.','f','j','u'], chars: [0xC624,0xB298,0xB0A0]}, /* 오늘날 */ {keys: ['c','d','e','j','l'], chars: [0xC81C,0xC77C]}, /* 제일 */ {keys: ['e','h','k'], chars: [0xACB0,0xCF54,0x20]}, /* 결코 */ {keys: ['b','d','k','l'], chars: [0xC9C0,0xAD6C]}, /* 지구 */ {keys: ['d','k','l','p'], chars: [0xC9C0,0xAD6C]}, /* 지구 */ {keys: ['f','n','o','v'], chars: [0xC18C,0xBE44,0xC790]}, /* 소비자 */ {keys: ['.','f','n','o'], chars: [0xC18C,0xBE44,0xC790]}, /* 소비자 */ {keys: ['a','f','i','u'], chars: [0xB178,0xB3D9,0xC790]}, /* 노동자 */ {keys: ['b','d','h','j'], chars: [0xC704,0xCE58]}, /* 위치 */ {keys: ['d','h','j','p'], chars: [0xC704,0xCE58]}, /* 위치 */ {keys: ['j','q'], chars: [0xC774,0xC6C3]}, /* 이웃 */ {keys: ['f','k','l','s'], chars: [0xAD00,0xACC4,0xC790]}, /* 관계자 */ {keys: ['d','k','l','v'], chars: [0xCD08,0xAE30]}, /* 초기 */ {keys: ['.','d','k','l'], chars: [0xCD08,0xAE30]}, /* 초기 */ {keys: ['a','h','k'], chars: [0xACE0,0xD5A5]}, /* 고향 */ {keys: [';','s','u'], chars: [0xB0B4,0xB193]}, /* 내놓 */ {keys: ['b','g','k','l'], chars: [0xADF8,0xC911]}, /* 그중 */ {keys: ['g','k','l','p'], chars: [0xADF8,0xC911]}, /* 그중 */ {keys: ['d','j','s','u'], chars: [0xB178,0xC778]}, /* 노인 */ {keys: ['a','f','l','n'], chars: [0xC0AC,0xC7A5]}, /* 사장 */ {keys: ['a','l','n','r'], chars: [0xC131,0xC7A5]}, /* 성장 */ {keys: ['a','l','n','v'], chars: [0xC7A5,0xC18C]}, /* 장소 */ {keys: ['f','i','o','s'], chars: [0xD310,0xB2E8]}, /* 판단 */ {keys: ['d','k','m'], chars: [0xAC70,0xB9AC]}, /* 거리 */ {keys: ['b','o','y','z'], chars: [0xBD80,0xBAA8,0xB2D8]}, /* 부모님 */ {keys: ['b','e','l','n'], chars: [0xC218,0xCD9C]}, /* 수출 */ {keys: ['d','m','y'], chars: [0xBBF8,0xB9AC,0x20]}, /* 미리 */ {keys: ['f','m','y'], chars: [0xB9C8,0xB9AC]}, /* 마리 */ {keys: ['d','f','m','y'], chars: [0xBBF8,0xB798]}, /* 미래 */ {keys: ['l','r','x','y'], chars: [0xBB34,0xCC99,0x20]}, /* 무척 */ {keys: ['a','d','k','o'], chars: [0xBE44,0xD589,0xAE30]}, /* 비행기 */ {keys: ['d','f','j','m','s'], chars: [0xC6D0,0xB798]}, /* 원래 */ {keys: ['b','m','o'], chars: [0xBFCC,0xB9AC]}, /* 뿌리 */ {keys: ['g','k','m','t'], chars: [0xADF8,0xB9AC,0xD558,0xC5EC,0x20]}, /* 그리하여 */ {keys: ['j','l','s','x'], chars: [0xC9C1,0xC6D0]}, /* 직원 */ {keys: ['b','o','u'], chars: [0xB0B4,0xBD80]}, /* 내부 */ {keys: ['f','i','n','v'], chars: [0xB2E4,0xC18C,0x20]}, /* 다소 */ {keys: ['.','f','i','n'], chars: [0xB2E4,0xC18C,0x20]}, /* 다소 */ {keys: ['c','f','k'], chars: [0xAC00,0xAC8C]}, /* 가게 */ {keys: ['g','i','k','x'], chars: [0xAC00,0xB4DD,0x20]}, /* 가득 */ {keys: ['b','f','k','l','s'], chars: [0xC7A5,0xAD70]}, /* 장군 */ {keys: ['f','k','l','p','s'], chars: [0xC7A5,0xAD70]}, /* 장군 */ {keys: ['b','t','y'], chars: [0xBB34,0xC5ED]}, /* 무역 */ {keys: ['p','t','y'], chars: [0xBB34,0xC5ED]}, /* 무역 */ {keys: ['b','f','i','o'], chars: [0xBD80,0xB2F4]}, /* 부담 */ {keys: ['f','i','o','p'], chars: [0xBD80,0xB2F4]}, /* 부담 */ {keys: ['a','l','o','s'], chars: [0xCDA9,0xBD84]}, /* 충분 */ {keys: ['a','d','l','o','s'], chars: [0xCDA9,0xBD84,0xD788,0x20]}, /* 충분히 */ {keys: ['c','l','u'], chars: [0xC5D0,0xB108,0xC9C0]}, /* 에너지 */ {keys: ['a','l','m','r'], chars: [0xC815,0xB9AC]}, /* 정리 */ {keys: ['j','l','s','w'], chars: [0xC9D1,0xC548]}, /* 집안 */ {keys: ['d','f','k','o'], chars: [0xBC30,0xACBD]}, /* 배경 */ {keys: ['b','i','n','s'], chars: [0xB2E8,0xC21C]}, /* 단순 */ {keys: ['b','d','i','n','s'], chars: [0xB2E8,0xC21C,0xD788,0x20]}, /* 단순히 */ {keys: ['d','i','n','p','s'], chars: [0xB2E8,0xC21C,0xD788,0x20]}, /* 단순히 */ {keys: ['f','i','n','s'], chars: [0xC218,0xB2E8]}, /* 수단 */ {keys: ['l','o','w'], chars: [0xBC95,0xCE59]}, /* 법칙 */ {keys: ['k','n','v','z'], chars: [0xC18C,0xAE08]}, /* 소금 */ {keys: ['f','k','l','r','s'], chars: [0xC790,0xC804,0xAC70]}, /* 자전거 */ {keys: ['d','l','o','v'], chars: [0xBC94,0xC8C4]}, /* 범죄 */ {keys: ['.','d','l','o'], chars: [0xBC94,0xC8C4]}, /* 범죄 */ {keys: ['m','s','u'], chars: [0xB17C,0xB9AC]}, /* 논리 */ {keys: ['f','g','i','m'], chars: [0xB4DC,0xB77C,0xB9C8]}, /* 드라마 */ {keys: ['g','h','l','r'], chars: [0xC800,0xD76C]}, /* 저희 */ {keys: ['g','h','r','u'], chars: [0xB108,0xD76C]}, /* 너희 */ {keys: ['a','f','i','k','r'], chars: [0xAD50,0xB3D9]}, /* 교통 */ {keys: ['.','a','i','k','v'], chars: [0xAD50,0xB3D9]}, /* 교통 */ {keys: ['e','j','l','z'], chars: [0xC80A,0xC740]}, /* 젊은 */ {keys: ['k','n','s','v'], chars: [0xC190,0xAC00,0xB77D]}, /* 손가락 */ {keys: ['g','k','l','r'], chars: [0xC801,0xADF9]}, /* 적극 */ {keys: ['g','k','l','r','x'], chars: [0xC801,0xADF9,0xC801]}, /* 적극적 */ {keys: ['d','f','o','y'], chars: [0xD310,0xB9E4]}, /* 판매 */ {keys: ['a','h','n','r','t'], chars: [0xD615,0xC131]}, /* 형성 */ {keys: ['a','l','s','y'], chars: [0xC7A5,0xBA74]}, /* 장면 */ {keys: ['d','u','y'], chars: [0xB098,0xBA38,0xC9C0]}, /* 나머지 */ {keys: ['e','n','u'], chars: [0xB0A0,0xC528]}, /* 날씨 */ {keys: ['a','d','f','k','o'], chars: [0xAC1C,0xBC29]}, /* 개방 */ {keys: ['j','o','r','t'], chars: [0xC5EC,0xB7EC,0xBD84]}, /* 여러분 */ {keys: ['d','f','h','u','v'], chars: [0xC65C,0xB0D0,0xD558,0xBA74,0x20]}, /* 왜냐하면 */ {keys: ['.','d','f','h','u'], chars: [0xC65C,0xB0D0,0xD558,0xBA74,0x20]}, /* 왜냐하면 */ {keys: ['d','f','i','k'], chars: [0xAE30,0xB300]}, /* 기대 */ {keys: ['f','i','l','x'], chars: [0xB3C4,0xCC29]}, /* 도착 */ {keys: ['c','n','o','r'], chars: [0xC18C,0xD504,0xD2B8,0xC6E8,0xC5B4]}, /* 소프트웨어 */ {keys: ['f','g','i','j'], chars: [0xB530,0xB73B]}, /* 따뜻 */ {keys: ['c','i','n'], chars: [0xC138,0xB300]}, /* 세대 */ {keys: ['b','k','l','x'], chars: [0xCD95,0xAD6C]}, /* 축구 */ {keys: ['a','h','t','u'], chars: [0xD615,0xB2D8]}, /* 형님 */ {keys: ['a','f','i','l'], chars: [0xB2F9,0xC7A5]}, /* 당장 */ {keys: ['b','t','w','y'], chars: [0xBB34,0xB835]}, /* 무렵 */ {keys: ['p','t','w','y'], chars: [0xBB34,0xB835]}, /* 무렵 */ {keys: ['e','f','n','y'], chars: [0xC0AC,0xBB3C]}, /* 사물 */ {keys: ['e','l','o','r','s'], chars: [0xC77C,0xBC18,0xC801]}, /* 일반적 */ {keys: ['f','i','o','x'], chars: [0xBC14,0xB2E5]}, /* 바닥 */ {keys: ['b','k','n'], chars: [0xAD50,0xC218]}, /* 교수 */ {keys: ['d','f','k','n'], chars: [0xC0C8,0xB07C]}, /* 새끼 */ {keys: ['d','g','n','o'], chars: [0xC11C,0xBE44,0xC2A4]}, /* 서비스 */ {keys: ['i','n','r','s'], chars: [0xC120,0xD0DD]}, /* 선택 */ {keys: ['f','i','k','s'], chars: [0xAC04,0xB2E8]}, /* 간단 */ {keys: ['c','f','i','k'], chars: [0xAC8C,0xB2E4,0xAC00,0x20]}, /* 게다가 */ {keys: ['a','g','i','k'], chars: [0xACE0,0xB4F1]}, /* 고등 */ {keys: ['c','h','y'], chars: [0xB9E4,0xCCB4]}, /* 매체 */ {keys: ['f','l','o','w'], chars: [0xBCF5,0xC7A1]}, /* 복잡 */ {keys: ['c','h','l','r'], chars: [0xCCB4,0xD5D8]}, /* 체험 */ {keys: ['b','k','n','x'], chars: [0xAD6C,0xC18D]}, /* 구속 */ {keys: ['d','f','i','m','v'], chars: [0xB54C,0xB85C,0x20]}, /* 때로 */ {keys: ['.','d','f','i','m'], chars: [0xB54C,0xB85C,0x20]}, /* 때로 */ {keys: ['l','r','t'], chars: [0xC5B4,0xCA4C,0xBA74,0x20]}, /* 어쩌면 */ {keys: ['k','o','x'], chars: [0xADF9,0xBCF5]}, /* 극복 */ {keys: ['e','o','y'], chars: [0xBE44,0xBC00]}, /* 비밀 */ {keys: ['j','t','u'], chars: [0xC774,0xB150]}, /* 이념 */ {keys: ['l','q','y'], chars: [0xC798,0xBABB]}, /* 잘못 */ {keys: ['f','n','o','x'], chars: [0xBC15,0xC0AC]}, /* 박사 */ {keys: ['b','l','r','s'], chars: [0xC804,0xBB38,0xAC00]}, /* 전문가 */ {keys: ['l','p','r','s'], chars: [0xC804,0xBB38,0xAC00]}, /* 전문가 */ {keys: ['b','f','k','l'], chars: [0xC790,0xAFB8,0x20]}, /* 자꾸 */ {keys: ['f','k','l','p'], chars: [0xC790,0xAFB8,0x20]}, /* 자꾸 */ {keys: ['d','f','h','j','v'], chars: [0xD574,0xC678]}, /* 해외 */ {keys: ['.','d','f','h','j'], chars: [0xD574,0xC678]}, /* 해외 */ {keys: ['d','n','v','y'], chars: [0xBBF8,0xC18C]}, /* 미소 */ {keys: ['.','d','n','y'], chars: [0xBBF8,0xC18C]}, /* 미소 */ {keys: ['a','i','o','v'], chars: [0xBCF4,0xB3D9]}, /* 보통 */ {keys: ['d','i','n','x'], chars: [0xC2DD,0xB2F9]}, /* 식당 */ {keys: ['d','j','m'], chars: [0xC774,0xB798]}, /* 이래 */ {keys: ['b','c','h','j'], chars: [0xCCB4,0xC721]}, /* 체육 */ {keys: ['c','h','j','p'], chars: [0xCCB4,0xC721]}, /* 체육 */ {keys: ['g','i','v'], chars: [0xB3C5,0xD2B9]}, /* 독특 */ {keys: ['.','g','i'], chars: [0xB3C5,0xD2B9]}, /* 독특 */ {keys: ['c','m','n','v'], chars: [0xC0AC,0xB840]}, /* 사례 */ {keys: ['a','k','o','s'], chars: [0xD3C9,0xADE0]}, /* 평균 */ {keys: ['a','d','k','l','z'], chars: [0xAC10,0xC815]}, /* 김정 */ {keys: ['h','m','s'], chars: [0xD6C8,0xB828]}, /* 훈련 */ {keys: ['h','m','z'], chars: [0xD750,0xB984]}, /* 흐름 */ {keys: ['s','u','y'], chars: [0xB17C,0xBB38]}, /* 논문 */ {keys: ['a','j','s'], chars: [0xC624,0xB7AB,0xB3D9,0xC548]}, /* 오랫동안 */ {keys: ['m','o','v','x'], chars: [0xD3ED,0xB825]}, /* 폭력 */ {keys: ['h','n','v','x'], chars: [0xD639,0xC2DC,0x20]}, /* 혹시 */ {keys: ['d','m','o','v','x'], chars: [0xC62C,0xB9BC,0xD53D]}, /* 올림픽 */ {keys: ['.','d','m','o','x'], chars: [0xC62C,0xB9BC,0xD53D]}, /* 올림픽 */ {keys: ['a','c','k','l'], chars: [0xC81C,0xACF5]}, /* 제공 */ {keys: ['a','f','g','k','l'], chars: [0xC99D,0xAC00]}, /* 증가 */ {keys: ['i','l','r','x'], chars: [0xC801,0xC5B4,0xB3C4,0x20]}, /* 적어도 */ {keys: ['b','k','m','r','s'], chars: [0xAD8C,0xB9AC]}, /* 권리 */ {keys: ['k','m','p','r','s'], chars: [0xAD8C,0xB9AC]}, /* 권리 */ {keys: ['j','m','r','t','z'], chars: [0xC5B4,0xB824,0xC6C0]}, /* 어려움 */ {keys: ['b','f','j','l','s'], chars: [0xC790,0xC6D0]}, /* 자원 */ {keys: ['f','j','l','p','s'], chars: [0xC790,0xC6D0]}, /* 자원 */ {keys: ['b','c','h','y'], chars: [0xBB3C,0xCCB4]}, /* 물체 */ {keys: ['c','h','p','y'], chars: [0xBB3C,0xCCB4]}, /* 물체 */ {keys: ['a','d','o','y'], chars: [0xBD84,0xBA85,0xD788,0x20]}, /* 분명히 */ {keys: ['b','f','j','q'], chars: [0xC544,0xBB34,0xAC83]}, /* 아무것 */ {keys: ['f','j','p','q'], chars: [0xC544,0xBB34,0xAC83]}, /* 아무것 */ {keys: ['a','n','o','t'], chars: [0xD3C9,0xC18C]}, /* 평소 */ {keys: ['b','f','i','k'], chars: [0xB354,0xAD6C,0xB098,0x20]}, /* 더구나 */ {keys: ['f','i','k','p'], chars: [0xB354,0xAD6C,0xB098,0x20]}, /* 더구나 */ {keys: ['g','i','l','r'], chars: [0xC5B4,0xCA0C,0xB4E0,0x20]}, /* 어쨌든 */ {keys: ['b','i','l','x'], chars: [0xC8FC,0xD0DD]}, /* 주택 */ {keys: ['e','s','u'], chars: [0xB208,0xAE38]}, /* 눈길 */ {keys: ['g','i','n'], chars: [0xC2A4,0xD2B8,0xB808,0xC2A4]}, /* 스트레스 */ {keys: ['e','m','u'], chars: [0xB110,0xB9AC,0x20]}, /* 널리 */ {keys: ['h','s','u','v'], chars: [0xB18D,0xCD0C]}, /* 농촌 */ {keys: ['d','f','l','n','v'], chars: [0xC18C,0xC7AC]}, /* 소재 */ {keys: ['.','d','f','l','n'], chars: [0xC18C,0xC7AC]}, /* 소재 */ {keys: ['a','f','l','y'], chars: [0xC804,0xB9DD]}, /* 전망 */ {keys: ['d','k','o','v'], chars: [0xD3EC,0xAE30]}, /* 포기 */ {keys: ['.','d','k','o'], chars: [0xD3EC,0xAE30]}, /* 포기 */ {keys: ['k','o','v'], chars: [0xBCF4,0xACE0]}, /* 보고 */ {keys: ['f','k','o','r'], chars: [0xBE44,0xAD50]}, /* 비교 */ {keys: ['.','k','o','v'], chars: [0xBE44,0xAD50]}, /* 비교 */ {keys: ['f','k','o','r','x'], chars: [0xBE44,0xAD50,0xC801]}, /* 비교적 */ {keys: ['.','k','o','v','x'], chars: [0xBE44,0xAD50,0xC801]}, /* 비교적 */ {keys: ['k','n','r','z'], chars: [0xAC80,0xC0AC]}, /* 검사 */ {keys: ['k','m','s','v'], chars: [0xACB0,0xB860]}, /* 결론 */ {keys: ['f','o','x','y'], chars: [0xBC15,0xBB3C,0xAD00]}, /* 박물관 */ {keys: ['n','v','y'], chars: [0xC18C,0xBB38]}, /* 소문 */ {keys: ['l','t','u'], chars: [0xC790,0xB140]}, /* 자녀 */ {keys: ['c','l','v','y'], chars: [0xC81C,0xBAA9]}, /* 제목 */ {keys: ['e','k','o','t'], chars: [0xD310,0xACB0]}, /* 판결 */ {keys: ['a','f','k','o'], chars: [0xAC00,0xBC29]}, /* 가방 */ {keys: ['b','i','k','s'], chars: [0xAD70,0xB300]}, /* 군대 */ {keys: ['g','h','y','z'], chars: [0xADF8,0xB9CC,0xD07C]}, /* 그만큼 */ {keys: ['a','d','f','i','o'], chars: [0xC0C1,0xB300,0xBC29]}, /* 상대방 */ {keys: ['b','k','n','r'], chars: [0xC11C,0xAD6C]}, /* 서구 */ {keys: ['k','n','p','r'], chars: [0xC11C,0xAD6C]}, /* 서구 */ {keys: ['e','k','n','v'], chars: [0xC2DC,0xACE8]}, /* 시골 */ {keys: ['d','l','m','v'], chars: [0xCE58,0xB8CC]}, /* 치료 */ {keys: ['.','d','l','m'], chars: [0xCE58,0xB8CC]}, /* 치료 */ {keys: ['b','o','s','y'], chars: [0xBD80,0xBB38]}, /* 부문 */ {keys: ['d','g','l','n'], chars: [0xC2DC,0xB9AC,0xC988]}, /* 시리즈 */ {keys: ['j','n','s','z'], chars: [0xC784,0xC2E0]}, /* 임신 */ {keys: ['d','k','n','x'], chars: [0xC2DD,0xAD6C]}, /* 식구 */ {keys: ['d','f','k','n','s'], chars: [0xAC1C,0xC120]}, /* 개선 */ {keys: ['m','o','x','z'], chars: [0xBC14,0xB78C,0xC9C1]}, /* 바람직 */ {keys: ['d','j','m','v'], chars: [0xB77C,0xB514,0xC624]}, /* 라디오 */ {keys: ['.','d','j','m'], chars: [0xB77C,0xB514,0xC624]}, /* 라디오 */ {keys: ['a','i','o','s','v'], chars: [0xBD80,0xB3D9,0xC0B0]}, /* 부동산 */ {keys: ['f','h','n','v'], chars: [0xC2E0,0xD654]}, /* 신화 */ {keys: ['.','f','h','n'], chars: [0xC2E0,0xD654]}, /* 신화 */ {keys: ['j','l','w','x'], chars: [0xC9C1,0xC5C5]}, /* 직업 */ {keys: ['o','z'], chars: [0xBC94,0xC704]}, /* 범위 */ {keys: ['a','f','l','n','v'], chars: [0xC870,0xC0C1]}, /* 조상 */ {keys: ['.','a','f','l','n'], chars: [0xC870,0xC0C1]}, /* 조상 */ {keys: ['e','h','l','x'], chars: [0xCCA0,0xD559]}, /* 철학 */ {keys: ['g','k','o','x'], chars: [0xADFC,0xBCF8,0xC801]}, /* 근본적 */ {keys: ['k','o','v','x'], chars: [0xBCF8,0xACA9,0xC801]}, /* 본격적 */ {keys: ['d','k','o','x'], chars: [0xAE30,0xBCF8,0xC801]}, /* 기본적 */ {keys: ['c','l','y','z'], chars: [0xBB38,0xC81C,0xC810]}, /* 문제점 */ {keys: ['c','d','j','s'], chars: [0xC778,0xC81C,0x20]}, /* 인제 */ {keys: ['a','k','l','x'], chars: [0xCDA9,0xACA9]}, /* 충격 */ {keys: ['g','k','o','z'], chars: [0xAE08,0xBC29,0x20]}, /* 금방 */ {keys: ['b','e','n','y'], chars: [0xBBF8,0xC220]}, /* 미술 */ {keys: ['d','f','n','o','x'], chars: [0xBC31,0xC131]}, /* 백성 */ {keys: ['a','f','h','n'], chars: [0xC0C1,0xB2F9,0xD788,0x20]}, /* 상당히 */ {keys: ['e','f','k','n'], chars: [0xC0C9,0xAE54]}, /* 색깔 */ {keys: ['a','b','t','y'], chars: [0xC720,0xBA85]}, /* 유명 */ {keys: ['a','p','t','y'], chars: [0xC720,0xBA85]}, /* 유명 */ {keys: ['j','k','s','v','x'], chars: [0xC678,0xAD6D,0xC778]}, /* 외국인 */ {keys: ['h','l','s','z'], chars: [0xD55C,0xCC38]}, /* 한참 */ {keys: ['b','f','k','n','s'], chars: [0xAD70,0xC0AC]}, /* 군사 */ {keys: ['f','k','n','p','s'], chars: [0xAD70,0xC0AC]}, /* 군사 */ {keys: ['b','c','l','n','x'], chars: [0xC219,0xC81C]}, /* 숙제 */ {keys: ['c','l','n','p','x'], chars: [0xC219,0xC81C]}, /* 숙제 */ {keys: ['c','f','j','n','s'], chars: [0xC608,0xC0B0]}, /* 예산 */ {keys: [';','e','j','k'], chars: [0xC628,0xAC16,0x20]}, /* 온갖 */ {keys: ['b','m','t'], chars: [0xC6B0,0xB824]}, /* 우려 */ {keys: ['m','p','t'], chars: [0xC6B0,0xB824]}, /* 우려 */ {keys: ['b','f','j','n','s'], chars: [0xC6B0,0xC0B0]}, /* 우산 */ {keys: ['f','j','n','p','s'], chars: [0xC6B0,0xC0B0]}, /* 우산 */ {keys: ['b','f','j','n'], chars: [0xC218,0xC694]}, /* 수요 */ {keys: ['f','j','n','p'], chars: [0xC218,0xC694]}, /* 수요 */ {keys: ['d','g','k','o'], chars: [0xAE30,0xC068]}, /* 기쁨 */ {keys: [';','w','y'], chars: [0xBB34,0xB98E]}, /* 무릎 */ {keys: ['c','i','n','z'], chars: [0xC2DC,0xC2A4,0xD15C]}, /* 시스템 */ {keys: ['d','e','f','l','u'], chars: [0xC9C0,0xB09C,0xB2EC]}, /* 지난달 */ {keys: ['f','h','j','z'], chars: [0xCC38,0xC5EC]}, /* 참여 */ {keys: ['e','g','j','k','r'], chars: [0xAC78,0xC74C]}, /* 걸음 */ {keys: ['b','k','t'], chars: [0xACA8,0xC6B0,0x20]}, /* 겨우 */ {keys: ['k','p','t'], chars: [0xACA8,0xC6B0,0x20]}, /* 겨우 */ {keys: ['d','f','l','n','x'], chars: [0xCC45,0xC0C1]}, /* 책상 */ {keys: ['g','i','n','x'], chars: [0xC18C,0xB4DD]}, /* 소득 */ {keys: ['d','i','l','z'], chars: [0xCE68,0xB300]}, /* 침대 */ {keys: ['f','g','i','n'], chars: [0xC2A4,0xD0C0]}, /* 스타 */ {keys: ['e','j','n','w'], chars: [0xC785,0xC220]}, /* 입술 */ {keys: ['a','b','k','l'], chars: [0xC911,0xAC04]}, /* 중간 */ {keys: ['n','v','w','y'], chars: [0xBAB9,0xC2DC,0x20]}, /* 몹시 */ {keys: ['b','g','i'], chars: [0xBB38,0xB4DD,0x20]}, /* 문득 */ {keys: ['g','i','p'], chars: [0xBB38,0xB4DD,0x20]}, /* 문득 */ {keys: ['g','n','o'], chars: [0xC2A4,0xD3EC,0xCE20]}, /* 스포츠 */ {keys: ['k','l','r'], chars: [0xC800,0xAE30]}, /* 저기 */ {keys: ['g','k','l'], chars: [0xADF8,0xC800,0x20]}, /* 그저 */ {keys: ['c','f','j','l','s'], chars: [0xC5B8,0xC820,0xAC00,0x20]}, /* 언젠가 */ {keys: ['c','f','j','n'], chars: [0xC608,0xC220,0xAC00]}, /* 예술가 */ {keys: ['g','i','j','v'], chars: [0xC758,0xB3C4]}, /* 의도 */ {keys: ['.','g','i','j'], chars: [0xC758,0xB3C4]}, /* 의도 */ {keys: ['f','h','s','y'], chars: [0xAC00,0xB9CC,0xD788,0x20]}, /* 가만히 */ {keys: ['b','f','h','i','s'], chars: [0xD55C,0xB450,0x20]}, /* 한두 */ {keys: ['f','h','i','p','s'], chars: [0xD55C,0xB450,0x20]}, /* 한두 */ {keys: ['d','f','h','j','s'], chars: [0xD55C,0xB54C]}, /* 한때 */ {keys: ['a','d','f','i','n','x'], chars: [0xC0C1,0xB300,0xC801]}, /* 상대적 */ {keys: ['d','n','o','x'], chars: [0xC2DD,0xD488]}, /* 식품 */ {keys: ['.','h','j','r','s'], chars: [0xD68C,0xC6D0]}, /* 회원 */ {keys: ['h','j','r','s','v'], chars: [0xD68C,0xC6D0]}, /* 회원 */ {keys: ['i','n','r','v'], chars: [0xB3C4,0xC11C,0xAD00]}, /* 도서관 */ {keys: ['.','i','n','r'], chars: [0xB3C4,0xC11C,0xAD00]}, /* 도서관 */ {keys: ['d','l','v','y'], chars: [0xC870,0xBBF8,0xB8CC]}, /* 조미료 */ {keys: ['.','d','l','y'], chars: [0xC870,0xBBF8,0xB8CC]}, /* 조미료 */ {keys: ['a','f','k','m'], chars: [0xAC15,0xB825]}, /* 강력 */ {keys: ['e','g','u','y'], chars: [0xB9C8,0xB298]}, /* 마늘 */ {keys: ['g','k','n','w'], chars: [0xC2B5,0xAD00]}, /* 습관 */ {keys: ['e','h','l'], chars: [0xC9C0,0xD558,0xCCA0]}, /* 지하철 */ {keys: ['h','s','t','y'], chars: [0xD654,0xBA74]}, /* 화면 */ {keys: ['f','i','o','r','x'], chars: [0xB300,0xD45C,0xC801]}, /* 대표적 */ {keys: ['.','i','o','v','x'], chars: [0xB300,0xD45C,0xC801]}, /* 대표적 */ {keys: ['b','l','o'], chars: [0xC8FC,0xBD80]}, /* 주부 */ {keys: ['d','l','m','s'], chars: [0xC9C4,0xB9AC]}, /* 진리 */ {keys: ['h','i','q','w'], chars: [0xD2C0,0xB9BC,0xC5C6]}, /* 틀림없 */ {keys: ['a','k','w'], chars: [0xACF5,0xAE09]}, /* 공급 */ {keys: ['a','i','m','v'], chars: [0xB3D9,0xB8CC]}, /* 동료 */ {keys: ['a','h','k','s'], chars: [0xADE0,0xD615]}, /* 균형 */ {keys: ['b','f','n','y'], chars: [0xC0AC,0xBB34]}, /* 사무 */ {keys: ['f','n','p','y'], chars: [0xC0AC,0xBB34]}, /* 사무 */ {keys: ['k','l','r','w'], chars: [0xC811,0xADFC]}, /* 접근 */ {keys: ['a','f','k','n','r'], chars: [0xAC1C,0xC131]}, /* 개성 */ {keys: ['f','h','n','x'], chars: [0xC0AC,0xD68C,0xC801]}, /* 사회적 */ {keys: ['d','f','h','n','x'], chars: [0xD575,0xC2EC]}, /* 핵심 */ {keys: ['a','f','o','y'], chars: [0xBC29,0xBB38]}, /* 방문 */ {keys: ['.','f','k','s','x'], chars: [0xAD00,0xAC1D]}, /* 관객 */ {keys: ['f','k','s','v','x'], chars: [0xAD00,0xAC1D]}, /* 관객 */ {keys: ['a','i','l','x'], chars: [0xB3D9,0xC791]}, /* 동작 */ {keys: ['d','e','m','y'], chars: [0xBC00,0xB9AC]}, /* 밀리 */ {keys: ['b','f','l','n'], chars: [0xC22B,0xC790]}, /* 숫자 */ {keys: ['f','l','n','p'], chars: [0xC22B,0xC790]}, /* 숫자 */ {keys: ['j','l','s','v','x'], chars: [0xC67C,0xCABD]}, /* 왼쪽 */ {keys: ['.','j','l','s','x'], chars: [0xC67C,0xCABD]}, /* 왼쪽 */ {keys: ['l','m','v','x'], chars: [0xC624,0xB978,0xCABD]}, /* 오른쪽 */ {keys: ['b','c','l','n'], chars: [0xC911,0xC138]}, /* 중세 */ {keys: ['c','l','n','p'], chars: [0xC911,0xC138]}, /* 중세 */ {keys: ['d','f','i','n','x'], chars: [0xD0DD,0xC2DC]}, /* 택시 */ {keys: ['a','h','i','w'], chars: [0xB3D9,0xD569]}, /* 통합 */ {keys: ['c','k','n','s','v'], chars: [0xACC4,0xC0B0]}, /* 계산 */ {keys: ['d','k','m','v'], chars: [0xAF2C,0xB9AC]}, /* 꼬리 */ {keys: ['.','d','k','m'], chars: [0xAF2C,0xB9AC]}, /* 꼬리 */ {keys: ['a','j','n','x'], chars: [0xC591,0xC2DD]}, /* 양식 */ {keys: ['d','k','l','s'], chars: [0xC804,0xAE30]}, /* 전기 */ {keys: ['b','d','l','n','x'], chars: [0xC8FC,0xC2DD]}, /* 주식 */ {keys: ['d','l','n','p','x'], chars: [0xC8FC,0xC2DD]}, /* 주식 */ {keys: ['j','k','q','w'], chars: [0xB04A,0xC784,0xC5C6]}, /* 끊임없 */ {keys: ['a','n','x'], chars: [0xC0C1,0xC2DD]}, /* 상식 */ {keys: ['b','g','k'], chars: [0xAD81,0xAE08]}, /* 궁금 */ {keys: ['g','k','p'], chars: [0xAD81,0xAE08]}, /* 궁금 */ {keys: ['l','w'], chars: [0xC7A1,0xC9C0]}, /* 잡지 */ {keys: ['b','k','o','r'], chars: [0xAC70,0xBD80]}, /* 거부 */ {keys: ['k','o','p','r'], chars: [0xAC70,0xBD80]}, /* 거부 */ {keys: ['f','k','o','s'], chars: [0xBC18,0xAC11]}, /* 반갑 */ {keys: ['j','o','s','w'], chars: [0xBC95,0xC6D0]}, /* 법원 */ {keys: ['d','i','o','v'], chars: [0xBE44,0xB514,0xC624]}, /* 비디오 */ {keys: ['.','d','i','o'], chars: [0xBE44,0xB514,0xC624]}, /* 비디오 */ {keys: ['g','j','n','t'], chars: [0xC5F0,0xC2B5]}, /* 연습 */ {keys: ['d','e','f','l','n'], chars: [0xD654,0xC7A5,0xC2E4]}, /* 화장실 */ {keys: ['d','m','n','x'], chars: [0xC2DD,0xB7C9]}, /* 식량 */ {keys: ['e','h','n','z'], chars: [0xC2E4,0xD5D8]}, /* 실험 */ {keys: ['i','m','s','v'], chars: [0xD1A0,0xB860]}, /* 토론 */ {keys: ['k','w'], chars: [0xACE0,0xAE09]}, /* 고급 */ {keys: ['e','l','x'], chars: [0xC801,0xC808]}, /* 적절 */ {keys: ['d','f','l','n','z'], chars: [0xCC38,0xC0AC]}, /* 참새 */ {keys: ['f','l','o','v'], chars: [0xD654,0xC7A5,0xD488]}, /* 화장품 */ {keys: ['.','f','l','o'], chars: [0xD654,0xC7A5,0xD488]}, /* 화장품 */ {keys: ['f','g','k','j','q'], chars: [0xAE68,0xB057]}, /* 깨끗 */ {keys: ['f','s','u','y'], chars: [0xB18D,0xC0B0,0xBB3C]}, /* 농산물 */ {keys: ['b','f','j','u'], chars: [0xB208,0xC55E]}, /* 눈앞 */ {keys: ['f','j','p','u'], chars: [0xB208,0xC55E]}, /* 눈앞 */ {keys: ['b','f','i','m'], chars: [0xC544,0xBB34,0xB798,0xB3C4,0x20]}, /* 아무래도 */ {keys: ['f','i','m','p'], chars: [0xC544,0xBB34,0xB798,0xB3C4,0x20]}, /* 아무래도 */ {keys: ['b','k','n','t'], chars: [0xC5F0,0xAD6C,0xC18C]}, /* 연구소 */ {keys: ['k','n','p','t'], chars: [0xC5F0,0xAD6C,0xC18C]}, /* 연구소 */ {keys: ['a','d','f','k','n','v'], chars: [0xACE0,0xC0DD]}, /* 고생 */ {keys: ['.','a','d','f','k','n'], chars: [0xACE0,0xC0DD]}, /* 고생 */ {keys: ['a','k','m','t'], chars: [0xAC00,0xB839,0x20]}, /* 가령 */ {keys: ['i','k','r'], chars: [0xAC70,0xB300]}, /* 거대 */ {keys: ['g','k','m','s'], chars: [0xADFC,0xB85C]}, /* 근로 */ {keys: ['l','o','r','z'], chars: [0xBC31,0xD654,0xC810]}, /* 백화점 */ {keys: ['j','m','s','t'], chars: [0xC5EC,0xB860]}, /* 여론 */ {keys: ['g','j','o','v'], chars: [0xC758,0xBCF5]}, /* 의복 */ {keys: ['.','g','j','o'], chars: [0xC758,0xBCF5]}, /* 의복 */ {keys: ['b','e','l','o'], chars: [0xCD9C,0xBC1C]}, /* 출발 */ {keys: ['h','n','r','s','t'], chars: [0xD604,0xC2E4,0xC801]}, /* 현실적 */ {keys: ['c','f','h','l'], chars: [0xD654,0xC81C]}, /* 화제 */ {keys: ['a','k','x'], chars: [0xACF5,0xACA9]}, /* 공격 */ {keys: ['b','e','f','y'], chars: [0xBB3C,0xAC00]}, /* 물가 */ {keys: ['e','f','p','y'], chars: [0xBB3C,0xAC00]}, /* 물가 */ {keys: ['a','b','l','n'], chars: [0xC18C,0xC911]}, /* 소중 */ {keys: ['b','g','j'], chars: [0xC758,0xBB38]}, /* 의문 */ {keys: ['g','j','p'], chars: [0xC758,0xBB38]}, /* 의문 */ {keys: ['d','e','k','m'], chars: [0xD0AC,0xB85C]}, /* 킬로 */ {keys: ['b','f','k','n'], chars: [0xAC00,0xC218]}, /* 가수 */ {keys: ['f','k','n','p'], chars: [0xAC00,0xC218]}, /* 가수 */ {keys: ['a','n','o','x'], chars: [0xBC29,0xC1A1,0xAD6D]}, /* 방송국 */ {keys: ['f','j','w','x'], chars: [0xC555,0xB825]}, /* 압력 */ {keys: ['d','j','s','x'], chars: [0xC778,0xB825]}, /* 인력 */ {keys: ['c','g','j','k'], chars: [0xC608,0xAE08]}, /* 예금 */ {keys: ['d','h','j','w'], chars: [0xC785,0xD559]}, /* 입학 */ {keys: ['a','l','m','r','v'], chars: [0xCC28,0xB7C9]}, /* 차량 */ {keys: ['.','a','l','m','r'], chars: [0xCC28,0xB7C9]}, /* 차량 */ {keys: ['b','e','f','h','l'], chars: [0xCD9C,0xC0B0]}, /* 출산 */ {keys: ['e','f','h','l','p'], chars: [0xCD9C,0xC0B0]}, /* 출산 */ {keys: ['b','l','n','x'], chars: [0xC120,0xC9C4,0xAD6D]}, /* 선진국 */ {keys: ['d','f','l','o','s'], chars: [0xC7AC,0xD310]}, /* 재판 */ {keys: ['a','f','l','s','y'], chars: [0xCC3D,0xBB38]}, /* 창문 */ {keys: ['l','n','r','x'], chars: [0xCC38,0xC11D]}, /* 참석 */ {keys: ['b','e','f','h','o'], chars: [0xBC1C,0xD718]}, /* 발휘 */ {keys: ['e','f','h','o','p'], chars: [0xBC1C,0xD718]}, /* 발휘 */ {keys: ['a','k','l','w'], chars: [0xC911,0xC18C,0xAE30,0xC5C5]}, /* 중소기업 */ {keys: ['l','r','w','x'], chars: [0xC9C1,0xC811,0xC801]}, /* 직접적 */ {keys: ['h','w','x'], chars: [0xD611,0xB825]}, /* 협력 */ {keys: ['f','g','k','n'], chars: [0xAC00,0xC2A4]}, /* 가스 */ {keys: ['f','h','v','y'], chars: [0xB9CC,0xD654]}, /* 만화 */ {keys: ['.','f','h','y'], chars: [0xB9CC,0xD654]}, /* 만화 */ {keys: ['e','l','n','x'], chars: [0xC0B4,0xC9DD,0x20]}, /* 살짝 */ {keys: ['e','k','l','s'], chars: [0xAD00,0xCC30]}, /* 관찰 */ {keys: ['u','x'], chars: [0xB140,0xC11D]}, /* 녀석 */ {keys: ['g','i','j','r'], chars: [0xB4DC,0xB514,0xC5B4,0x20]}, /* 드디어 */ {keys: ['g','k','l','x'], chars: [0xC790,0xADF9]}, /* 자극 */ {keys: ['d','l','n'], chars: [0xC9C0,0xC2DC]}, /* 지시 */ {keys: ['c','h','l','t'], chars: [0xD615,0xC81C]}, /* 형제 */ {keys: ['f','h','m','v'], chars: [0xD654,0xB824]}, /* 화려 */ {keys: ['.','f','h','m'], chars: [0xD654,0xB824]}, /* 화려 */ {keys: ['c','k','n','x'], chars: [0xC138,0xACC4,0xC801]}, /* 세계적 */ {keys: ['d','e','l','o'], chars: [0xBCF8,0xC9C8]}, /* 본질 */ {keys: ['d','n','o','s'], chars: [0xC2E0,0xBD84]}, /* 신분 */ {keys: ['b','d','n','o'], chars: [0xC2E0,0xBD80]}, /* 신부 */ {keys: ['d','n','o','p'], chars: [0xC2E0,0xBD80]}, /* 신부 */ {keys: ['e','j','l','w'], chars: [0xC878,0xC5C5]}, /* 졸업 */ {keys: ['h','o','z'], chars: [0xD3EC,0xD568]}, /* 포함 */ {keys: ['a','f','h','m'], chars: [0xD638,0xB791,0xC774]}, /* 호랑이 */ {keys: ['c','k','n','z'], chars: [0xC138,0xAE08]}, /* 세금 */ {keys: ['a','j','r','t'], chars: [0xC601,0xC591]}, /* 영양 */ {keys: ['d','i','m','w'], chars: [0xB3C5,0xB9BD]}, /* 독립 */ {keys: ['d','f','i','n','v'], chars: [0xB610,0xB2E4,0xC2DC,0x20]}, /* 또다시 */ {keys: ['.','d','f','i','n'], chars: [0xB610,0xB2E4,0xC2DC,0x20]}, /* 또다시 */ {keys: ['b','f','j','o','s'], chars: [0xC704,0xBC18]}, /* 위반 */ {keys: ['f','j','o','p','s'], chars: [0xC704,0xBC18]}, /* 위반 */ {keys: ['a','d','f','n','o'], chars: [0xD3C9,0xC0DD]}, /* 평생 */ {keys: ['a','b','f','k','l'], chars: [0xAD11,0xC8FC]}, /* 광주 */ {keys: ['a','f','k','l','p'], chars: [0xAD11,0xC8FC]}, /* 광주 */ {keys: ['f','j','m','r'], chars: [0xB7EC,0xC2DC,0xC544]}, /* 러시아 */ {keys: ['f','h','u','z'], chars: [0xB0A8,0xD55C]}, /* 남한 */ {keys: ['f','j','k','s','x'], chars: [0xAD00,0xC545]}, /* 관악 */ {keys: ['a','h','s'], chars: [0xD55C,0xAC15]}, /* 한강 */ {keys: ['g','j','m','t'], chars: [0xC73C,0xB824]}, /* 으려 */ {keys: ['c','d','j','k'], chars: [0xAE30,0xC5D0,0x20]}, /* 기에 */ {keys: ['b','i','k','r','s'], chars: [0xB354,0xAD70,0x2E,0x20]}, /* 더군. */ {keys: ['i','k','p','r','s'], chars: [0xB354,0xAD70,0x2E,0x20]}, /* 더군. */ {keys: ['d','f','i','u'], chars: [0xB2E4,0xB2C8]}, /* 다니 */ {keys: ['g','s','u','v'], chars: [0xACE0,0xB294,0x20]}, /* 고는 */ {keys: ['.','g','s','u'], chars: [0xACE0,0xB294,0x20]}, /* 고는 */ {keys: ['f','l','y'], chars: [0xB9C8,0xC790,0x20]}, /* 마자 */ {keys: ['a','d','k','l','v'], chars: [0xAD49,0xC7A5]}, /* 굉장 */ {keys: ['.','a','d','k','l'], chars: [0xAD49,0xC7A5]}, /* 굉장 */ {keys: ['k','l','t','x'], chars: [0xC790,0xACA9]}, /* 자격 */ {keys: ['c','f','l','x'], chars: [0xC81C,0xC791]}, /* 제작 */ {keys: ['a','l','w'], chars: [0xC9D1,0xC911]}, /* 집중 */ {keys: ['a','h','l','w'], chars: [0xC885,0xD569]}, /* 종합 */ {keys: ['e','f','l','y'], chars: [0xC8FC,0xB9D0]}, /* 주말 */ {keys: ['b','f','k','m'], chars: [0xAC00,0xB8E8]}, /* 가루 */ {keys: ['f','k','m','p'], chars: [0xAC00,0xB8E8]}, /* 가루 */ {keys: ['f','j','m','x'], chars: [0xC5F0,0xB77D]}, /* 연락 */ {keys: ['a','c','i','l','v'], chars: [0xB3D9,0xC81C]}, /* 통제 */ {keys: ['c','f','h','l','s'], chars: [0xC81C,0xD55C]}, /* 제한 */ {keys: ['c','h','j','r'], chars: [0xD5E4,0xC5B4]}, /* 헤어 */ {keys: ['e','j','l','s'], chars: [0xCD9C,0xC5F0]}, /* 출연 */ {keys: ['b','f','l','y'], chars: [0xB9C8,0xC8FC,0x20]}, /* 마주 */ {keys: ['f','l','p','y'], chars: [0xB9C8,0xC8FC,0x20]}, /* 마주 */ {keys: ['a','c','f','k','l'], chars: [0xAC15,0xC81C]}, /* 강제 */ {keys: ['d','f','n','u'], chars: [0xB09A,0xC2DC]}, /* 낚시 */ {keys: ['b','l','s','y'], chars: [0xBB38,0xC790]}, /* 문자 */ {keys: ['k','s','z'], chars: [0xAD00,0xB150]}, /* 관념 */ {keys: ['e','n','o','s'], chars: [0xC2E0,0xBC1C]}, /* 신발 */ {keys: ['d','k','l','z'], chars: [0xAC10,0xCE58]}, /* 김치 */ {keys: ['a','d','i','k'], chars: [0xB3D9,0xAE30]}, /* 동기 */ {keys: ['e','f','g','i','n'], chars: [0xC2A4,0xD0C0,0xC77C]}, /* 스타일 */ {keys: ['l','n','r','z'], chars: [0xC2DC,0xC810]}, /* 시점 */ {keys: ['a','l','n','z'], chars: [0xC2EC,0xC7A5]}, /* 심장 */ {keys: ['d','l','n','z'], chars: [0xC810,0xC2EC]}, /* 점심 */ {keys: ['j','i','w'], chars: [0xB3C4,0xC785]}, /* 도입 */ {keys: ['g','j','m','z'], chars: [0xC74C,0xB8CC]}, /* 음료 */ {keys: ['f','g','j','l'], chars: [0xC758,0xC790]}, /* 의자 */ {keys: ['f','g','j','l'], chars: [0xC758,0xC790]}, /* 의자 */ {keys: ['c','f','h','y'], chars: [0xCE74,0xBA54,0xB77C]}, /* 카메라 */ {keys: ['d','m','o'], chars: [0xD3B8,0xB9AC]}, /* 편리 */ {keys: ['d','e','k','l'], chars: [0xAC70,0xCE60]}, /* 거칠 */ {keys: ['f','u','y'], chars: [0xB098,0xB9C8,0x20]}, /* 나마 */ {keys: ['f','o','u','z'], chars: [0xB0A8,0xBD80]}, /* 남부 */ {keys: ['e','m','v','y'], chars: [0xBAB0,0xB798,0x20]}, /* 몰래 */ {keys: ['g','k','l','v'], chars: [0xC870,0xADF8]}, /* 조그 */ {keys: ['.','g','k','l'], chars: [0xC870,0xADF8]}, /* 조그 */ {keys: ['b','e','k','l'], chars: [0xC904,0xAE30]}, /* 줄기 */ {keys: ['f','g','k','l','x'], chars: [0xADF9,0xC7A5]}, /* 극장 */ {keys: ['d','g','k','o','x'], chars: [0xBE44,0xADF9]}, /* 비극 */ {keys: [';','u','w'], chars: [0xB192,0xC774]}, /* 높이 */ {keys: ['f','h','n','s'], chars: [0xD55C,0xC228]}, /* 한숨 */ {keys: ['a','f','l','n','s'], chars: [0xC120,0xC7A5]}, /* 선장 */ {keys: ['g','i','k','v'], chars: [0xCF54,0xB4DC]}, /* 코드 */ {keys: ['.','g','i','k'], chars: [0xCF54,0xB4DC]}, /* 코드 */ {keys: ['h','r','t','u'], chars: [0xCCAD,0xB144]}, /* 청년 */ {keys: ['d','n','u'], chars: [0xC2DC,0xB0B4]}, /* 시내 */ {keys: ['d','e','n','u'], chars: [0xC2E4,0xB0B4]}, /* 실내 */ {keys: ['e','j','k','s'], chars: [0xC5F0,0xACB0]}, /* 연결 */ {keys: ['c','k','r'], chars: [0xACC4,0xC57D]}, /* 계약 */ {keys: ['b','g','k','o'], chars: [0xBD80,0xB044]}, /* 부끄 */ {keys: ['g','k','o','p'], chars: [0xBD80,0xB044]}, /* 부끄 */ {keys: ['b','h','n'], chars: [0xC218,0xD589]}, /* 수행 */ {keys: ['a','d','l','n','s'], chars: [0xC2E0,0xCCAD]}, /* 신청 */ {keys: ['b','f','o'], chars: [0xBC14,0xC704]}, /* 바위 */ {keys: ['f','o','p'], chars: [0xBC14,0xC704]}, /* 바위 */ {keys: ['c','l','r','x'], chars: [0xC804,0xCCB4,0xC801]}, /* 전체적 */ {keys: ['h','j','s','t'], chars: [0xD3B8,0xC548]}, /* 편안 */ {keys: ['d','f','m','x','y'], chars: [0xB9E4,0xB825]}, /* 매력 */ {keys: ['a','b','f','l','o'], chars: [0xBD80,0xC7A5]}, /* 부장 */ {keys: ['a','f','l','o','p'], chars: [0xBD80,0xC7A5]}, /* 부장 */ {keys: ['d','m','n','z'], chars: [0xC2EC,0xB9AC]}, /* 심리 */ {keys: ['e','f','i','l'], chars: [0xC804,0xB2EC]}, /* 전달 */ {keys: ['a','h','n','t'], chars: [0xD615,0xC0AC]}, /* 형사 */ {keys: ['f','i','k','z'], chars: [0xAC10,0xB3D9]}, /* 감동 */ {keys: ['b','f','m','y'], chars: [0xB9C8,0xB8E8]}, /* 마루 */ {keys: ['f','m','p','y'], chars: [0xB9C8,0xB8E8]}, /* 마루 */ {keys: ['a','l','n','x'], chars: [0xC131,0xC801]}, /* 성적 */ {keys: ['e','l','n'], chars: [0xC194,0xC9C1]}, /* 솔직 */ {keys: ['i','l','r','s','x'], chars: [0xC804,0xB3D9,0xC801]}, /* 전통적 */ {keys: ['d','i','n','v'], chars: [0xC2DC,0xB3C4]}, /* 시도 */ {keys: ['.','d','i','n'], chars: [0xC2DC,0xB3C4]}, /* 시도 */ {keys: ['f','k','l','z'], chars: [0xCC38,0xAC00]}, /* 참가 */ {keys: ['d','f','h','y'], chars: [0xCE58,0xB9C8]}, /* 치마 */ {keys: ['a','h','i','x'], chars: [0xD2B9,0xC815]}, /* 특정 */ {keys: ['d','k','n','v','x'], chars: [0xACF5,0xC2DD]}, /* 공식 */ {keys: ['.','d','k','n','x'], chars: [0xACF5,0xC2DD]}, /* 공식 */ {keys: ['a','d','k','l','s'], chars: [0xAE34,0xC7A5]}, /* 긴장 */ {keys: ['a','k','l','s','v'], chars: [0xC8FC,0xC778,0xACF5]}, /* 주인공 */ {keys: ['l','o','r'], chars: [0xBD80,0xCC98]}, /* 부처 */ {keys: ['e','i','l'], chars: [0xC808,0xB300]}, /* 절대 */ {keys: ['f','o','r','y'], chars: [0xD45C,0xBA74]}, /* 표면 */ {keys: ['.','o','v','y'], chars: [0xD45C,0xBA74]}, /* 표면 */ {keys: ['k','r','s','x'], chars: [0xAC1D,0xAD00,0xC801]}, /* 객관적 */ {keys: ['a','k','l','r','t'], chars: [0xACBD,0xC81C,0xC801]}, /* 경제적 */ {keys: ['k','n','r','x'], chars: [0xAD6C,0xC11D]}, /* 구석 */ {keys: ['a','i','z'], chars: [0xB2F4,0xB2F9]}, /* 담당 */ {keys: ['d','f','i','o'], chars: [0xBD80,0xB300]}, /* 부대 */ {keys: ['h','n','v'], chars: [0xCCAD,0xC18C]}, /* 청소 */ {keys: ['a','h','n','r'], chars: [0xC2DC,0xCCAD]}, /* 시청 */ {keys: ['a','g','k','l','r'], chars: [0xC99D,0xAC70]}, /* 증거 */ {keys: ['c','e','k','l','r'], chars: [0xACC4,0xC808]}, /* 계절 */ {keys: ['e','g','o','u'], chars: [0xBC14,0xB298]}, /* 바늘 */ {keys: ['d','f','k','l','s'], chars: [0xC804,0xAC1C]}, /* 전개 */ {keys: ['l','v','w','x'], chars: [0xC811,0xCD09]}, /* 접촉 */ {keys: ['.','l','w','x'], chars: [0xC811,0xCD09]}, /* 접촉 */ {keys: ['b','f','h','o','s'], chars: [0xD6C4,0xBC18]}, /* 후반 */ {keys: ['f','h','o','p','s'], chars: [0xD6C4,0xBC18]}, /* 후반 */ {keys: ['d','e','h','j'], chars: [0xC77C,0xCE58]}, /* 일치 */ {keys: ['a','i','s'], chars: [0xB2F9,0xC5F0]}, /* 당연 */ {keys: ['d','i','o'], chars: [0xB300,0xBE44]}, /* 대비 */ {keys: ['l','u','v','x'], chars: [0xB0A8,0xCABD]}, /* 남쪽 */ {keys: ['a','b','f','n'], chars: [0xC218,0xC0C1]}, /* 수상 */ {keys: ['a','f','n','p'], chars: [0xC218,0xC0C1]}, /* 수상 */ {keys: ['k','l','s','z'], chars: [0xC7A0,0xAE50]}, /* 잠깐 */ {keys: ['i','l','x'], chars: [0xC801,0xB2F9]}, /* 적당 */ {keys: ['l','n','v','x'], chars: [0xC9C0,0xC18D]}, /* 지속 */ {keys: ['b','k','o','s'], chars: [0xAD6C,0xBD84]}, /* 구분 */ {keys: ['d','g','k','l','z'], chars: [0xAE08,0xC9C0]}, /* 금지 */ {keys: ['b','g','y'], chars: [0xC758,0xBB34]}, /* 의무 */ {keys: ['g','p','y'], chars: [0xC758,0xBB34]}, /* 의무 */ {keys: ['h','o','s','x'], chars: [0xD55C,0xBCF5]}, /* 한복 */ {keys: ['k','l','s','x'], chars: [0xAC74,0xCD95]}, /* 건축 */ {keys: ['k','o','s','z'], chars: [0xBC14,0xAE65]}, /* 바깥 */ {keys: ['d','l','o'], chars: [0xBC14,0xC9C0]}, /* 바지 */ {keys: ['f','k','o','s','v'], chars: [0xBCF4,0xAD00]}, /* 보관 */ {keys: ['.','f','k','o','s'], chars: [0xBCF4,0xAD00]}, /* 보관 */ {keys: [';','e','i','o'], chars: [0xBD80,0xB52A]}, /* 부딪 */ {keys: ['h','j','w'], chars: [0xC5F0,0xD569]}, /* 연합 */ {keys: ['f','l','n','r','s'], chars: [0xC0AC,0xC804]}, /* 사전 */ {keys: ['d','l','n','v','z'], chars: [0xC870,0xC2EC]}, /* 조심 */ {keys: ['.','d','l','n','z'], chars: [0xC870,0xC2EC]}, /* 조심 */ {keys: ['f','o','s','u'], chars: [0xBE44,0xB09C]}, /* 비난 */ {keys: ['d','k','n','s','v'], chars: [0xC2E0,0xACE0]}, /* 신고 */ {keys: ['.','d','k','n','s'], chars: [0xC2E0,0xACE0]}, /* 신고 */ {keys: ['a','c','f','j','n'], chars: [0xC608,0xC0C1]}, /* 예상 */ {keys: ['b','l','r','y'], chars: [0xC8FC,0xBA39]}, /* 주먹 */ {keys: ['l','p','r','y'], chars: [0xC8FC,0xBA39]}, /* 주먹 */ {keys: ['a','b','k','t'], chars: [0xAD6C,0xACBD]}, /* 구경 */ {keys: ['a','k','p','t'], chars: [0xAD6C,0xACBD]}, /* 구경 */ {keys: ['b','g','k','s'], chars: [0xADFC,0xBB34]}, /* 근무 */ {keys: ['g','k','p','s'], chars: [0xADFC,0xBB34]}, /* 근무 */ {keys: ['g','n','v'], chars: [0xC18C,0xC2A4]}, /* 소스 */ {keys: ['.','g','n'], chars: [0xC18C,0xC2A4]}, /* 소스 */ {keys: ['a','f','l','m'], chars: [0xC790,0xB791]}, /* 자랑 */ {keys: ['k','l','r','v','x'], chars: [0xACFC,0xD559,0xC801]}, /* 과학적 */ {keys: ['.','k','l','r','x'], chars: [0xACFC,0xD559,0xC801]}, /* 과학적 */ {keys: ['f','k','l','v','x'], chars: [0xACFC,0xD559,0xC790]}, /* 과학자 */ {keys: ['.','f','k','l','x'], chars: [0xACFC,0xD559,0xC790]}, /* 과학자 */ {keys: ['a','g','m','n'], chars: [0xC2B9,0xB9AC]}, /* 승리 */ {keys: ['d','f','k','l','x'], chars: [0xAC1C,0xC778,0xC801]}, /* 개인적 */ {keys: ['b','l','n','v'], chars: [0xC18C,0xC8FC]}, /* 소주 */ {keys: ['f','g','h','j'], chars: [0xC758,0xD559]}, /* 의학 */ {keys: ['e','l','s'], chars: [0xC9C4,0xCD9C]}, /* 진출 */ {keys: ['c','k','l','r'], chars: [0xC81C,0xAC70]}, /* 제거 */ {keys: ['g','k','m','z'], chars: [0xAE30,0xB984]}, /* 기름 */ {keys: ['b','g','i','n'], chars: [0xD2B9,0xC218]}, /* 특수 */ {keys: ['g','i','n','p'], chars: [0xD2B9,0xC218]}, /* 특수 */ {keys: ['e','x','y'], chars: [0xACE8,0xBAA9]}, /* 골목 */ {keys: ['f','h','j','x'], chars: [0xC720,0xD559]}, /* 유학 */ {keys: ['e','l','o','r'], chars: [0xCC98,0xBC8C]}, /* 처벌 */ {keys: ['c','f','k','o'], chars: [0xCE74,0xD398]}, /* 카페 */ {keys: ['i','k','v'], chars: [0xAC80,0xD1A0]}, /* 검토 */ {keys: ['m','v','y'], chars: [0xBAA8,0xB798]}, /* 모래 */ {keys: ['b','f','i','n'], chars: [0xB2E4,0xC218]}, /* 다수 */ {keys: ['f','i','n','p'], chars: [0xB2E4,0xC218]}, /* 다수 */ {keys: ['h','n','r','x'], chars: [0xD574,0xC11D]}, /* 해석 */ {keys: ['g','j','k','r','s'], chars: [0xADFC,0xC6D0]}, /* 근원 */ {keys: ['b','f','i','o','x'], chars: [0xBD80,0xD0C1]}, /* 부탁 */ {keys: ['f','i','o','p','x'], chars: [0xBD80,0xD0C1]}, /* 부탁 */ {keys: ['d','e','n','o'], chars: [0xC2E4,0xD328]}, /* 실패 */ {keys: ['b','j','s','t'], chars: [0xC6B0,0xC5F0]}, /* 우연 */ {keys: ['j','p','s','t'], chars: [0xC6B0,0xC5F0]}, /* 우연 */ {keys: ['a','l','m','v'], chars: [0xCD1D,0xB9AC]}, /* 총리 */ {keys: [';','q','u'], chars: [0xB208,0xBE5B]}, /* 눈빛 */ {keys: ['j','s','u','v'], chars: [0xB17C,0xC758]}, /* 논의 */ {keys: ['b','g','j','n'], chars: [0xC6B0,0xC2B9]}, /* 우승 */ {keys: ['g','j','n','p'], chars: [0xC6B0,0xC2B9]}, /* 우승 */ {keys: ['g','k','l','r','s'], chars: [0xC99D,0xAD8C]}, /* 증권 */ {keys: ['g','k','l','r','s'], chars: [0xC99D,0xAD8C]}, /* 증권 */ {keys: ['a','g','h','y'], chars: [0xD765,0xBBF8]}, /* 흥미 */ {keys: ['e','o','u'], chars: [0xBE44,0xB2D0]}, /* 비닐 */ {keys: ['a','d','f','n','y'], chars: [0xC0DD,0xBB3C]}, /* 생물 */ {keys: ['e','j','l'], chars: [0xC77C,0xC815]}, /* 일정 */ {keys: ['d','m','o','v'], chars: [0xD53C,0xB85C]}, /* 피로 */ {keys: ['.','d','m','o'], chars: [0xD53C,0xB85C]}, /* 피로 */ {keys: ['d','n','o','v'], chars: [0xBE44,0xB85C,0xC18C,0x20]}, /* 비로소 */ {keys: ['.','d','n','o'], chars: [0xBE44,0xB85C,0xC18C,0x20]}, /* 비로소 */ {keys: ['b','e','f','l'], chars: [0xC790,0xC728]}, /* 자율 */ {keys: ['e','f','l','p'], chars: [0xC790,0xC728]}, /* 자율 */ {keys: ['b','l','o','r','s'], chars: [0xC804,0xBD80]}, /* 전부 */ {keys: ['l','o','p','r','s'], chars: [0xC804,0xBD80]}, /* 전부 */ {keys: ['d','i','l','s'], chars: [0xC9C4,0xB2E8]}, /* 진단 */ {keys: ['b','i','k'], chars: [0xAD6C,0xB450]}, /* 구두 */ {keys: ['a','f','l','o','v'], chars: [0xBCF4,0xC7A5]}, /* 보장 */ {keys: ['.','a','f','l','o'], chars: [0xBCF4,0xC7A5]}, /* 보장 */ {keys: ['f','k','n','v'], chars: [0xC0AC,0xACFC]}, /* 사과 */ {keys: ['.','f','k','n'], chars: [0xC0AC,0xACFC]}, /* 사과 */ {keys: ['b','d','j','m'], chars: [0xC720,0xB9AC]}, /* 유리 */ {keys: ['d','j','m','p'], chars: [0xC720,0xB9AC]}, /* 유리 */ {keys: ['a','g','i','m'], chars: [0xB4F1,0xB85D]}, /* 등록 */ {keys: ['a','g','i','m'], chars: [0xB4F1,0xB85D]}, /* 등록 */ {keys: ['c','f','j','o'], chars: [0xC608,0xBC29]}, /* 예방 */ {keys: ['d','f','h','m','w'], chars: [0xD569,0xB9AC]}, /* 합리 */ {keys: ['g','i','k','r','w'], chars: [0xAC70,0xB4ED]}, /* 거듭 */ {keys: ['a','l','o','r'], chars: [0xC815,0xBE44]}, /* 정비 */ {keys: ['e','g','h','j'], chars: [0xC774,0xD2C0]}, /* 이틀 */ {keys: ['e','o','s'], chars: [0xBD88,0xC548]}, /* 불안 */ {keys: ['e','h','o','s'], chars: [0xBD88,0xD3B8]}, /* 불편 */ {keys: ['e','f','o','y'], chars: [0xBD88,0xC548]}, /* 불만 */ {keys: [';','h','s'], chars: [0xD558,0xC597]}, /* 하얗 */ //1701 약어 추가 {keys: ['g','h','u'], chars: [0xD558,0xB294,0x20]}, /* 하는 */ {keys: ['g','i','k','r'], chars: [0xB728,0xAC70,0xC6B4,0x20]}, /* 뜨거운 */ {keys: ['f','g','u'], chars: [0xB098,0xB294,0x20]}, /* 나는 */ {keys: ['c','u','y'], chars: [0xB9CC,0xB098,0xAC8C,0x20]}, /* 만나게 */ {keys: ['c','f','u'], chars: [0xB098,0xAC8C,0x20]}, /* 나게 */ {keys: ['d','i','u','v'], chars: [0xB418,0xB294,0x20]}, /* 되는 */ {keys: ['.','d','i','u'], chars: [0xB418,0xB294,0x20]}, /* 되는 */ {keys: ['b','j','m','s'], chars: [0xB85C,0xC6B4,0x20]}, /* 로운 */ {keys: ['g','u','y'], chars: [0xBAA8,0xB974,0xB294,0x20]}, /* 모르는 */ {keys: ['g','m','u'], chars: [0xB974,0xB294,0x20]}, /* 르는 */ {keys: ['d','g','m','u'], chars: [0xB9AC,0xB294,0x20]}, /* 리는 */ {keys: ['q','j','u'], chars: [0xC788,0xB294,0x20]}, /* 있는 */ {keys: [';','c','d','j'], chars: [0xC788,0xAC8C,0x20]}, /* 있게 */ {keys: ['j','q','u','w'], chars: [0xC5C6,0xB294,0x20]}, /* 없는 */ {keys: ['g','h','s','u'], chars: [0xCE58,0xB294,0x20]}, /* 치는 */ {keys: ['h','s','u'], chars: [0xD0A4,0xB294,0x20]}, /* 키는 */ {keys: ['c','f','h'], chars: [0xD558,0xAC8C,0x20]}, /* 하게 */ {keys: ['g','l','v'], chars: [0xC88B,0xC740,0x20]}, /* 좋은 */ {keys: ['.','g','l'], chars: [0xC88B,0xC740,0x20]}, /* 좋은 */ {keys: ['f','k','m','s'], chars: [0xCEE4,0xB2E4,0xB780,0x20]}, /* 커다란 */ {keys: ['k','s'], chars: [0xAE30,0xB294,0x20]}, /* 기는 */ {keys: ['f','g','k'], chars: [0xAC00,0xB294,0x20]}, /* 가는 */ {keys: ['f','g','k','z'], chars: [0xAC19,0xC740,0x20]}, /* 같은 */ {keys: ['b','g','j','k'], chars: [0xAFB8,0xB294,0x20]}, /* 꾸는 */ {keys: ['g','j','k','p'], chars: [0xAFB8,0xB294,0x20]}, /* 꾸는 */ {keys: ['d','f','g','u'], chars: [0xB0B4,0xB294,0x20]}, /* 내는 */ {keys: ['g','i','u'], chars: [0xB4DC,0xB294,0x20]}, /* 드는 */ {keys: ['f','g','m'], chars: [0xB77C,0xB294,0x20]}, /* 라는 */ {keys: ['c','f','m'], chars: [0xB77C,0xAC8C,0x20]}, /* 라게 */ {keys: ['j','m','r'], chars: [0xB7EC,0xC6B4,0x20]}, /* 러운 */ {keys: ['j','m','t'], chars: [0xB824,0xC6B4,0x20]}, /* 려운 */ {keys: ['d','f','m','n','s'], chars: [0xC0C8,0xB85C,0xC6B4,0x20]}, /* 새로운 */ {keys: ['b','h','s','y'], chars: [0xD765,0xBBF8,0xB85C,0xC6B4,0x20]}, /* 흥미로운 */ {keys: ['b','g','m'], chars: [0xB8E8,0xB294,0x20]}, /* 루는 */ {keys: ['g','m','p'], chars: [0xB8E8,0xB294,0x20]}, /* 루는 */ {keys: ['o','u','v'], chars: [0xBCF4,0xB294,0x20]}, /* 보는 */ {keys: ['c','k','o'], chars: [0xC058,0xAC8C,0x20]}, /* 쁘게 */ {keys: ['g','n','r'], chars: [0xC11C,0xB294,0x20]}, /* 서는 */ {keys: ['k','n','w'], chars: [0xC27D,0xAC8C,0x20]}, /* 쉽게 */ {keys: ['g','m','n','r'], chars: [0xC2A4,0xB7EC]}, /* 스러 */ {keys: ['g','m','n','r','s'], chars: [0xC2A4,0xB7EC,0xC6B4,0x20]}, /* 스러운 */ {keys: ['d','g','n','u'], chars: [0xC2DC,0xB294,0x20]}, /* 시는 */ {keys: ['a','f','g','j'], chars: [0xC54A,0xC740,0x20]}, /* 않은 */ {keys: ['a','f','g','u'], chars: [0xC54A,0xB294,0x20]}, /* 않는 */ {keys: ['j','s','u'], chars: [0xC624,0xB294,0x20]}, /* 오는 */ {keys: ['b','j','u'], chars: [0xC6B0,0xB294,0x20]}, /* 우는 */ {keys: ['g','j','u'], chars: [0xC774,0xB294,0x20]}, /* 이는 */ {keys: ['b','g','l'], chars: [0xC8FC,0xB294,0x20]}, /* 주는 */ {keys: ['g','l','p'], chars: [0xC8FC,0xB294,0x20]}, /* 주는 */ {keys: ['g','l','s','u'], chars: [0xC9C0,0xB294,0x20]}, /* 지는 */ {keys: ['c','d','h','l'], chars: [0xCE58,0xAC8C,0x20]}, /* 치게 */ {keys: ['g','k','o'], chars: [0xD504,0xAC8C,0x20]}, /* 프게 */ {keys: ['c','h','u'], chars: [0xD558,0xB294,0xB370]}, /* 하는데 */ {keys: ['c','g','h','k'], chars: [0xD06C,0xAC8C,0x20]}, /* 크게 */ {keys: ['f','g','o'], chars: [0xBC1B,0xC740,0x20]}, /* 받은 */ {keys: ['f','g','n'], chars: [0xC0AC,0xB294,0x20]}, /* 사는 */ {keys: ['g','r','y'], chars: [0xBA39,0xB294,0x20]}, /* 먹는 */ {keys: [';','n','w'], chars: [0xC2F6,0xC740,0x20]}, /* 싶은 */ {keys: ['f','g','l'], chars: [0xC791,0xC740,0x20]}, /* 작은 */ {keys: ['g','u','v'], chars: [0xB192,0xC740,0x20]}, /* 높은 */ {keys: ['.','g','u'], chars: [0xB192,0xC740,0x20]}, /* 높은 */ {keys: ['b','f','i','s'], chars: [0xB2E4,0xC6B4]}, /* 다운 */ {keys: ['f','i','p','s'], chars: [0xB2E4,0xC6B4]}, /* 다운 */ {keys: ['c','k','m','v'], chars: [0xB86D,0xAC8C,0x20]}, /* 롭게 */ {keys: ['g','s','u','y'], chars: [0xB9DE,0xB294,0x20]}, /* 맞는 */ {keys: ['m','n','w'], chars: [0xC2A4,0xB7FD]}, /* 스럽 */ {keys: ['g','n','u'], chars: [0xC4F0,0xB294,0x20]}, /* 쓰는 */ {keys: ['f','g','h','u'], chars: [0xCC3E,0xB294,0x20]}, /* 찾는 */ {keys: ['f','g','j','s'], chars: [0xC544,0xB294,0x20]}, /* 아는 */ {keys: [';','k','w'], chars: [0xAE4A,0xC740,0x20]}, /* 깊은 */ {keys: ['e','u','w'], chars: [0xB113,0xC740,0x20]}, /* 넓은 */ {keys: ['e','f','g','j','l'], chars: [0xC9E7,0xC740,0x20]}, /* 짧은 */ {keys: ['e','f','g','u'], chars: [0xB0AE,0xC740,0x20]}, /* 낮은 */ {keys: ['j','q','r','u','w'], chars: [0xC5C6,0xC560,0xB294,0x20]}, /* 없애는 */ {keys: ['g','k','r','z'], chars: [0xAC80,0xC740,0x20]}, /* 검은 */ {keys: ['e','g','k','l'], chars: [0xC990,0xAC70,0xC6B4,0x20]}, /* 즐거운 */ {keys: ['q','h','k'], chars: [0xD558,0xACA0]}, /* 하겠 */ {keys: ['q','h','j'], chars: [0xD558,0xC600]}, /* 하였 */ {keys: ['d','h','n','s'], chars: [0xD558,0xC2E0,0x20]}, /* 하신 */ {keys: ['q','h','n'], chars: [0xD558,0xC168]}, /* 하셨 */ {keys: ['q','i','k'], chars: [0xB418,0xACA0]}, /* 되겠 */ {keys: ['q','i','j'], chars: [0xB418,0xC5C8]}, /* 되었 */ {keys: ['e','i','j'], chars: [0xB4E4,0xC758,0x20]}, /* 들의 */ {keys: ['e','i'], chars: [0xB4E4,0xC774,0x20]}, /* 들이 */ {keys: ['e','i','s'], chars: [0xB4E4,0xC740,0x20]}, /* 들은 */ {keys: ['g','l','m'], chars: [0xC801,0xC73C,0xB85C,0x20]}, /* 적으로 */ {keys: ['l','s','x'], chars: [0xC801,0xC778,0x20]}, /* 적인 */ {keys: ['e','f','h','n'], chars: [0xD560,0x20,0xC218]}, /* 할 수 */ {keys: ['q','n'], chars: [0xC218,0x20,0xC788]}, /* 수 있 */ {keys: ['n','q','w'], chars: [0xC218,0x20,0xC5C6]}, /* 수 없 */ {keys: ['e','h','n','s'], chars: [0xD560,0x20,0xC218,0xB294,0x20]}, /* 할 수는 */ {keys: ['c','i','o','v'], chars: [0xB3C4,0x20,0xBD88,0xAD6C,0xD558,0xACE0,0x20]}, /* 도 불구하고 */ {keys: ['i','k','r','q'], chars: [0xAC83,0xB3C4,0x20]}, /* 것도 */ {keys: ['g','k','r'], chars: [0xAC83,0xC740,0x20]}, /* 것은 */ {keys: ['e','g','k','r'], chars: [0xAC83,0xC744,0x20]}, /* 것을 */ {keys: ['c','d','i','u'], chars: [0xB418,0xB294,0xB370]}, /* 되는데 */ {keys: ['f','g','i','u'], chars: [0xB294,0xB2E4,0x2E,0x20]}, /* 는다. */ {keys: ['f','g','s','u'], chars: [0xB294,0xAC00]}, /* 는가 */ {keys: ['g','i','s','u'], chars: [0xB2E4,0xB294,0x20]}, /* 다는 */ {keys: ['f','o','u'], chars: [0xBC1B,0xB294,0x20]}, /* 받는 */ {keys: ['d','f','o','u'], chars: [0xBCF4,0xB0B4]}, /* 보내 */ {keys: ['d','f','o','s','u'], chars: [0xBCF4,0xB0B8,0x20]}, /* 보낸 */ {keys: ['f','g','k','l'], chars: [0xAE00,0xC790]}, /* 글자 */ {keys: ['c','d','k','l'], chars: [0xC9C0,0xAC8C,0x20]}, /* 지게 */ {keys: ['c','d','h','n'], chars: [0xC790,0xC138,0xD788,0x20]}, /* 자세히 */ {keys: ['a','f','h','j','r'], chars: [0xC870,0xC6A9,0xD788,0x20]}, /* 조용히 */ {keys: ['.','a','h','j','v'], chars: [0xC870,0xC6A9,0xD788,0x20]}, /* 조용히 */ //1701 약어 수정 {keys: ['c','f','l','u'], chars: [0xC790,0xB124]}, /* 자네 */ {keys: ['c','j','r','s'], chars: [0xC5B8,0xC81C,0xB098,0x20]}, /* 언제나 */ {keys: ['d','h','n','s','v'], chars: [0xC2E0,0xD638]}, /* 신호 */ {keys: ['.','d','h','n','s'], chars: [0xC2E0,0xD638]}, /* 신호 */ {keys: ['e','g','i','r'], chars: [0xB4E4,0xC5B4]}, /* 들어 */ {keys: ['e','i','j','s'], chars: [0xC77C,0xB2E8,0x20]}, /* 일단 */ {keys: ['c','f','k','s'], chars: [0xAD00,0xACC4]}, /* 관계 */ {keys: ['a','g','r','u'], chars: [0xADF8,0xB0E5,0x20]}, /* 그냥 */ {keys: ['a','u'], chars: [0xAC00,0xB2A5]}, /* 가능 */ {keys: ['j','k','z'], chars: [0xAE08,0xC735]}, /* 금융 */ {keys: ['g','j','k','v'], chars: [0xC694,0xAE08]}, /* 요금 */ {keys: ['.','g','j','k'], chars: [0xC694,0xAE08]}, /* 요금 */ {keys: ['a','j','s','u'], chars: [0xC548,0xB155]}, /* 안녕 */ {keys: ['g','j','r','u'], chars: [0xC5B4,0xB290,0x20]}, /* 어느 */ {keys: ['f','g','k','l','z'], chars: [0xADF8,0xB9BC,0xC790]}, /* 그림자 */ {keys: ['g','k','r','s'], chars: [0xADFC,0xAC70]}, /* 근거 */ {keys: ['k','o','s'], chars: [0xBD84,0xC704,0xAE30]}, /* 분위기 */ {keys: ['i','l','z'], chars: [0xB2E4,0xC9D0]}, /* 다짐 */ {keys: ['d','l','n','s','v'], chars: [0xCD5C,0xC18C,0xD55C]}, /* 최소한 */ {keys: ['.','d','l','n','s'], chars: [0xCD5C,0xC18C,0xD55C]}, /* 최소한 */ {keys: ['e','i','n','r'], chars: [0xC124,0xD0D5]}, /* 설탕 */ {keys: ['b','f','h','s','y'], chars: [0xBB38,0xD654,0xC7AC]}, /* 문화재 */ {keys: ['f','h','p','s','y'], chars: [0xBB38,0xD654,0xC7AC]}, /* 문화재 */ {keys: ['b','f','s','y'], chars: [0xB300,0xBB38]}, /* 대문 */ {keys: ['f','p','s','y'], chars: [0xB300,0xBB38]}, /* 대문 */ {keys: ['g','m','r'], chars: [0xADF8,0xB7EC,0xBBC0,0xB85C,0x20]}, /* 그러므로 */ {keys: ['c','d','j'], chars: [0xC608,0xC678]}, /* 예외 */ {keys: ['e','j','q','u'], chars: [0xC61B,0xB0A0]}, /* 옛날 */ {keys: ['i','q','u'], chars: [0xC778,0xD130,0xB137]}, /* 인터넷 */ {keys: ['g','k','m','q'], chars: [0xADF8,0xB987]}, /* 그릇 */ {keys: ['k','m','q'], chars: [0xADF8,0xB7AC]}, /* 그랬 */ {keys: ['s','u'], chars: [0xB0B4,0xB144]} /* 내년 */ ]; K3_test_abbreviation_table = [ {phonemes: [0x1109,0x11B8], chars: [0x1109,0x1173,0x11B8,0x1102,0x1175,0x1103,0x1161,0x2E,0x20]}, /* ㅅ *ㅂ : 습니다. */ {phonemes: [0x110B,0x11B8], chars: [0xC785,0x1102,0x1175,0x1103,0x1161,0x2E,0x20]}, /* ㅇ *ㅂ : 입니다. */ {phonemes: [0x1112,0x11B8], chars: [0x1112,0x1161,0x11B8,0x1102,0x1175,0x1103,0x1161,0x2E,0x20]}, /* ㅎ *ㅂ : 합니다. */ {phonemes: [0x1112,0x11AB], chars: [0x1112,0x1161,0x110C,0x1175,0xB9CC,0x20]}, /* ㅎ *ㄴ : 하지만 */ {phonemes: [0x110C,0x1107], chars: [0x110C,0x1165,0x11BC,0x1107,0x116E]} /* ㅈㅂ : 정부 */ ]; K3_test_moachigi_hangeul_abbreviation_table = [ //{phonemes: [0x1109,0x11B8], chars: [0x1109,0x1173,0x11B8,0x1102,0x1175,0x1103,0x1161,0x2E,0x20]}, /* ㅅ *ㅂ : 습니다. */ ]; K3_test_multikey_abbreviation_table = [ {class: ['이름1'], keys: ['e','k'], chars: [0xACB0,0xACFC]}, /* 결과 */ {class: ['이름1'], keys: ['k','x'], chars: [0xAD6D,0xAC00]}, /* 국가 */ {class: ['이름1'], keys: ['f','l','s','y'], chars: [0xB9C8,0xCC2C,0xAC00,0xC9C0]}, /* 마찬가지 */ {class: ['이름2'], keys: ['k','z'], chars: [0xAC1C,0xB150]}, /* 개념 */ {class: ['이름2'], keys: ['s','u'], chars: [0xB0B4,0xB144]}, /* 내년 */ {class: ['이름2'], keys: ['m','s','t','y'], chars: [0xB77C,0xBA74]}, /* 라면 */ {class: ['이름3'], keys: ['i','k','r'], chars: [0xAC70,0xB300]}, /* 거대 */ {class: ['이름3'], keys: ['l','m','r'], chars: [0xCC98,0xB9AC]}, /* 처리 */ {class: ['이름4'], keys: ['k','o','x'], chars: [0xADF9,0xBCF5]}, /* 극복 */ {class: ['이름4'], keys: ['a','f','o','y'], chars: [0xBC29,0xBB38]}, /* 방문 */ {class: ['풀이1'], keys: ['j','q','r','w'], chars: [0xC5C6,0xC560]}, /* 없애 */ {class: ['풀이2'], keys: ['j','k','q','w'], chars: [0xB04A,0xC784,0xC5C6]}, /* 끊임없 */ {prev_class: ['이름1','이름2','이름3'], keys: ['c'], chars: [0xC5D0,0x20]}, /* 에 */ {prev_class: ['이름1','이름3'], keys: ['e','y'], chars: [0xC57C,0xB9D0,0xB85C,0x20]}, /* 야말로 */ {prev_class: ['이름2','이름4'], keys: ['e','y'], chars: [0xC774,0xC57C,0xB9D0,0xB85C,0x20]}, /* 이야말로 */ {prev_class: ['이름1','이름2','이름3','이름4'], class: ['토2'], keys: ['l','m','z'], chars: [0xCC98,0xB7FC]}, /* 처럼 */ {prev_class: ['이름1','이름3'], class: ['토2'], keys: ['m','s','t'], chars: [0xB77C,0xBA74]}, /* 라면 */ {prev_class: ['이름2','이름4'], class: ['토2'], keys: ['m','s','t'], chars: [0xC774,0xB77C,0xBA74]}, /* 이라면 */ {prev_class: ['풀이1'], keys: ['u','w'], chars: [0x11B8,0xB2C8,0xB2E4,0x2E,0x20]}, /* ㅂ니다. */ {prev_class: ['풀이2'], keys: ['u','w'], chars: [0xC2B5,0xB2C8,0xB2E4,0x2E,0x20]}, /* 습니다. */ {prev_class: ['풀이1'], keys: ['u'], chars: [0xC73C,0xB2C8,0x20]}, /* 니 */ {prev_class: ['풀이2'], keys: ['u'], chars: [0xC73C,0xB2C8,0x20]}, /* 으니 */ {prev_class: ['풀이1'], class: ['끝1'], keys: ['d','f','u','k'], chars: [0xB2C8,0xAE4C]}, /* 니까 */ {prev_class: ['풀이2'], class: ['끝2'], keys: ['d','f','u','k'], chars: [0xC73C,0xB2C8,0xAE4C]}, /* 으니까 */ {prev_class: ['이름3','이름4'], keys: ['g','h','u'], chars: [0xD558,0xB294,0x20]}, /* 하는 */ {prev_class: ['이름1','이름3','풀이1','풀이2'], keys: ['l','s','y'], chars: [0xC9C0,0xB9CC,0x20]}, /* 지만 */ {prev_class: ['이름2','이름4'], keys: ['l','s','y'], chars: [0xC774,0xC9C0,0xB9CC,0x20]}, /* 이지만 */ {prev_class: ['토1','끝1'], keys: ['s'], chars: [0x11AB,0x20]}, /* ㄴ */ {prev_class: ['토2','끝2'], keys: ['s'], chars: [0xC740,0x20]}, /* 은 */ ]; } // input_additional_combination_table_info() qs9oq47c2kxh2xxlkbn6nw9tua86r3k User:Namoroka/common.css 2 175101 740127 2026-05-02T02:37:16Z Namoroka 19627 Created page with "@import url(https://test.wikipedia.org/w/index.php?title=User:Namoroka/ohi.css&action=raw&ctype=text/css);" 740127 css text/css @import url(https://test.wikipedia.org/w/index.php?title=User:Namoroka/ohi.css&action=raw&ctype=text/css); 0w214k4g61fv1a3qxzd6rbac4dvu8lx 740129 740127 2026-05-02T03:18:24Z Namoroka 19627 Blanked the page 740129 css text/css phoiac9h4m842xq45sp7s6u21eteeq1 Store 0 175102 740131 2026-05-02T04:20:14Z Supertian8 67751 Test alignment 740131 wikitext text/x-wiki [[file:example.jpg|thumb|right]] [[category:coasters]] ocjflq10stun0no1jjyyj6eglgywpls Demo5 0 175104 740140 2026-05-02T11:44:02Z Hridyesh hg3 73385 Created page with "demo0 demo1 demo2 demo3 demo4 demo5 demo6 demo7 demo9 demo10 demo11 demo12 demo13 [[Category:WikiClub Tech UIT]]" 740140 wikitext text/x-wiki demo0 demo1 demo2 demo3 demo4 demo5 demo6 demo7 demo9 demo10 demo11 demo12 demo13 [[Category:WikiClub Tech UIT]] 8or78mj5mi6a7b03pobsuc5gcf77m0n