Modul:Hilfe:VisualEditor

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2022-01-16

local HelpVisualEditor = { suite  = "HelpVisualEditor",
                           serial = "2022-01-16",
                           item   = 63383133 }
--[=[
Unterstützung für Vorlagen/Hilfeseiten Hilfe:VisualEditor/***
]=]



local lucky, Data = pcall( mw.loadData,
                           "Module:Hilfe:VisualEditor/config" )
if type( Data ) ~= "table" then
    error( "[[Module:Hilfe:VisualEditor/config]] fehlt" )
end
local Config  = Data.config
local Widgets = { }
local Frame
local Title
local Tree



local function facet( activate )
    return activate:gsub( "%.svg$", "-progressive.svg" )
end -- facet()



local function factory( ancestor, array, all )
    -- Clone a table
    -- Parameter:
    --     ancestor  -- table, to be copied
    --     array     -- true, if sequence; number: copy first elements
    --     all       -- true, if deep elements to be copied also
    -- Returns:
    --     new table
    local r = { }
    if array then
        local n
        if type( array ) == "number" then
            n = array
        else
            n = table.maxn( ancestor )
            if n == 0 then
                for k, v in pairs( ancestor ) do
                    n = n + 1
                end -- for k, v
            end
        end
        for i = 1, n do
            table.insert( r,  ancestor[ i ] )
        end -- for i
        if all then
            local e
            for i = 1, n do
                e = r[ i ]
                if type( e ) == "table" then
                    r[ i ] = factory( e, false, true )
                end
            end -- for i
        end
    else
        for k, v in pairs( ancestor ) do
            r[ k ] = v
        end -- for k, v
        if all then
            local e
            for k, v in pairs( r ) do
                e = r[ k ]
                if type( e ) == "table" then
                    r[ k ] = factory( e, false, true )
                end
            end -- for k, v
        end
    end
    return r
end -- factory()



local function faculty( adjust )
    -- Test template arg for boolean
    --     adjust  -- string or nil
    -- Returns boolean
    local s = type( adjust )
    local r
    if s == "string" then
        r = mw.text.trim( adjust )
        r = ( r ~= ""  and  r ~= "0" )
    elseif s == "boolean" then
        r = adjust
    else
        r = false
    end
    return r
end -- faculty()



local function fallback( ask )
    -- Create similar item
    --     ask  -- string, with ID
    -- Returns table
    local insertFew = 4
    local kurzSchrift = 4
    local r
    if ask:sub( 1, 10 ) == "CodeMirror"  and  #ask > 10 then
        r = { menu    = 3,
              show    = Tree.CodeMirror.show,
              slot    = Tree.CodeMirror.slot,
              icon    = Tree.CodeMirror.icon,
              start   = Tree.CodeMirror.start,
              lowered = false }
        if ask == "CodeMirrorAktiv" then
            r.icon = facet( r.icon )
        end
    elseif ask == "InsertFew" then
        r = { menu  = -2,
              show  = Tree.InsertAll.show,
              slot  = Tree.InsertAll.slot,
              icon  = Tree.InsertAll.icon,
              start = Tree.InsertAll.start,
              entries = factory( Tree.InsertAll.entries, insertFew ) }
        r.entries[ insertFew ] = "Mehr"
    elseif ask:sub( 1, 7 ) == "Schrift" then
        local entries, k
        if ask:sub( 1, 11 ) == "SchriftKurz" then
            k = kurzSchrift
        else
            k = true
        end
        entries = factory( Tree.SchriftAlle.entries, k )
        r = { menu    = -2,
              icon    = Tree.SchriftAlle.icon,
              slot    = Tree.SchriftAlle.slot,
              entries = entries }
        if k == kurzSchrift then
            r.entries[ kurzSchrift ] = "Mehr"
            if ask == "SchriftKurz" then
                r.entries[ kurzSchrift - 1 ] = "Gestaltlos"
            else
                r.entries[ kurzSchrift - 1 ] = "GestaltlosNix"
            end
        else
            r.entries[ #r.entries - 1 ] = "GestaltlosNix"
        end
    elseif ask:sub( 1, 14 ) == "Seitenoptionen"  and  #ask > 14 then
        local sub
        r = factory( Tree.Seitenoptionen, false, true )
        for i = 2, #r.entries do
            sub = r.entries[ i ]
            if i <= 5 then
                Tree[ sub ] = factory( Tree[ sub ] )
                Tree[ sub ].lowered = true
            elseif sub == "CodeMirror" then
                if ask:sub( 20 ) == "" then
                    r.entries[ i ] = "CodeMirrorInaktiv"
                else
                    r.entries[ i ] = sub .. ask:sub( 20 )
                end
                break    -- for i
            end
        end -- for i
    end
    return r
end -- fallback()



local function fault( a )
    -- Formatiere Fehler mit class=error
    -- Parameter:
    --     a  -- string, mit Fehlermeldung
    -- Rückgabewert:
    --     string, mit HTML-Element
    local e = mw.html.create( "span" )
    e:addClass( "error" )
     :wikitext( "FEHLER * " .. a )
    return tostring( e )
end -- fault()



local function fetch( access )
    -- Retrieve static tree entry; else on the fly
    -- Parameter:
    --     access  -- string, mit ID
    -- Rückgabewert:
    --     tree entry
    local r = Tree[ access ]
    if not r then
        r = fallback( access )
    end
    return r
end -- fetch()



local function fill( apply, assume )
    -- Beschriftung ermitteln
    -- Parameter:
    --     apply   -- table
    --     assume  -- string, mit Rückfallwert
    -- Rückgabewert:
    --     string
    local r
    if apply.slot then
        r = mw.message.new( apply.slot:gsub( "^@", "visualeditor-" ) )
        if not r:exists() then
            r = false
        end
    end
    if r then
        r = r:plain()
    elseif apply.show then
        r = apply.show
    else
        r = assume
    end
    return r
end -- fill()



local function flat( a )
    -- Eingabewert trimmen; leeren Wert ignorieren
    -- Parameter:
    --     a  -- string oder nil
    -- Rückgabewert:
    --     string oder nil oder false
    local r
    if a then
        r = mw.text.trim( a )
        if r == "" then
            r = false
        end
    end
    return r
end -- flat()



Widgets.dropdown2 = function ( all, accessed, aim, above, active, align )
    local r
    if all.entries then
        local cssT  = { "background-color:#FFFFFF",
                        "border-collapse:collapse",
                        "margin-left:.5em",
                        "margin-bottom:.5em" }
        local high  = { }
        local low   = ( all.menu < 0 )
        local e, entry, launch, show, short, sign, space, strong, style
        if aim then
            e = mw.text.split( aim, "+", true )
            for k, v in pairs( e ) do
                high[ v ] = true
            end -- for k, v
        end
        if above then
            if type( above ) == "string" then
                table.insert( cssT,  "min-width:" .. above )
            end
        else
            table.insert( cssT, "width:100%" )
        end
        style = table.concat( cssT, ";" )
        r     = "\n{|"
        if above then
            local pars  = { background = "#" .. Config.bgMainSel,
                            div = true }
            local sep   = "style='border%%s: #%s 1px solid;%%s'"
            sep   = string.format( sep, Config.borderDd )
            style = style .. ";box-shadow: 0 2px 2px 0 rgba(0,0,0,0.25)"
            r     = string.format( "%s %s\n|%s|%s",
                                   r,
                                   string.format( sep, "", style ),
                                   string.format( sep, "-bottom", "" ),
                                   Widgets.toolItem( accessed, pars ) )
        else
          cssT  = { style,
                      "font-size:90%" }
            r = string.format( "%s style='%s'",
                               r,
                               table.concat( cssT, ";" ) )
        end
        space = false
        for k, v in pairs( all.entries ) do
            entry  = fetch( v )
            launch = high[ v ]
            if not entry then
                return fault( "dropdown: Bad entry " .. v )
            end
            if launch then
                if low  or  Config.itemSel == 1 then
                    strong = "background-color: #" .. Config.bgItemSel
                else
                    strong = "border: 2px solid #" .. Config.borderSel
                end
                high[ v ] = false
            elseif entry.lowered then
                strong = "opacity:0.5"
            else
                strong = ""
            end
            if strong ~= "" then
                strong = string.format( "style='%s'|", strong )
            end
            if entry.icon then
                sign = entry.icon
                if launch  and  not entry.lock   and
                   sign:match( "^OOjs UI .+%.svg$" ) then
                    sign = facet( sign )
                end
            elseif launch and all.leader then
                sign = "OOjs UI icon check-progressive.svg"
            else
                sign = false
            end
            if sign then
                sign = string.format( "[[File:%s|%dpx|alt=|icon]]",
                                      sign,
                                      entry.px or Config.icon )
            else
                if not space then
                    space = string.format( "%dpx", Config.icon )
                    e     = mw.html.create( "span" )
                    e:css( { ["display"] = "inline-block",
                             ["width"]   = space } )
                     :wikitext( "&#160;" )
                    space = tostring( e )
                end
                sign = space
            end
            show = fill( entry, v )
            if active and entry.smart then
                local s
                e = mw.html.create( "span" )
                if launch then
                    s = "#3366CC"
                else
                    s = "#444444"
                end
                e:css( "color", s )
                 :wikitext( show )
                show = string.format( "[[#%s|%s]]",
                                      entry.smart,
                                      tostring( e ) )
                launch = false
            end
            if entry.style or launch then
                e = mw.html.create( "span" )
                if entry.style then
                    e:cssText( entry.style )
                end
                if launch then
                    e:css( "color", "#3366CC" )
                end
                e:wikitext( show )
                show = tostring( e )
            end
            e = mw.html.create( "div" )
            e:css( { ["float"]         = "left",
                     ["padding-right"] = "3px",
                     ["white-space"]   = "pre" } )
             :wikitext(  string.format( "%s %s", sign, show )  )
            show = tostring( e )
            if entry.shortcut then
                if not Title then
                    Title = mw.title.getCurrentTitle()
                end
                if Title.text == Config.single then
                    short = "#VEshortcuts"
                else
                    short = string.format( "%s:%s",
                                           mw.site.namespaces[12].name,
                                           Config.shortcut )
                end
                e = mw.html.create( "div" )
                e:css( { ["float"]        = "right",
                         ["opacity"]      = "0.5",
                         ["margin-left"]  = "0.8em",
                         ["margin-right"] = "0.3em",
                         ["white-space"]  = "nowrap" } )
                 :wikitext( string.format( "[[%s|%s]]",
                                           short,
                                           entry.shortcut ) )
                short = tostring( e )
            elseif entry.iconRight then
                e = mw.html.create( "div" )
                e:css( { ["float"]        = "right",
                         ["margin-left"]  = "0.8em",
                         ["margin-right"] = "0.3em" } )
                 :wikitext( string.format( "[[File:%s|%dpx]]",
                                           entry.iconRight,
                                           entry.pxR or Config.icon ) )
                short = tostring( e )
            else
                short = ""
            end
            r = string.format( "%s\n|-\n|%s %s %s",
                               r, strong, show, short )
        end -- for k, v
        r = r .. "\n|}"
        if aim then
            show = false
            for k, v in pairs( e ) do
                if high[ v ] then
                    if show then
                        show = string.format( "%s+%s", show, k )
                    else
                        show = k
                    end
                end
            end -- for k, v
            if show then
                r = r .. fault( "dropdown: Auswahl fehlt: " .. show )
            end
        end
    else
        r = fault( "dropdown: Keine .entries" )
    end
    return r
end -- Widgets.dropdown2()



Widgets.progressive = function ( about, adjust )
    local e    = mw.html.create( "span" )
    local size = "1em"
    local r
    if adjust.div  or  adjust.mini then
        size = "0.8em"
        e:css( { ["border-radius"] = "0",
                 ["font-size"] = "75%" } )
    end
    e:addClass( "mw-ui-button mw-ui-progressive" )
     :css( { ["color"]       = "#FFFFFF",
             ["line-height"] = size,
             ["min-width"]   = "none" } )
     :wikitext( about )
    r = tostring( e )
    if Config.tstyleUI then
        r = Frame:extensionTag( "templatestyles",
                                nil,
                                { src = Config.tstyleUI } )
            .. r
    end
    return r
end -- Widgets.progressive()



Widgets.toolbar = function ( ahead, after )
    local div      = { div = true,
                       ["line-height"] = "1em" }
    local element  = mw.html.create( "div" )
    local entries  = factory( Tree.TOOLBAR.entries, true )
    local n        = #entries
    local surround = "1px solid #" .. Config.borderBar
    local entry, s
    element:css( { ["background"] = "#FFFFFF",
                   ["border"]     = surround,
                   ["float"]      = "left",
                   ["margin"]     = "0" } )
    if ahead >= 1  and  after >= 1 then
        element:css( { ["width"] = "100%" } )
    end
    if after > 0 then
        local rechts = mw.html.create( "div" )
        rechts:css( { ["float"]       = "right",
                      ["margin-left"] = "1em",
                      ["white-space"] = "nowrap" } )
        if ahead > 0 then
            rechts:css( { ["border-left"] = surround } )
        end
        for i = 5, n do
            s     = entries[ i ]
            entry = Tree[ s ]
            if entry.rechts then
                rechts:wikitext( Widgets.toolItem( s, div ) )
            end
        end -- for i
        element:wikitext( tostring( rechts ) )
    end
    if ahead > 0 then
        for i = 1, n do
            s     = entries[ i ]
            entry = Tree[ s ]
            if entry.links  and  ahead >= entry.links then
                element:wikitext( Widgets.toolItem( s, div ) )
            end
        end -- for i
    end
    entry = mw.html.create( "div" )
    entry:css( { ["clear"] = "both" } )
    element:wikitext( tostring( entry ) )
    return tostring( element )
end -- Widgets.toolbar()



Widgets.toolItem = function ( access, adjust )
    local details = factory( fetch( access ),  false )
    local element, low, r, shift, show, smart
    if adjust then
        for k, v in pairs( adjust ) do
            details[ k ] = v
        end -- for k, v
    end
    low = ( details.border  or  details.menu ~= 2 )
          and   not details.div
    if low then
        element = "span"
    else
        element = "div"
    end
    element = mw.html.create( element )
    if details.border and details.icon then
        show = ""
    else
        show = fill( details, access )
        if not low then
            local height = adjust["line-height"]
            element:css( { ["float"] = "left" } )
            if height then
                element:css( { ["line-height"] = height } )
            end
        end
        if not Title then
            Title = mw.title.getCurrentTitle()
        end
        if Title.text == Config.single then
            if details.swift then
                shift = "#" .. details.swift
            end
        elseif details.start then
            shift = string.format( "%s:%s",
                                   mw.site.namespaces[12].name,
                                   details.start )
            if shift == Title.prefixedText then
                shift = false
            end
        end
        if details.progressive then
            show = Widgets.progressive( show, details )
        end
        if shift then
            show = string.format( "[[%s|%s]]", shift, show )
        end
    end
    if details.smart then
        smart = details.smart
        if details.shortcut then
            smart = string.format( "%s %s %s",
                                   smart,
                                   mw.ustring.char( 8211 ),
                                   details.shortcut )
        end
    elseif details.shortcut then
        smart = details.shortcut
    end
    element:css( { ["white-space"] = "nowrap" } )
    if not details.progressive then
        local graphics = details.icon or details.before
        if details.border then
            element:css( { ["border"]  = "1px solid #"
                                         .. Config.borderBar,
                           ["padding"] = "2px 5px" } )
        else
            element:css( { ["padding"] = "0 5px" } )
            if type( details.borderR ) ~= "boolean" then
                details.borderR = true
            end
            if details.background then
                element:css( { ["background"] = details.background } )
            end
            if details.borderR then
                element:css( { ["border-right"] = "1px solid #"
                                                  .. Config.borderBar } )
            end
        end
        if graphics then
            local k, s
            if type( graphics ) == "table" then
                details.px  = graphics.px
                details.top = graphics.top
                graphics    = graphics.img  or  "example.svg"
            else
                show = ""
            end
            if type( details.px ) == "number" then
                k = details.px
            else
                k = Config.mainPX
            end
--          if details.align == "right" then
--              s = "|right"
--          end
            if details.top then
                s = "|top"
            end
            show = string.format( "[[File:%s|%dpx%s|link=%s|alt=%s|%s]]%s",
                                  graphics,
                                  k,
                                  s     or "",
                                  shift or "",
                                  smart or "",
                                  smart or "icon",
                                  show )
        end
        if details.div  and  not details.icon then
            element:css( { ["padding-bottom"] = "3px",
                           ["padding-top"]    = "2px" } )
        end
        if details.entries  and  not details.less then
            s    = smart or "Dropdown-Menü"
            show = string.format( "%s [[File:%s|%dpx|link=|alt=%s|%s]]",
                                  show,
                                  Config.openDown.src,
                                  Config.openDown.px,
                                  s,
                                  s )
        end
    end
    if smart then
        element:attr( "title", smart )
    end
    element:wikitext( show )
    if details.name then
        show = fill( details, access )
        if math.abs( details.name ) > 1 then
            show = string.format( "'''%s'''", show )
        end
        if details.name > 0 then
            r = string.format( "%s %s", tostring( element ), show )
        else
            r = string.format( "%s %s", show, tostring( element ) )
        end
    else
        r = tostring( element )
    end
    return r
end -- Widgets.toolItem()



Tree = factory( Data.tree )



HelpVisualEditor.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata",
    --                or false
    -- Postcondition:
    --     Returns  string with appropriate version, or false
    local since = atleast
    local r
    if since == "wikidata" then
        local item = HelpVisualEditor.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local vsn = entity:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string" and
                   vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if not r then
        if not since  or  since <= HelpVisualEditor.serial then
            r = HelpVisualEditor.serial
        else
            r = false
        end
    end
    return r
end -- HelpVisualEditor.failsafe()



-- Export
local p = { }

p.dropdown = function ( frame )
    -- Tropfrunter
    local params = frame:getParent().args
    local scope  = flat( params[ 1 ] )
    local r
    if scope then
        local path = fetch( scope )
        if path then
            local leader = not faculty( params.nohead )
                           or  faculty( params.head )
            local link   = faculty( params.link )
            local widget = string.format( "dropdown%d",
                                          math.abs( path.menu ) )
            Frame = frame
            if leader and params.head and
               params.head:match( "^%d+%l+$" ) then
                leader = params.head
            end
            r = Widgets[ widget ]( path,
                                   scope,
                                   flat( params[ 2 ] ),
                                   leader,
                                   link,
                                   flat( params.float ) )
        else
            r = fault( "dropdown * Menü unbekannt: " .. scope )
        end
    else
        r = fault( "dropdown: Kein Menü" )
    end
    return r
end -- p.dropdown()



p.headline = function ( frame )
    -- Dynamische Überschrift
    local pages  = { }
    local params = frame:getParent().args
    local parts  = { }
    local e, m, s, show
    Title = mw.title.getCurrentTitle()
    for k, v in pairs( params ) do
        s = type( k )
        if s == "number" then
            if k == 1 then
                if v:match( "^%s*[1-6]%s*$" ) then
                    m = tonumber( v )
                else
                    show = fault( "h-Zahl ungültig:" .. v )
                end
            elseif k == 2  and not show then
                v = mw.text.trim( v )
                if v ~= "" then
                    show = v
                end
            end
        elseif s == "string" then
            if k == "Gesamt" then
                k = Config.single
            end
            if k:find( "/", 2, true ) then
                pages[ k:gsub( "^%./", "" ) ] = v
            elseif k:sub( 1, 5 ) == "Anker" then
                if v ~= "" then
                    table.insert( parts, v )
                end
            end
        end
    end -- for k, v
    if not m then
        m = 2
    end
    if not pages[ Config.single ] then
        pages[ Config.single ] = tostring( m + 1 )
    end
    if Title.namespace == 12 then
        s = pages[ Title.text ]
        if s then
            if s:match( "^[1-6]$" ) then
                m = tonumber( s )
            else
                show = fault( "h-Zahl ungültig:" .. s )
            end
        end
    end
    if not show then
        show = fault( "Überschrift fehlt" )
    end
    e = mw.html.create( string.format( "h%d",  m ) )
    m = table.maxn( parts )
    if m > 0 then
        e:attr( "id",  mw.uri.anchorEncode( parts[ 1 ] ) )
        if m > 1 then
            local el
            for i = 2, m do
                el = mw.html.create( "span" )
                el:attr( "id",  mw.uri.anchorEncode( parts[ i ] ) )
                show = tostring( el ) .. show
            end    -- for i
        end
    end
    e:wikitext( show )
    return tostring( e )
end -- p.headline()



p.icon = function ( frame )
    -- Icon per ID
    local params = frame:getParent().args
    local sketch = flat( params[ 1 ] )
    local r
    if sketch then
        local entry = Tree[ sketch ]
        if entry then
            r = entry.icon
            if r then
                local icon = flat( params[ 2 ] )
                if icon then
                    icon = tonumber( icon ) or 1
                else
                    icon = Config.icon
                end
                if icon > 7 then
                    r = string.format( "[[File:%s|%dpx|alt=|icon]]",
                                       r, icon )
                else
                    r = fault( "icon * zu klein: " .. tostring( icon ) )
                end
            else
                r = fault( "icon * unbebildert: " .. sketch )
            end
        else
            r = fault( "icon * unbekannt: " .. sketch )
        end
    else
        r = fault( "icon: Keine ID" )
    end
    return r
end -- p.icon()



p.progressive = function ( frame )
    -- Button auf blau
    local params  = frame:getParent().args
    Frame = frame
    return Widgets.progressive( params[ 1 ] or "??????????????",
                                { mini = faculty( params.mini ) } )
end -- p.progressive()



p.toolbar = function ( frame )
    -- Hauptmenü
    local params = frame:getParent().args
    local links  = flat( params.links )   or  1
    local rechts = flat( params.rechts )  or  1
    local r
    if links == "½" then
        links = 0.5
    elseif links == "¾" then
        links = 0.75
    elseif links == mw.ustring.char( 8540 ) then    --  3/8
        links = 0.375
    else
        links = tonumber( links )  or  0
    end
    rechts = tonumber( rechts )  or  0
    if links > 0  or  rechts > 0 then
        Frame = frame
        r     = Widgets.toolbar( links, rechts )
    else
        r = fault( "toolbar: links und rechts 0" )
    end
    return r
end -- p.toolbar()



p.toolitem = function ( frame )
    -- Hauptmenü-Element
    local params = frame:getParent().args
    local scope  = flat( params[ 1 ] )
    local r
    if scope then
        local branch = Tree[ scope ]
        if branch then
            local name = params.name
            local pars = { }
            if branch.menu == 2 then
                pars.border = true
            else
                pars.borderR = false
            end
            if name  and  name:match( "^[-+]?[12]$" ) then
                pars.name = tonumber( name )
            end
            Frame = frame
            r     = Widgets.toolItem( scope, pars )
        else
            r = fault( "toolitem * Item unbekannt: " .. scope )
        end
    else
        r = fault( "toolitem: Kein Item" )
    end
    return r
end -- p.toolitem()



p.failsafe = function ( frame )
    -- Check or retrieve version information
    -- Precondition:
    --     frame  -- object; #invoke environment
    -- Postcondition:
    --     Return string with error message or ""
    -- Uses:
    --     HelpVisualEditor.failsafe()
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return HelpVisualEditor.failsafe( since )  or  ""
end -- p.failsafe()

p.HelpVisualEditor = function ()
    -- Module interface
    return HelpVisualEditor
end

return p