aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--coverage.html682
2 files changed, 1 insertions, 682 deletions
diff --git a/.gitignore b/.gitignore
index a9c68ae..915ccf5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
gomuks
gomuks.exe
coverage.out
+coverage.html
diff --git a/coverage.html b/coverage.html
deleted file mode 100644
index c3d77dd..0000000
--- a/coverage.html
+++ /dev/null
@@ -1,682 +0,0 @@
-
-<!DOCTYPE html>
-<html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <style>
- body {
- background: black;
- color: rgb(80, 80, 80);
- }
- body, pre, #legend span {
- font-family: Menlo, monospace;
- font-weight: bold;
- }
- #topbar {
- background: black;
- position: fixed;
- top: 0; left: 0; right: 0;
- height: 42px;
- border-bottom: 1px solid rgb(80, 80, 80);
- }
- #content {
- margin-top: 50px;
- }
- #nav, #legend {
- float: left;
- margin-left: 10px;
- }
- #legend {
- margin-top: 12px;
- }
- #nav {
- margin-top: 10px;
- }
- #legend span {
- margin: 0 5px;
- }
- .cov0 { color: rgb(192, 0, 0) }
-.cov1 { color: rgb(128, 128, 128) }
-.cov2 { color: rgb(116, 140, 131) }
-.cov3 { color: rgb(104, 152, 134) }
-.cov4 { color: rgb(92, 164, 137) }
-.cov5 { color: rgb(80, 176, 140) }
-.cov6 { color: rgb(68, 188, 143) }
-.cov7 { color: rgb(56, 200, 146) }
-.cov8 { color: rgb(44, 212, 149) }
-.cov9 { color: rgb(32, 224, 152) }
-.cov10 { color: rgb(20, 236, 155) }
-
- </style>
- </head>
- <body>
- <div id="topbar">
- <div id="nav">
- <select id="files">
-
- <option value="file0">maunium.net/go/gomuks/matrix/pushrules/action.go (51.6%)</option>
-
- <option value="file1">maunium.net/go/gomuks/matrix/pushrules/condition.go (97.5%)</option>
-
- <option value="file2">maunium.net/go/gomuks/matrix/pushrules/pushrules.go (50.0%)</option>
-
- <option value="file3">maunium.net/go/gomuks/matrix/pushrules/rule.go (13.6%)</option>
-
- <option value="file4">maunium.net/go/gomuks/matrix/pushrules/ruleset.go (52.9%)</option>
-
- </select>
- </div>
- <div id="legend">
- <span>not tracked</span>
-
- <span class="cov0">not covered</span>
- <span class="cov8">covered</span>
-
- </div>
- </div>
- <div id="content">
-
- <pre class="file" id="file0" style="display: none">// gomuks - A terminal Matrix client written in Go.
-// Copyright (C) 2018 Tulir Asokan
-//
-// 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 3 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.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
-
-package pushrules
-
-import "encoding/json"
-
-// PushActionType is the type of a PushAction
-type PushActionType string
-
-// The allowed push action types as specified in spec section 11.12.1.4.1.
-const (
- ActionNotify PushActionType = "notify"
- ActionDontNotify PushActionType = "dont_notify"
- ActionCoalesce PushActionType = "coalesce"
- ActionSetTweak PushActionType = "set_tweak"
-)
-
-// PushActionTweak is the type of the tweak in SetTweak push actions.
-type PushActionTweak string
-
-// The allowed tweak types as specified in spec section 11.12.1.4.1.1.
-const (
- TweakSound PushActionTweak = "sound"
- TweakHighlight PushActionTweak = "highlight"
-)
-
-// PushActionArray is an array of PushActions.
-type PushActionArray []*PushAction
-
-// PushActionArrayShould contains the important information parsed from a PushActionArray.
-type PushActionArrayShould struct {
- // Whether or not the array contained a Notify, DontNotify or Coalesce action type.
- NotifySpecified bool
- // Whether or not the event in question should trigger a notification.
- Notify bool
- // Whether or not the event in question should be highlighted.
- Highlight bool
-
- // Whether or not the event in question should trigger a sound alert.
- PlaySound bool
- // The name of the sound to play if PlaySound is true.
- SoundName string
-}
-
-// Should parses this push action array and returns the relevant details wrapped in a PushActionArrayShould struct.
-func (actions PushActionArray) Should() (should PushActionArrayShould) <span class="cov8" title="1">{
- for _, action := range actions </span><span class="cov8" title="1">{
- switch action.Action </span>{
- case ActionNotify, ActionCoalesce:<span class="cov0" title="0">
- should.Notify = true
- should.NotifySpecified = true</span>
- case ActionDontNotify:<span class="cov8" title="1">
- should.Notify = false
- should.NotifySpecified = true</span>
- case ActionSetTweak:<span class="cov0" title="0">
- switch action.Tweak </span>{
- case TweakHighlight:<span class="cov0" title="0">
- var ok bool
- should.Highlight, ok = action.Value.(bool)
- if !ok </span><span class="cov0" title="0">{
- // Highlight value not specified, so assume true since the tweak is set.
- should.Highlight = true
- }</span>
- case TweakSound:<span class="cov0" title="0">
- should.SoundName = action.Value.(string)
- should.PlaySound = len(should.SoundName) &gt; 0</span>
- }
- }
- }
- <span class="cov8" title="1">return</span>
-}
-
-// PushAction is a single action that should be triggered when receiving a message.
-type PushAction struct {
- Action PushActionType
- Tweak PushActionTweak
- Value interface{}
-}
-
-// UnmarshalJSON parses JSON into this PushAction.
-//
-// * If the JSON is a single string, the value is stored in the Action field.
-// * If the JSON is an object with the set_tweak field, Action will be set to
-// "set_tweak", Tweak will be set to the value of the set_tweak field and
-// and Value will be set to the value of the value field.
-// * In any other case, the function does nothing.
-func (action *PushAction) UnmarshalJSON(raw []byte) error <span class="cov8" title="1">{
- var data interface{}
-
- err := json.Unmarshal(raw, &amp;data)
- if err != nil </span><span class="cov0" title="0">{
- return err
- }</span>
-
- <span class="cov8" title="1">switch val := data.(type) </span>{
- case string:<span class="cov8" title="1">
- action.Action = PushActionType(val)</span>
- case map[string]interface{}:<span class="cov8" title="1">
- tweak, ok := val["set_tweak"].(string)
- if ok </span><span class="cov8" title="1">{
- action.Action = ActionSetTweak
- action.Tweak = PushActionTweak(tweak)
- action.Value, _ = val["value"]
- }</span>
- }
- <span class="cov8" title="1">return nil</span>
-}
-
-// MarshalJSON is the reverse of UnmarshalJSON()
-func (action *PushAction) MarshalJSON() (raw []byte, err error) <span class="cov0" title="0">{
- if action.Action == ActionSetTweak </span><span class="cov0" title="0">{
- data := map[string]interface{}{
- "set_tweak": action.Tweak,
- "value": action.Value,
- }
- return json.Marshal(&amp;data)
- }</span><span class="cov0" title="0"> else {
- data := string(action.Action)
- return json.Marshal(&amp;data)
- }</span>
-}
-</pre>
-
- <pre class="file" id="file1" style="display: none">// gomuks - A terminal Matrix client written in Go.
-// Copyright (C) 2018 Tulir Asokan
-//
-// 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 3 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.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
-
-package pushrules
-
-import (
- "regexp"
- "strconv"
- "strings"
-
- "github.com/zyedidia/glob"
- "maunium.net/go/gomatrix"
- "maunium.net/go/gomuks/matrix/rooms"
-)
-
-// Room is an interface with the functions that are needed for processing room-specific push conditions
-type Room interface {
- GetMember(mxid string) *rooms.Member
- GetMembers() map[string]*rooms.Member
- GetSessionOwner() *rooms.Member
-}
-
-// PushCondKind is the type of a push condition.
-type PushCondKind string
-
-// The allowed push condition kinds as specified in section 11.12.1.4.3 of r0.3.0 of the Client-Server API.
-const (
- KindEventMatch PushCondKind = "event_match"
- KindContainsDisplayName PushCondKind = "contains_display_name"
- KindRoomMemberCount PushCondKind = "room_member_count"
-)
-
-// PushCondition wraps a condition that is required for a specific PushRule to be used.
-type PushCondition struct {
- // The type of the condition.
- Kind PushCondKind `json:"kind"`
- // The dot-separated field of the event to match. Only applicable if kind is EventMatch.
- Key string `json:"key,omitempty"`
- // The glob-style pattern to match the field against. Only applicable if kind is EventMatch.
- Pattern string `json:"pattern,omitempty"`
- // The condition that needs to be fulfilled for RoomMemberCount-type conditions.
- // A decimal integer optionally prefixed by ==, &lt;, &gt;, &gt;= or &lt;=. Prefix "==" is assumed if no prefix found.
- MemberCountCondition string `json:"is,omitempty"`
-}
-
-// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions.
-var MemberCountFilterRegex = regexp.MustCompile("^(==|[&lt;&gt;]=?)?([0-9]+)$")
-
-// Match checks if this condition is fulfilled for the given event in the given room.
-func (cond *PushCondition) Match(room Room, event *gomatrix.Event) bool <span class="cov8" title="1">{
- switch cond.Kind </span>{
- case KindEventMatch:<span class="cov8" title="1">
- return cond.matchValue(room, event)</span>
- case KindContainsDisplayName:<span class="cov8" title="1">
- return cond.matchDisplayName(room, event)</span>
- case KindRoomMemberCount:<span class="cov8" title="1">
- return cond.matchMemberCount(room, event)</span>
- default:<span class="cov8" title="1">
- return false</span>
- }
-}
-
-func (cond *PushCondition) matchValue(room Room, event *gomatrix.Event) bool <span class="cov8" title="1">{
- index := strings.IndexRune(cond.Key, '.')
- key := cond.Key
- subkey := ""
- if index &gt; 0 </span><span class="cov8" title="1">{
- subkey = key[index+1:]
- key = key[0:index]
- }</span>
-
- <span class="cov8" title="1">pattern, _ := glob.Compile(cond.Pattern)
-
- switch key </span>{
- case "type":<span class="cov8" title="1">
- return pattern.MatchString(event.Type)</span>
- case "sender":<span class="cov8" title="1">
- return pattern.MatchString(event.Sender)</span>
- case "room_id":<span class="cov8" title="1">
- return pattern.MatchString(event.RoomID)</span>
- case "state_key":<span class="cov8" title="1">
- if event.StateKey == nil </span><span class="cov8" title="1">{
- return cond.Pattern == ""
- }</span>
- <span class="cov8" title="1">return pattern.MatchString(*event.StateKey)</span>
- case "content":<span class="cov8" title="1">
- val, _ := event.Content[subkey].(string)
- return pattern.MatchString(val)</span>
- default:<span class="cov8" title="1">
- return false</span>
- }
-}
-
-func (cond *PushCondition) matchDisplayName(room Room, event *gomatrix.Event) bool <span class="cov8" title="1">{
- member := room.GetSessionOwner()
- if member == nil || member.UserID == event.Sender </span><span class="cov8" title="1">{
- return false
- }</span>
- <span class="cov8" title="1">text, _ := event.Content["body"].(string)
- return strings.Contains(text, member.DisplayName)</span>
-}
-
-func (cond *PushCondition) matchMemberCount(room Room, event *gomatrix.Event) bool <span class="cov8" title="1">{
- group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition)
- if len(group) != 3 </span><span class="cov8" title="1">{
- return false
- }</span>
-
- <span class="cov8" title="1">operator := group[1]
- wantedMemberCount, _ := strconv.Atoi(group[2])
-
- memberCount := len(room.GetMembers())
-
- switch operator </span>{
- case "==", "":<span class="cov8" title="1">
- return memberCount == wantedMemberCount</span>
- case "&gt;":<span class="cov8" title="1">
- return memberCount &gt; wantedMemberCount</span>
- case "&gt;=":<span class="cov8" title="1">
- return memberCount &gt;= wantedMemberCount</span>
- case "&lt;":<span class="cov8" title="1">
- return memberCount &lt; wantedMemberCount</span>
- case "&lt;=":<span class="cov8" title="1">
- return memberCount &lt;= wantedMemberCount</span>
- default:<span class="cov0" title="0">
- // Should be impossible due to regex.
- return false</span>
- }
-}
-</pre>
-
- <pre class="file" id="file2" style="display: none">package pushrules
-
-import (
- "encoding/json"
- "net/url"
-
- "maunium.net/go/gomatrix"
-)
-
-// GetPushRules returns the push notification rules for the global scope.
-func GetPushRules(client *gomatrix.Client) (*PushRuleset, error) <span class="cov0" title="0">{
- return GetScopedPushRules(client, "global")
-}</span>
-
-// GetScopedPushRules returns the push notification rules for the given scope.
-func GetScopedPushRules(client *gomatrix.Client, scope string) (resp *PushRuleset, err error) <span class="cov0" title="0">{
- u, _ := url.Parse(client.BuildURL("pushrules", scope))
- // client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
- u.Path += "/"
- _, err = client.MakeRequest("GET", u.String(), nil, &amp;resp)
- return
-}</span>
-
-// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON.
-func EventToPushRules(event *gomatrix.Event) (*PushRuleset, error) <span class="cov8" title="1">{
- content, _ := event.Content["global"]
- raw, err := json.Marshal(content)
- if err != nil </span><span class="cov0" title="0">{
- return nil, err
- }</span>
-
- <span class="cov8" title="1">ruleset := &amp;PushRuleset{}
- err = json.Unmarshal(raw, ruleset)
- if err != nil </span><span class="cov0" title="0">{
- return nil, err
- }</span>
-
- <span class="cov8" title="1">return ruleset, nil</span>
-}
-</pre>
-
- <pre class="file" id="file3" style="display: none">// gomuks - A terminal Matrix client written in Go.
-// Copyright (C) 2018 Tulir Asokan
-//
-// 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 3 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.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
-
-package pushrules
-
-import (
- "github.com/zyedidia/glob"
- "maunium.net/go/gomatrix"
-)
-
-type PushRuleCollection interface {
- GetActions(room Room, event *gomatrix.Event) PushActionArray
-}
-
-type PushRuleArray []*PushRule
-
-func (rules PushRuleArray) setType(typ PushRuleType) PushRuleArray <span class="cov8" title="1">{
- for _, rule := range rules </span><span class="cov8" title="1">{
- rule.Type = typ
- }</span>
- <span class="cov8" title="1">return rules</span>
-}
-
-func (rules PushRuleArray) GetActions(room Room, event *gomatrix.Event) PushActionArray <span class="cov0" title="0">{
- for _, rule := range rules </span><span class="cov0" title="0">{
- if !rule.Match(room, event) </span><span class="cov0" title="0">{
- continue</span>
- }
- <span class="cov0" title="0">return rule.Actions</span>
- }
- <span class="cov0" title="0">return nil</span>
-}
-
-type PushRuleMap struct {
- Map map[string]*PushRule
- Type PushRuleType
-}
-
-func (rules PushRuleArray) setTypeAndMap(typ PushRuleType) PushRuleMap <span class="cov8" title="1">{
- data := PushRuleMap{
- Map: make(map[string]*PushRule),
- Type: typ,
- }
- for _, rule := range rules </span><span class="cov0" title="0">{
- rule.Type = typ
- data.Map[rule.RuleID] = rule
- }</span>
- <span class="cov8" title="1">return data</span>
-}
-
-func (ruleMap PushRuleMap) GetActions(room Room, event *gomatrix.Event) PushActionArray <span class="cov0" title="0">{
- var rule *PushRule
- var found bool
- switch ruleMap.Type </span>{
- case RoomRule:<span class="cov0" title="0">
- rule, found = ruleMap.Map[event.RoomID]</span>
- case SenderRule:<span class="cov0" title="0">
- rule, found = ruleMap.Map[event.Sender]</span>
- }
- <span class="cov0" title="0">if found &amp;&amp; rule.Match(room, event) </span><span class="cov0" title="0">{
- return rule.Actions
- }</span>
- <span class="cov0" title="0">return nil</span>
-}
-
-func (ruleMap PushRuleMap) unmap() PushRuleArray <span class="cov0" title="0">{
- array := make(PushRuleArray, len(ruleMap.Map))
- index := 0
- for _, rule := range ruleMap.Map </span><span class="cov0" title="0">{
- array[index] = rule
- index++
- }</span>
- <span class="cov0" title="0">return array</span>
-}
-
-type PushRuleType string
-
-const (
- OverrideRule PushRuleType = "override"
- ContentRule PushRuleType = "content"
- RoomRule PushRuleType = "room"
- SenderRule PushRuleType = "sender"
- UnderrideRule PushRuleType = "underride"
-)
-
-type PushRule struct {
- // The type of this rule.
- Type PushRuleType `json:"-"`
- // The ID of this rule.
- // For room-specific rules and user-specific rules, this is the room or user ID (respectively)
- // For other types of rules, this doesn't affect anything.
- RuleID string `json:"rule_id"`
- // The actions this rule should trigger when matched.
- Actions PushActionArray `json:"actions"`
- // Whether this is a default rule, or has been set explicitly.
- Default bool `json:"default"`
- // Whether or not this push rule is enabled.
- Enabled bool `json:"enabled"`
- // The conditions to match in order to trigger this rule.
- // Only applicable to generic underride/override rules.
- Conditions []*PushCondition `json:"conditions,omitempty"`
- // Pattern for content-specific push rules
- Pattern string `json:"pattern,omitempty"`
-}
-
-func (rule *PushRule) Match(room Room, event *gomatrix.Event) bool <span class="cov0" title="0">{
- if !rule.Enabled </span><span class="cov0" title="0">{
- return false
- }</span>
- <span class="cov0" title="0">switch rule.Type </span>{
- case OverrideRule, UnderrideRule:<span class="cov0" title="0">
- return rule.matchConditions(room, event)</span>
- case ContentRule:<span class="cov0" title="0">
- return rule.matchPattern(room, event)</span>
- case RoomRule:<span class="cov0" title="0">
- return rule.RuleID == event.RoomID</span>
- case SenderRule:<span class="cov0" title="0">
- return rule.RuleID == event.Sender</span>
- default:<span class="cov0" title="0">
- return false</span>
- }
-}
-
-func (rule *PushRule) matchConditions(room Room, event *gomatrix.Event) bool <span class="cov0" title="0">{
- for _, cond := range rule.Conditions </span><span class="cov0" title="0">{
- if !cond.Match(room, event) </span><span class="cov0" title="0">{
- return false
- }</span>
- }
- <span class="cov0" title="0">return true</span>
-}
-
-func (rule *PushRule) matchPattern(room Room, event *gomatrix.Event) bool <span class="cov0" title="0">{
- pattern, err := glob.Compile(rule.Pattern)
- if err != nil </span><span class="cov0" title="0">{
- return false
- }</span>
- <span class="cov0" title="0">text, _ := event.Content["body"].(string)
- return pattern.MatchString(text)</span>
-}
-</pre>
-
- <pre class="file" id="file4" style="display: none">// gomuks - A terminal Matrix client written in Go.
-// Copyright (C) 2018 Tulir Asokan
-//
-// 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 3 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.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
-
-package pushrules
-
-import (
- "encoding/json"
-
- "maunium.net/go/gomatrix"
-)
-
-type PushRuleset struct {
- Override PushRuleArray
- Content PushRuleArray
- Room PushRuleMap
- Sender PushRuleMap
- Underride PushRuleArray
-}
-
-type rawPushRuleset struct {
- Override PushRuleArray `json:"override"`
- Content PushRuleArray `json:"content"`
- Room PushRuleArray `json:"room"`
- Sender PushRuleArray `json:"sender"`
- Underride PushRuleArray `json:"underride"`
-}
-
-// UnmarshalJSON parses JSON into this PushRuleset.
-//
-// For override, sender and underride push rule arrays, the type is added
-// to each PushRule and the array is used as-is.
-//
-// For room and sender push rule arrays, the type is added to each PushRule
-// and the array is converted to a map with the rule ID as the key and the
-// PushRule as the value.
-func (rs *PushRuleset) UnmarshalJSON(raw []byte) (err error) <span class="cov8" title="1">{
- data := rawPushRuleset{}
- err = json.Unmarshal(raw, &amp;data)
- if err != nil </span><span class="cov0" title="0">{
- return
- }</span>
-
- <span class="cov8" title="1">rs.Override = data.Override.setType(OverrideRule)
- rs.Content = data.Content.setType(ContentRule)
- rs.Room = data.Room.setTypeAndMap(RoomRule)
- rs.Sender = data.Sender.setTypeAndMap(SenderRule)
- rs.Underride = data.Underride.setType(UnderrideRule)
- return</span>
-}
-
-// MarshalJSON is the reverse of UnmarshalJSON()
-func (rs *PushRuleset) MarshalJSON() ([]byte, error) <span class="cov0" title="0">{
- data := rawPushRuleset{
- Override: rs.Override,
- Content: rs.Content,
- Room: rs.Room.unmap(),
- Sender: rs.Sender.unmap(),
- Underride: rs.Underride,
- }
- return json.Marshal(&amp;data)
-}</span>
-
-// DefaultPushActions is the value returned if none of the rule
-// collections in a Ruleset match the event given to GetActions()
-var DefaultPushActions = make(PushActionArray, 0)
-
-// GetActions matches the given event against all of the push rule
-// collections in this push ruleset in the order of priority as
-// specified in spec section 11.12.1.4.
-func (rs *PushRuleset) GetActions(room Room, event *gomatrix.Event) (match PushActionArray) <span class="cov0" title="0">{
- // Add push rule collections to array in priority order
- arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
- // Loop until one of the push rule collections matches the room/event combo.
- for _, pra := range arrays </span><span class="cov0" title="0">{
- if match = pra.GetActions(room, event); match != nil </span><span class="cov0" title="0">{
- // Match found, return it.
- return
- }</span>
- }
- // No match found, return default actions.
- <span class="cov0" title="0">return DefaultPushActions</span>
-}
-</pre>
-
- </div>
- </body>
- <script>
- (function() {
- var files = document.getElementById('files');
- var visible;
- files.addEventListener('change', onChange, false);
- function select(part) {
- if (visible)
- visible.style.display = 'none';
- visible = document.getElementById(part);
- if (!visible)
- return;
- files.value = part;
- visible.style.display = 'block';
- location.hash = part;
- }
- function onChange() {
- select(files.value);
- window.scrollTo(0, 0);
- }
- if (location.hash != "") {
- select(location.hash.substr(1));
- }
- if (!visible) {
- select("file0");
- }
- })();
- </script>
-</html>