├── LICENSE ├── README.md ├── builds ├── build.go └── patch.go ├── cmd └── rbxapiref │ ├── data.go │ ├── main.go │ ├── page.go │ ├── search.go │ ├── templates.go │ └── typeids.go ├── documents ├── README.md ├── directory.go ├── doc.go ├── document.go ├── git.go └── markdown.go ├── entities └── entities.go ├── fetch └── config.go ├── go.mod ├── go.sum ├── internal └── binio │ ├── bits.go │ ├── reader.go │ └── writer.go ├── manifest └── manifest.go ├── resources ├── about.css ├── actions.js ├── ana.svg ├── class.css ├── class.js ├── doc.css ├── docmon.css ├── docmon.js ├── enum.css ├── enum.js ├── favicons │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-48x48.png │ ├── favicon-512x512.png │ └── favicon.ico ├── icon-devhub.png ├── icon-objectbrowser.png ├── index.css ├── index.js ├── license-badge.png ├── main.css ├── main.js ├── quick-theme.js ├── search.js ├── settings.js ├── settings.svg ├── theme-dark.css ├── theme-light.css ├── type.css ├── type.js ├── updates.css └── updates.js ├── settings ├── const.go ├── output.go └── settings.go └── templates ├── about.gohtml ├── class.gohtml ├── devhub-link.gohtml ├── docmon.gohtml ├── enum.gohtml ├── enumitem-index-table.gohtml ├── enumitem-section.gohtml ├── history.gohtml ├── index.gohtml ├── main.gohtml ├── member-index-table.gohtml ├── member-section.gohtml ├── metadata.gohtml ├── outline.gohtml ├── param-table.gohtml ├── parameters.gohtml ├── referrers.gohtml ├── resource.gohtml ├── status-box.gohtml ├── status-boxes.gohtml ├── type.gohtml ├── update-action.gohtml ├── updates.gohtml └── value.gohtml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anaminus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rbxapiref 2 | The rbxapiref command generates API reference pages for the Roblox Lua API. 3 | 4 | ## Subpackages 5 | 6 | ## fetch 7 | [![GoDoc](https://godoc.org/github.com/RobloxAPI/rbxapiref/fetch?status.png)](https://godoc.org/github.com/RobloxAPI/rbxapiref/fetch) 8 | 9 | The fetch package retrieves information about Roblox builds. 10 | 11 | ## documents 12 | [![GoDoc](https://godoc.org/github.com/RobloxAPI/rbxapiref/documents?status.png)](https://godoc.org/github.com/RobloxAPI/rbxapiref/documents) 13 | 14 | The documents package queries resources for document fragments that can be 15 | inlined elsewhere. -------------------------------------------------------------------------------- /builds/build.go: -------------------------------------------------------------------------------- 1 | package builds 2 | 3 | import ( 4 | "fmt" 5 | "github.com/anaminus/but" 6 | "github.com/robloxapi/rbxapi/rbxapijson" 7 | "github.com/robloxapi/rbxapiref/fetch" 8 | "sort" 9 | "time" 10 | ) 11 | 12 | type Build struct { 13 | Config string 14 | Info Info 15 | API *rbxapijson.Root 16 | } 17 | 18 | type Info struct { 19 | Hash string 20 | Date time.Time 21 | Version fetch.Version 22 | } 23 | 24 | func (a Info) Equal(b Info) bool { 25 | if a.Hash != b.Hash { 26 | return false 27 | } 28 | if a.Version != b.Version { 29 | return false 30 | } 31 | if !a.Date.Equal(b.Date) { 32 | return false 33 | } 34 | return true 35 | } 36 | 37 | func (m Info) String() string { 38 | return fmt.Sprintf("%s; %s; %s", m.Hash, m.Date, m.Version) 39 | } 40 | 41 | type Settings struct { 42 | // Configs maps an identifying name to a fetch configuration. 43 | Configs map[string]fetch.Config 44 | // UseConfigs specifies the logical concatenation of the fetch configs 45 | // defined in the Configs setting. Builds from these configs are read 46 | // sequentially. 47 | UseConfigs []string 48 | // DisableRewind sets whether rewinding is enabled. If true, builds that are 49 | // not yet live will be included. 50 | DisableRewind bool 51 | } 52 | 53 | func (settings Settings) Fetch() (builds []Build, err error) { 54 | client := &fetch.Client{CacheMode: fetch.CacheNone} 55 | for _, cfg := range settings.UseConfigs { 56 | client.Config = settings.Configs[cfg] 57 | bs, err := client.Builds() 58 | if err != nil { 59 | return nil, fmt.Errorf("fetch build: %w", err) 60 | } 61 | for _, b := range bs { 62 | builds = append(builds, Build{Config: cfg, Info: Info(b)}) 63 | } 64 | } 65 | 66 | // Collapse adjacent builds of equal versions. 67 | b := builds[:0] 68 | for _, build := range builds { 69 | if len(b) == 0 || build.Info.Version != b[len(b)-1].Info.Version { 70 | b = append(b, build) 71 | } 72 | } 73 | for i := len(b); i < len(builds); i++ { 74 | builds[i] = Build{} 75 | } 76 | builds = b 77 | 78 | if !settings.DisableRewind { 79 | // Rewind to current live build. 80 | if lives, err := client.Live(); err != nil { 81 | but.Logf("fetch live builds: %v\n", err) 82 | } else { 83 | max := -1 84 | for _, live := range lives { 85 | for i := len(builds) - 1; i > max; i-- { 86 | if builds[i].Info.Hash == live.Hash { 87 | max = i 88 | break 89 | } 90 | } 91 | } 92 | if max >= 0 { 93 | for i := len(builds) - 1; i > max; i-- { 94 | but.Log("REWIND", builds[i].Info.Hash) 95 | } 96 | builds = builds[:max+1] 97 | } 98 | } 99 | } 100 | 101 | sort.Slice(builds, func(i, j int) bool { 102 | return builds[i].Info.Date.Before(builds[j].Info.Date) 103 | }) 104 | return builds, nil 105 | } 106 | 107 | func (settings Settings) Merge(cached []Patch, builds []Build) (patches []Patch, err error) { 108 | client := &fetch.Client{CacheMode: fetch.CacheTemp} 109 | var latest *Build 110 | loop: 111 | for _, build := range builds { 112 | for _, patch := range cached { 113 | if !build.Info.Equal(patch.Info) { 114 | // Not relevant; skip. 115 | continue 116 | } 117 | // Current build has a cached version. 118 | if latest == nil { 119 | if patch.Prev != nil { 120 | // Cached build is now the first, but was not originally; 121 | // actions are stale. 122 | but.Log("STALE", patch.Info) 123 | break 124 | } 125 | } else { 126 | if patch.Prev == nil { 127 | // Cached build was not originally the first, but now is; 128 | // actions are stale. 129 | but.Log("STALE", patch.Info) 130 | break 131 | } 132 | if !latest.Info.Equal(*patch.Prev) { 133 | // Latest build does not match previous build; actions are 134 | // stale. 135 | but.Log("STALE", patch.Info) 136 | break 137 | } 138 | } 139 | // Cached actions are still fresh; set them directly. 140 | patches = append(patches, patch) 141 | latest = &Build{Info: patch.Info, Config: patch.Config} 142 | continue loop 143 | } 144 | but.Log("NEW", build.Info) 145 | client.Config = settings.Configs[build.Config] 146 | root, err := client.APIDump(build.Info.Hash) 147 | if but.IfErrorf(err, "%s: fetch build %s", build.Config, build.Info.Hash) { 148 | continue 149 | } 150 | build.API = root 151 | var actions []Action 152 | if latest == nil { 153 | // First build; compare with nothing. 154 | actions = WrapActions((&rbxapijson.Diff{Prev: nil, Next: build.API}).Diff()) 155 | } else { 156 | if latest.API == nil { 157 | // Previous build was cached; fetch its data to compare with 158 | // current build. 159 | client.Config = settings.Configs[latest.Config] 160 | root, err := client.APIDump(latest.Info.Hash) 161 | if but.IfErrorf(err, "%s: fetch build %s", latest.Config, latest.Info.Hash) { 162 | continue 163 | } 164 | latest.API = root 165 | } 166 | actions = WrapActions((&rbxapijson.Diff{Prev: latest.API, Next: build.API}).Diff()) 167 | } 168 | patch := Patch{Stale: true, Info: build.Info, Config: build.Config, Actions: actions} 169 | if latest != nil { 170 | prev := latest.Info 171 | patch.Prev = &prev 172 | } 173 | patches = append(patches, patch) 174 | b := build 175 | latest = &b 176 | } 177 | 178 | // Set action indices. 179 | for i, patch := range patches { 180 | for j := range patch.Actions { 181 | patches[i].Actions[j].Index = j 182 | } 183 | } 184 | 185 | return patches, nil 186 | } 187 | -------------------------------------------------------------------------------- /builds/patch.go: -------------------------------------------------------------------------------- 1 | package builds 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/robloxapi/rbxapi" 6 | "github.com/robloxapi/rbxapi/patch" 7 | "github.com/robloxapi/rbxapi/rbxapijson" 8 | "reflect" 9 | ) 10 | 11 | type Patch struct { 12 | Stale bool `json:"-"` 13 | Prev *Info `json:",omitempty"` 14 | Info Info 15 | Config string 16 | Actions []Action 17 | } 18 | 19 | func MergePatches(left, right []Patch, filter func(*Action) bool) []Patch { 20 | var patches []Patch 21 | for _, l := range left { 22 | patch := Patch{ 23 | Info: l.Info, 24 | Actions: make([]Action, len(l.Actions)), 25 | } 26 | copy(patch.Actions, l.Actions) 27 | patches = append(patches, patch) 28 | } 29 | loop: 30 | for _, r := range right { 31 | for p, patch := range patches { 32 | if patch.Info.Equal(r.Info) { 33 | if filter == nil { 34 | patches[p].Actions = append(patches[p].Actions, r.Actions...) 35 | } else { 36 | for _, action := range r.Actions { 37 | if filter(&action) { 38 | patches[p].Actions = append(patches[p].Actions, action) 39 | } 40 | } 41 | } 42 | continue loop 43 | } 44 | } 45 | patch := Patch{ 46 | Info: r.Info, 47 | Actions: make([]Action, len(r.Actions)), 48 | } 49 | if filter == nil { 50 | copy(patch.Actions, r.Actions) 51 | } else { 52 | patch.Actions = patch.Actions[:0] 53 | for _, action := range r.Actions { 54 | if filter(&action) { 55 | patch.Actions = append(patch.Actions, action) 56 | } 57 | } 58 | } 59 | patches = append(patches, patch) 60 | } 61 | return patches 62 | } 63 | 64 | var patchTypeStrings = [3]map[string]string{ 65 | patch.Remove + 1: { 66 | "ed": "Removed", 67 | "ing": "Removing", 68 | "s": "Removes", 69 | "n": "Removal", 70 | "ns": "Removals", 71 | }, 72 | patch.Change + 1: { 73 | "ed": "Changed", 74 | "ing": "Changing", 75 | "s": "Changes", 76 | "n": "Change", 77 | "ns": "Changes", 78 | }, 79 | patch.Add + 1: { 80 | "ed": "Added", 81 | "ing": "Adding", 82 | "s": "Adds", 83 | "n": "Addition", 84 | "ns": "Additions", 85 | }, 86 | } 87 | 88 | func PatchTypeString(typ patch.Type, mode string) string { 89 | if s := patchTypeStrings[typ+1][mode]; s != "" { 90 | return s 91 | } 92 | return typ.String() 93 | } 94 | 95 | type Action struct { 96 | Type patch.Type 97 | Index int `json:"-"` 98 | Class *rbxapijson.Class `json:",omitempty"` 99 | Property *rbxapijson.Property `json:",omitempty"` 100 | Function *rbxapijson.Function `json:",omitempty"` 101 | Event *rbxapijson.Event `json:",omitempty"` 102 | Callback *rbxapijson.Callback `json:",omitempty"` 103 | Enum *rbxapijson.Enum `json:",omitempty"` 104 | EnumItem *rbxapijson.EnumItem `json:",omitempty"` 105 | Field string `json:",omitempty"` 106 | Prev *Value `json:",omitempty"` 107 | Next *Value `json:",omitempty"` 108 | } 109 | 110 | func WrapActions(actions []patch.Action) []Action { 111 | c := make([]Action, len(actions)) 112 | for i, action := range actions { 113 | c[i] = Action{ 114 | Type: action.GetType(), 115 | Field: action.GetField(), 116 | } 117 | if p := action.GetPrev(); p != nil { 118 | c[i].Prev = WrapValue(p) 119 | } 120 | if n := action.GetNext(); n != nil { 121 | c[i].Next = WrapValue(n) 122 | } 123 | switch action := action.(type) { 124 | case patch.Member: 125 | class := action.GetClass().(*rbxapijson.Class) 126 | members := class.Members 127 | class.Members = nil 128 | c[i].Class = class.Copy().(*rbxapijson.Class) 129 | class.Members = members 130 | 131 | c[i].SetMember(action.GetMember().Copy()) 132 | case patch.Class: 133 | if action.GetType() == patch.Change { 134 | class := action.GetClass().(*rbxapijson.Class) 135 | members := class.Members 136 | class.Members = nil 137 | c[i].Class = class.Copy().(*rbxapijson.Class) 138 | class.Members = members 139 | } else { 140 | c[i].Class = action.GetClass().Copy().(*rbxapijson.Class) 141 | } 142 | case patch.EnumItem: 143 | enum := action.GetEnum().(*rbxapijson.Enum) 144 | items := enum.Items 145 | enum.Items = nil 146 | c[i].Enum = enum.Copy().(*rbxapijson.Enum) 147 | enum.Items = items 148 | 149 | c[i].EnumItem = action.GetEnumItem().Copy().(*rbxapijson.EnumItem) 150 | case patch.Enum: 151 | if action.GetType() == patch.Change { 152 | enum := action.GetEnum().(*rbxapijson.Enum) 153 | items := enum.Items 154 | enum.Items = nil 155 | c[i].Enum = enum.Copy().(*rbxapijson.Enum) 156 | enum.Items = items 157 | 158 | } else { 159 | c[i].Enum = action.GetEnum().Copy().(*rbxapijson.Enum) 160 | } 161 | } 162 | } 163 | return c 164 | } 165 | func (a *Action) GetClass() rbxapi.Class { 166 | if a.Class == nil { 167 | return nil 168 | } 169 | return a.Class 170 | } 171 | func (a *Action) GetMember() rbxapi.Member { 172 | switch { 173 | case a.Property != nil: 174 | return a.Property 175 | case a.Function != nil: 176 | return a.Function 177 | case a.Event != nil: 178 | return a.Event 179 | case a.Callback != nil: 180 | return a.Callback 181 | } 182 | return nil 183 | } 184 | func (a *Action) SetMember(member rbxapi.Member) { 185 | switch member := member.(type) { 186 | case *rbxapijson.Property: 187 | a.Property = member 188 | a.Function = nil 189 | a.Event = nil 190 | a.Callback = nil 191 | case *rbxapijson.Function: 192 | a.Property = nil 193 | a.Function = member 194 | a.Event = nil 195 | a.Callback = nil 196 | case *rbxapijson.Event: 197 | a.Property = nil 198 | a.Function = nil 199 | a.Event = member 200 | a.Callback = nil 201 | case *rbxapijson.Callback: 202 | a.Property = nil 203 | a.Function = nil 204 | a.Event = nil 205 | a.Callback = member 206 | } 207 | } 208 | func (a *Action) GetEnum() rbxapi.Enum { 209 | if a.Enum == nil { 210 | return nil 211 | } 212 | return a.Enum 213 | } 214 | func (a *Action) GetEnumItem() rbxapi.EnumItem { 215 | if a.EnumItem == nil { 216 | return nil 217 | } 218 | return a.EnumItem 219 | } 220 | func (a *Action) GetType() patch.Type { return a.Type } 221 | func (a *Action) GetField() string { return a.Field } 222 | func (a *Action) GetPrev() interface{} { 223 | if a.Prev != nil { 224 | return a.Prev.V 225 | } 226 | return nil 227 | } 228 | func (a *Action) GetNext() interface{} { 229 | if a.Next != nil { 230 | return a.Next.V 231 | } 232 | return nil 233 | } 234 | func (a *Action) String() string { return "Action" } 235 | func (a *Action) GetElementType() string { 236 | switch { 237 | case a.Class != nil && a.GetMember() != nil: 238 | return a.GetMember().GetMemberType() 239 | case a.Class != nil: 240 | return "Class" 241 | case a.Enum != nil && a.EnumItem != nil: 242 | return "EnumItem" 243 | case a.Enum != nil: 244 | return "Enum" 245 | } 246 | return "" 247 | } 248 | func (a *Action) GetElement() interface{} { 249 | switch { 250 | case a.Class != nil && a.GetMember() != nil: 251 | return a.GetMember() 252 | case a.Class != nil: 253 | return a.Class 254 | case a.Enum != nil && a.EnumItem != nil: 255 | return a.EnumItem 256 | case a.Enum != nil: 257 | return a.Enum 258 | } 259 | return "" 260 | } 261 | 262 | type Value struct { 263 | V interface{} 264 | } 265 | 266 | func WrapValue(v interface{}) *Value { 267 | w := Value{} 268 | switch v := v.(type) { 269 | case rbxapijson.Type, rbxapijson.Parameters: 270 | w.V = v 271 | case rbxapi.Type: 272 | w.V = rbxapijson.Type{ 273 | Category: v.GetCategory(), 274 | Name: v.GetName(), 275 | } 276 | case rbxapi.Parameters: 277 | n := v.GetLength() 278 | params := make([]rbxapijson.Parameter, n) 279 | for i := 0; i < n; i++ { 280 | p := v.GetParameter(i) 281 | params[i] = rbxapijson.Parameter{ 282 | Type: rbxapijson.Type{ 283 | Category: p.GetType().GetCategory(), 284 | Name: p.GetType().GetName(), 285 | }, 286 | Name: p.GetName(), 287 | } 288 | params[i].Default, params[i].HasDefault = p.GetDefault() 289 | } 290 | w.V = rbxapijson.Parameters{List: ¶ms} 291 | default: 292 | w.V = v 293 | } 294 | return &w 295 | } 296 | 297 | func (v *Value) MarshalJSON() (b []byte, err error) { 298 | var w struct { 299 | Type string 300 | Value interface{} 301 | } 302 | switch v := v.V.(type) { 303 | case bool: 304 | w.Type = "bool" 305 | w.Value = v 306 | case int: 307 | w.Type = "int" 308 | w.Value = v 309 | case string: 310 | w.Type = "string" 311 | w.Value = v 312 | case rbxapijson.Type: 313 | w.Type = "Type" 314 | w.Value = v 315 | case []string: 316 | w.Type = "strings" 317 | w.Value = v 318 | case rbxapijson.Parameters: 319 | w.Type = "Parameters" 320 | w.Value = v 321 | default: 322 | panic("unknown action value type " + reflect.TypeOf(v).String()) 323 | } 324 | return json.Marshal(&w) 325 | } 326 | 327 | func (v *Value) UnmarshalJSON(b []byte) (err error) { 328 | var w struct{ Type string } 329 | if err = json.Unmarshal(b, &w); err != nil { 330 | return err 331 | } 332 | switch w.Type { 333 | case "bool": 334 | var value struct{ Value bool } 335 | if err = json.Unmarshal(b, &value); err != nil { 336 | return err 337 | } 338 | v.V = value.Value 339 | case "int": 340 | var value struct{ Value int } 341 | if err = json.Unmarshal(b, &value); err != nil { 342 | return err 343 | } 344 | v.V = value.Value 345 | case "string": 346 | var value struct{ Value string } 347 | if err = json.Unmarshal(b, &value); err != nil { 348 | return err 349 | } 350 | v.V = value.Value 351 | case "Type": 352 | var value struct{ Value rbxapijson.Type } 353 | if err = json.Unmarshal(b, &value); err != nil { 354 | return err 355 | } 356 | v.V = value.Value 357 | case "strings": 358 | var value struct{ Value []string } 359 | if err = json.Unmarshal(b, &value); err != nil { 360 | return err 361 | } 362 | v.V = value.Value 363 | case "Parameters": 364 | var value struct{ Value rbxapijson.Parameters } 365 | if err = json.Unmarshal(b, &value); err != nil { 366 | return err 367 | } 368 | v.V = value.Value 369 | } 370 | return nil 371 | } 372 | 373 | // Generates a list of actions for each member of the element. 374 | func MakeSubactions(action Action) []Action { 375 | if class := action.Class; class != nil { 376 | actions := make([]Action, len(class.Members)) 377 | for i, member := range class.Members { 378 | actions[i] = Action{ 379 | Type: action.GetType(), 380 | Class: class, 381 | } 382 | actions[i].SetMember(member) 383 | } 384 | return actions 385 | } else if enum := action.Enum; enum != nil { 386 | actions := make([]Action, len(enum.Items)) 387 | for i, item := range enum.Items { 388 | actions[i] = Action{ 389 | Type: action.GetType(), 390 | Enum: enum, 391 | EnumItem: item, 392 | } 393 | } 394 | return actions 395 | } 396 | return nil 397 | } 398 | -------------------------------------------------------------------------------- /cmd/rbxapiref/search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/robloxapi/rbxapi" 7 | "github.com/robloxapi/rbxapi/rbxapijson" 8 | "github.com/robloxapi/rbxapiref/entities" 9 | "github.com/robloxapi/rbxapiref/internal/binio" 10 | ) 11 | 12 | /* 13 | // Search Database Format 14 | 15 | main struct { 16 | // Database version. 17 | Version uint:8 = 1 18 | // Number of icons. 19 | IconCount uint:16 20 | // Starting index of items that are classes. Subtracted from item index to 21 | // retrieve icon index. 22 | ClassOffset uint:16 23 | // Total number of items. 24 | ItemCount uint:16 25 | // List of ExplorerImageIndex for each class. Index corresponds to 26 | // Items[index - ClassOffset]. 27 | Icons [.IconCount]uint:8 28 | // List of items. 29 | Items [.ItemCount]Item 30 | // List of item strings. Index corresponds to index of Items. 31 | Strings [.ItemCount]String 32 | } 33 | 34 | String struct { 35 | Size uint:8 36 | Value [.Size]uint:8 37 | } 38 | 39 | ItemType enum uint:3 { 40 | Class 41 | Enum 42 | EnumItem 43 | Type 44 | Property 45 | Function 46 | Event 47 | Callback 48 | } 49 | 50 | Security enum uint:3 { 51 | None 52 | RobloxPlaceSecurity 53 | PluginSecurity 54 | LocalUserSecurity 55 | RobloxScriptSecurity 56 | RobloxSecurity 57 | NotAccessibleSecurity 58 | } 59 | 60 | Item struct { 61 | Type ItemType 62 | Removed bool:1 63 | Deprecated bool:1 64 | Unbrowsable bool:1 65 | if .Type == Class { 66 | Uncreatable bool:1 67 | } 68 | if .Type == Property { 69 | Hidden bool:1 70 | @8 71 | ReadSecurity Security 72 | WriteSecurity Security 73 | } 74 | if .Type > Property { 75 | @8 76 | Security Security 77 | } 78 | @16 79 | } 80 | 81 | */ 82 | 83 | func writeDatabaseSecurity(data uint64, i int, security string) uint64 { 84 | var sec int 85 | switch security { 86 | case "RobloxPlaceSecurity": 87 | sec = 1 88 | case "PluginSecurity": 89 | sec = 2 90 | case "LocalUserSecurity": 91 | sec = 3 92 | case "RobloxScriptSecurity": 93 | sec = 4 94 | case "RobloxSecurity": 95 | sec = 5 96 | case "NotAccessibleSecurity": 97 | sec = 6 98 | } 99 | return binio.SetBits(data, i, i+3, sec) 100 | } 101 | 102 | func writeDatabaseItem(v interface{}, removed bool) uint16 { 103 | var data uint64 104 | 105 | var typ int 106 | switch v.(type) { 107 | case *rbxapijson.Class: 108 | typ = 0 109 | case *rbxapijson.Enum: 110 | typ = 1 111 | case *rbxapijson.EnumItem: 112 | typ = 2 113 | case rbxapijson.Type: 114 | typ = 3 115 | case *rbxapijson.Property: 116 | typ = 4 117 | case *rbxapijson.Function: 118 | typ = 5 119 | case *rbxapijson.Event: 120 | typ = 6 121 | case *rbxapijson.Callback: 122 | typ = 7 123 | } 124 | data = binio.SetBits(data, 0, 3, typ) 125 | data = binio.SetBit(data, 3, removed) 126 | 127 | if v, ok := v.(rbxapi.Taggable); ok { 128 | data = binio.SetBit(data, 4, v.GetTag("Deprecated")) 129 | data = binio.SetBit(data, 5, v.GetTag("NotBrowsable")) 130 | switch typ { 131 | case 0: // Class 132 | data = binio.SetBit(data, 6, v.GetTag("NotCreatable")) 133 | case 4: // Property 134 | data = binio.SetBit(data, 6, v.GetTag("Hidden")) 135 | } 136 | } 137 | 138 | if v, ok := v.(interface{ GetSecurity() string }); ok { 139 | data = writeDatabaseSecurity(data, 8, v.GetSecurity()) 140 | } else if v, ok := v.(interface{ GetSecurity() (string, string) }); ok { 141 | r, w := v.GetSecurity() 142 | data = writeDatabaseSecurity(data, 8, r) 143 | data = writeDatabaseSecurity(data, 11, w) 144 | } 145 | 146 | return uint16(data) 147 | } 148 | 149 | func GenerateDatabase(w io.Writer, ent *entities.Entities) error { 150 | bw := binio.NewWriter(w) 151 | 152 | // Version 153 | if !bw.Number(uint8(1)) { 154 | return bw.Err 155 | } 156 | 157 | // IconCount 158 | if !bw.Number(uint16(len(ent.ClassList))) { 159 | return bw.Err 160 | } 161 | 162 | items := 0 163 | items += len(ent.TypeList) 164 | // ClassOffset 165 | if !bw.Number(uint16(items)) { 166 | return bw.Err 167 | } 168 | items += len(ent.ClassList) 169 | items += len(ent.EnumList) 170 | for _, class := range ent.ClassList { 171 | items += len(class.MemberList) 172 | } 173 | for _, enum := range ent.EnumList { 174 | items += len(enum.ItemList) 175 | } 176 | // ItemCount 177 | if !bw.Number(uint16(items)) { 178 | return bw.Err 179 | } 180 | 181 | // Icons 182 | for _, class := range ent.ClassList { 183 | var icon int 184 | if class.Metadata.Instance != nil { 185 | icon = class.Metadata.GetInt("ExplorerImageIndex") 186 | } 187 | if !bw.Number(uint8(icon)) { 188 | return bw.Err 189 | } 190 | } 191 | 192 | // Items 193 | for _, typ := range ent.TypeList { 194 | if !bw.Number(writeDatabaseItem(typ.Element, typ.Removed)) { 195 | return bw.Err 196 | } 197 | } 198 | for _, class := range ent.ClassList { 199 | if !bw.Number(writeDatabaseItem(class.Element, class.Removed)) { 200 | return bw.Err 201 | } 202 | } 203 | for _, enum := range ent.EnumList { 204 | if !bw.Number(writeDatabaseItem(enum.Element, enum.Removed)) { 205 | return bw.Err 206 | } 207 | } 208 | for _, class := range ent.ClassList { 209 | for _, member := range class.MemberList { 210 | if !bw.Number(writeDatabaseItem(member.Element, member.Removed)) { 211 | return bw.Err 212 | } 213 | } 214 | } 215 | for _, enum := range ent.EnumList { 216 | for _, item := range enum.ItemList { 217 | if !bw.Number(writeDatabaseItem(item.Element, item.Removed)) { 218 | return bw.Err 219 | } 220 | } 221 | } 222 | 223 | // Strings 224 | for _, typ := range ent.TypeList { 225 | if !bw.String(typ.ID) { 226 | return bw.Err 227 | } 228 | } 229 | for _, class := range ent.ClassList { 230 | if !bw.String(class.ID) { 231 | return bw.Err 232 | } 233 | } 234 | for _, enum := range ent.EnumList { 235 | if !bw.String(enum.ID) { 236 | return bw.Err 237 | } 238 | } 239 | for _, class := range ent.ClassList { 240 | for _, member := range class.MemberList { 241 | if !bw.String(member.ID[0] + "." + member.ID[1]) { 242 | return bw.Err 243 | } 244 | } 245 | } 246 | for _, enum := range ent.EnumList { 247 | for _, item := range enum.ItemList { 248 | if !bw.String(item.ID[0] + "." + item.ID[1]) { 249 | return bw.Err 250 | } 251 | } 252 | } 253 | 254 | return nil 255 | } 256 | -------------------------------------------------------------------------------- /cmd/rbxapiref/typeids.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | "unicode/utf8" 7 | 8 | "github.com/robloxapi/rbxapiref/documents" 9 | "github.com/robloxapi/rbxapiref/entities" 10 | ) 11 | 12 | type typeDecParser struct { 13 | i int 14 | s string 15 | } 16 | 17 | func (p *typeDecParser) eof() bool { 18 | return p.i >= len(p.s) 19 | } 20 | 21 | func (p *typeDecParser) find(f func(rune) bool) string { 22 | if p.eof() { 23 | return "" 24 | } 25 | j := p.i 26 | for p.i < len(p.s) { 27 | r, w := utf8.DecodeRuneInString(p.s[p.i:]) 28 | if !f(r) { 29 | return p.s[j:p.i] 30 | } 31 | p.i += w 32 | } 33 | return p.s[j:] 34 | } 35 | 36 | func (p *typeDecParser) match(r rune) bool { 37 | if p.eof() { 38 | return false 39 | } 40 | if c, w := utf8.DecodeRuneInString(p.s[p.i:]); c == r { 41 | p.i += w 42 | return true 43 | } 44 | return false 45 | } 46 | 47 | func (p *typeDecParser) string() string { 48 | return p.s[p.i:] 49 | } 50 | 51 | func (p *typeDecParser) parseType(typ *typeDecType) bool { 52 | var t typeDecType 53 | if p.match('.') { 54 | // Variadic. 55 | if !p.match('.') || !p.match('.') { 56 | return false 57 | } 58 | t.Variadic = true 59 | } 60 | if t.Name = p.find(isVar); t.Name == "" { 61 | return false 62 | } 63 | if p.match('?') { 64 | t.Optional = true 65 | } 66 | *typ = t 67 | return true 68 | } 69 | 70 | func (p *typeDecParser) parseParams(params *[]typeDecField) bool { 71 | // '(' already parsed 72 | for { 73 | p.find(unicode.IsSpace) 74 | name := p.find(isVar) 75 | if name == "" { 76 | if !p.match(')') { 77 | return false 78 | } 79 | break 80 | } 81 | p.find(unicode.IsSpace) 82 | if !p.match(':') { 83 | return false 84 | } 85 | p.find(unicode.IsSpace) 86 | field := typeDecField{Name: name} 87 | if !p.parseType(&field.Returns) { 88 | return false 89 | } 90 | *params = append(*params, field) 91 | p.find(unicode.IsSpace) 92 | if p.match(',') { 93 | continue 94 | } 95 | if p.match(')') { 96 | break 97 | } 98 | return false 99 | } 100 | return true 101 | } 102 | 103 | type typeDecType struct { 104 | Name string 105 | Variadic bool 106 | Optional bool 107 | } 108 | 109 | type typeDecField struct { 110 | Name string 111 | Returns typeDecType 112 | } 113 | 114 | func isVar(r rune) bool { 115 | return unicode.IsLetter(r) || unicode.IsDigit(r) 116 | } 117 | 118 | func parseTypeDecField(s string) *typeDecField { 119 | var t typeDecField 120 | p := typeDecParser{s: s} 121 | if t.Name = p.find(isVar); t.Name == "" { 122 | return nil 123 | } 124 | p.find(unicode.IsSpace) 125 | if !p.match(':') { 126 | return nil 127 | } 128 | p.find(unicode.IsSpace) 129 | if !p.parseType(&t.Returns) { 130 | return nil 131 | } 132 | if !p.eof() { 133 | return nil 134 | } 135 | return &t 136 | } 137 | 138 | func (t typeDecField) ID() string { 139 | return "field-" + t.Name 140 | } 141 | 142 | type typeDecCtor struct { 143 | Struct string 144 | Name string 145 | Parameters []typeDecField 146 | Returns []typeDecField 147 | Short bool 148 | } 149 | 150 | func parseTypeDecCtor(s string) *typeDecCtor { 151 | var t typeDecCtor 152 | p := typeDecParser{s: s} 153 | if t.Struct = p.find(isVar); t.Struct == "" { 154 | return nil 155 | } 156 | if !p.match('.') { 157 | return nil 158 | } 159 | method := parseTypeDecMethod(p.string()) 160 | if method == nil { 161 | return nil 162 | } 163 | t.Name = method.Name 164 | t.Parameters = method.Parameters 165 | t.Returns = method.Returns 166 | return &t 167 | } 168 | 169 | func (t typeDecCtor) ID() string { 170 | if t.Short { 171 | return "ctor-" + t.Name 172 | } 173 | s := make([]string, len(t.Parameters)+2) 174 | s[0] = "ctor" 175 | s[1] = t.Name 176 | for i, p := range t.Parameters { 177 | s[i+2] = p.Name 178 | } 179 | return strings.Join(s, "-") 180 | } 181 | 182 | type typeDecMethod struct { 183 | Name string 184 | Parameters []typeDecField 185 | Returns []typeDecField 186 | } 187 | 188 | func parseTypeDecMethod(s string) *typeDecMethod { 189 | var t typeDecMethod 190 | p := typeDecParser{s: s} 191 | if t.Name = p.find(isVar); t.Name == "" { 192 | return nil 193 | } 194 | p.find(unicode.IsSpace) 195 | if !p.match('(') { 196 | return nil 197 | } 198 | if !p.parseParams(&t.Parameters) { 199 | return nil 200 | } 201 | p.find(unicode.IsSpace) 202 | if p.match(':') { 203 | p.find(unicode.IsSpace) 204 | if p.match('(') { 205 | if !p.parseParams(&t.Returns) { 206 | return nil 207 | } 208 | } else { 209 | var param typeDecField 210 | if !p.parseType(¶m.Returns) { 211 | return nil 212 | } 213 | t.Returns = []typeDecField{param} 214 | } 215 | } 216 | if !p.eof() { 217 | return nil 218 | } 219 | return &t 220 | } 221 | 222 | func (t typeDecMethod) ID() string { 223 | return "method-" + t.Name 224 | } 225 | 226 | type typeDecOperator struct { 227 | Op string 228 | Struct string 229 | Operand string 230 | Returns typeDecType 231 | Call *typeDecMethod 232 | } 233 | 234 | func parseTypeDecOperator(s string) *typeDecOperator { 235 | var t typeDecOperator 236 | p := typeDecParser{s: s} 237 | switch { 238 | case p.match('-'): 239 | t.Op = "unm" 240 | p.find(unicode.IsSpace) 241 | case p.match('#'): 242 | t.Op = "len" 243 | p.find(unicode.IsSpace) 244 | } 245 | if t.Struct = p.find(isVar); t.Struct == "" { 246 | return nil 247 | } 248 | p.find(unicode.IsSpace) 249 | if t.Op != "" { 250 | // Handle unary operators. 251 | if !p.match(':') { 252 | return nil 253 | } 254 | if !p.parseType(&t.Returns) { 255 | return nil 256 | } 257 | if !p.eof() { 258 | return nil 259 | } 260 | return &t 261 | } 262 | switch { 263 | case p.match('+'): 264 | t.Op = "add" 265 | case p.match('-'): 266 | t.Op = "sub" 267 | case p.match('*'): 268 | t.Op = "mul" 269 | case p.match('/'): 270 | t.Op = "div" 271 | case p.match('%'): 272 | t.Op = "mod" 273 | case p.match('^'): 274 | t.Op = "pow" 275 | case p.match('.'): 276 | if !p.match('.') { 277 | return nil 278 | } 279 | t.Op = "concat" 280 | case p.match('='): 281 | if !p.match('=') { 282 | return nil 283 | } 284 | t.Op = "eq" 285 | case p.match('<'): 286 | if p.match('=') { 287 | t.Op = "le" 288 | } else { 289 | t.Op = "lt" 290 | } 291 | case p.match('['): // index/newindex? 292 | case p.match('('): // call 293 | t.Op = "call" 294 | if t.Call = parseTypeDecMethod(s); t.Call == nil { 295 | return nil 296 | } 297 | return &t 298 | } 299 | p.find(unicode.IsSpace) 300 | if t.Operand = p.find(isVar); t.Operand == "" { 301 | return nil 302 | } 303 | p.find(unicode.IsSpace) 304 | if t.Op == "" && !p.match(']') { 305 | return nil 306 | } 307 | p.find(unicode.IsSpace) 308 | switch { 309 | case p.match('='): 310 | t.Op = "newindex" 311 | case p.match(':'): 312 | if t.Op == "" { 313 | t.Op = "index" 314 | } 315 | } 316 | p.find(unicode.IsSpace) 317 | if !p.parseType(&t.Returns) { 318 | return nil 319 | } 320 | if !p.eof() { 321 | return nil 322 | } 323 | return &t 324 | } 325 | 326 | func (t typeDecOperator) ID() string { 327 | switch t.Op { 328 | case "unm", "len", "index", "newindex", "call": 329 | return "op-" + t.Op 330 | } 331 | return "op-" + t.Op + "-" + t.Operand 332 | } 333 | 334 | // GenerateDocumentTypeIDs scans a document for sections that indicate the 335 | // documentation of a type entity, then ID heading IDs that follow a 336 | // standard format. 337 | func GenerateDocumentTypeIDs(document entities.Document) { 338 | if sec := document.Query("Constructors"); sec != nil { 339 | subs := sec.Subsections() 340 | ctors := make([]*typeDecCtor, len(subs)) 341 | count := map[string]int{} 342 | for i, sub := range subs { 343 | ctor := parseTypeDecCtor(strings.TrimSpace(sub.Name())) 344 | ctors[i] = ctor 345 | if ctor != nil { 346 | count[ctor.Name]++ 347 | } 348 | } 349 | for i, sub := range subs { 350 | if sub, ok := sub.(documents.Headingable); ok { 351 | if ctors[i] != nil { 352 | if count[ctors[i].Name] <= 1 { 353 | ctors[i].Short = true 354 | } 355 | sub.SetHeadingID(ctors[i].ID()) 356 | } 357 | } 358 | } 359 | } 360 | if sec := document.Query("Fields"); sec != nil { 361 | for _, sub := range sec.Subsections() { 362 | if sub, ok := sub.(documents.Headingable); ok { 363 | dec := parseTypeDecField(strings.TrimSpace(sub.Name())) 364 | if dec != nil { 365 | sub.SetHeadingID(dec.ID()) 366 | } 367 | } 368 | } 369 | } 370 | if sec := document.Query("Methods"); sec != nil { 371 | for _, sub := range sec.Subsections() { 372 | if sub, ok := sub.(documents.Headingable); ok { 373 | dec := parseTypeDecMethod(strings.TrimSpace(sub.Name())) 374 | if dec != nil { 375 | sub.SetHeadingID(dec.ID()) 376 | } 377 | } 378 | } 379 | } 380 | if sec := document.Query("Operators"); sec != nil { 381 | for _, sub := range sec.Subsections() { 382 | if sub, ok := sub.(documents.Headingable); ok { 383 | dec := parseTypeDecOperator(strings.TrimSpace(sub.Name())) 384 | if dec != nil { 385 | sub.SetHeadingID(dec.ID()) 386 | } 387 | } 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /documents/README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/RobloxAPI/rbxapiref/documents?status.png)](https://godoc.org/github.com/RobloxAPI/rbxapiref/documents) 2 | 3 | # documents 4 | 5 | The `documents` package is used for locating and parsing Roblox API 6 | documentation files. 7 | -------------------------------------------------------------------------------- /documents/directory.go: -------------------------------------------------------------------------------- 1 | package documents 2 | 3 | import ( 4 | "html/template" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // FileHandler hooks into a directory query, transforming a file within the 11 | // directory into a Section. 12 | // 13 | // The dir argument is the directory in which the file is located. The info 14 | // argument is information about the current file. The query argument is the 15 | // current query being made. 16 | // 17 | // The handler should return nil when the query does not match, or the 18 | // information given about the file is inapplicable. 19 | type FileHandler func(dir string, info os.FileInfo, query string) Section 20 | 21 | // DirectorySection represents a directory in a file system. 22 | type DirectorySection struct { 23 | Path string 24 | Handlers []FileHandler 25 | } 26 | 27 | // NewDirectorySection returns a new DirectorySection from the given path. 28 | // Optional handlers may be specified. 29 | func NewDirectorySection(path string, handlers ...FileHandler) *DirectorySection { 30 | return &DirectorySection{Path: path, Handlers: handlers} 31 | } 32 | 33 | func (s DirectorySection) Name() string { 34 | return filepath.Base(s.Path) 35 | } 36 | 37 | func (s DirectorySection) query(queries ...string) (section Section, next []string, isfile bool) { 38 | if len(queries) == 0 { 39 | return nil, nil, false 40 | } 41 | query, next := queries[0], queries[1:] 42 | files, err := ioutil.ReadDir(s.Path) 43 | if err != nil { 44 | return nil, nil, false 45 | } 46 | for _, info := range files { 47 | // Try each handler. 48 | for _, handler := range s.Handlers { 49 | if section = handler(s.Path, info, query); section != nil { 50 | return section, next, true 51 | } 52 | } 53 | // Try subdirectory. 54 | if info.IsDir() && query == info.Name() { 55 | section = &DirectorySection{ 56 | Path: filepath.Join(s.Path, info.Name()), 57 | Handlers: s.Handlers, 58 | } 59 | break 60 | } 61 | } 62 | return section, next, false 63 | } 64 | 65 | // Query queries a file of the given name from the directory. For each file of 66 | // the directory, each FileHandler is called in order. If a handler returns a 67 | // non-nil Section, then that section becomes the result. 68 | // 69 | // If no handler returns a section, and the current file is a directory whose 70 | // name matches the query, then the result will be a DirectorySection that 71 | // inherits the handlers of the current section. 72 | // 73 | // Subsequent names are queried from the resulting section, if one exists. 74 | func (s DirectorySection) Query(name ...string) Section { 75 | section, next, _ := s.query(name...) 76 | if section == nil { 77 | return nil 78 | } 79 | if len(next) > 0 { 80 | return section.Query(next...) 81 | } 82 | return section 83 | } 84 | 85 | // QueryAll is similar to Query, but returns all sections matching the first 86 | // name. 87 | func (s DirectorySection) QueryAll(name string) (sections []Section) { 88 | files, err := ioutil.ReadDir(s.Path) 89 | if err != nil { 90 | return nil 91 | } 92 | for _, info := range files { 93 | // Try each handler. 94 | for _, handler := range s.Handlers { 95 | if section := handler(s.Path, info, name); section != nil { 96 | sections = append(sections, section) 97 | } 98 | } 99 | // Try subdirectory. 100 | if info.IsDir() && name == info.Name() { 101 | sections = append(sections, &DirectorySection{ 102 | Path: filepath.Join(s.Path, info.Name()), 103 | Handlers: s.Handlers, 104 | }) 105 | } 106 | } 107 | return sections 108 | } 109 | 110 | func (s DirectorySection) Subsections() []Section { 111 | files, err := ioutil.ReadDir(s.Path) 112 | if err != nil { 113 | return nil 114 | } 115 | var sections []Section 116 | for _, info := range files { 117 | if !info.IsDir() { 118 | continue 119 | } 120 | sections = append(sections, &DirectorySection{ 121 | Path: filepath.Join(s.Path, info.Name()), 122 | Handlers: s.Handlers, 123 | }) 124 | } 125 | return sections 126 | } 127 | 128 | func (s DirectorySection) Render() template.HTML { 129 | return template.HTML(s.Path) 130 | } 131 | -------------------------------------------------------------------------------- /documents/doc.go: -------------------------------------------------------------------------------- 1 | // The documents page queries resources for document fragments. 2 | // 3 | // A resource is a hierarchical construct that contains Sections, which is 4 | // usually a directory or file. 5 | package documents 6 | -------------------------------------------------------------------------------- /documents/document.go: -------------------------------------------------------------------------------- 1 | package documents 2 | 3 | import ( 4 | "html/template" 5 | ) 6 | 7 | // Section represents a queryable portion of a resource. 8 | type Section interface { 9 | // Name is the name of the section. 10 | Name() string 11 | // Query retrieves the subsection referred to by the given name list. Each 12 | // successive name refers to a subsection of the previous. Returns nil if 13 | // the subsection was not found. 14 | Query(name ...string) Section 15 | // Subsections returns a list of the subsections within the current 16 | // section. 17 | Subsections() []Section 18 | // Render returns the content rendered to HTML. 19 | Render() template.HTML 20 | } 21 | 22 | // Headingable extends a Section by representing an outline with traversable 23 | // headings. 24 | type Headingable interface { 25 | Section 26 | // AdjustLevels offsets the levels of all headings in the outline, such 27 | // that RootLevel returns the given value. 28 | AdjustLevels(level int) 29 | // RootLevel returns the level of the root heading. This is defined as one 30 | // level less than the lowest heading level present in the outline. 31 | RootLevel() int 32 | // HeadingID returns the ID attribute of the heading. 33 | HeadingID() string 34 | // SetHeadingID sets the ID attribute of the heading. 35 | SetHeadingID(string) 36 | } 37 | 38 | // Linkable extends a Section by representing a document with traversable 39 | // reference links. 40 | type Linkable interface { 41 | Section 42 | // Links receives a walk function, which receives a link. The function is 43 | // applied to all links within the section, which can include those within 44 | // subsections. 45 | Links(walk func(link string)) 46 | // SetLinks receives a walk function, which receives a link and returns an 47 | // adjusted link. The function is applied to all links within the section, 48 | // which can include those within subsections. 49 | SetLinks(walk func(link string) string) 50 | } 51 | 52 | // Countable extends a Section by representing a document with countable 53 | // content. 54 | type Countable interface { 55 | Section 56 | // Count returns the number of elements in the section. 57 | Count() int 58 | } 59 | -------------------------------------------------------------------------------- /documents/git.go: -------------------------------------------------------------------------------- 1 | package documents 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "os/exec" 8 | "path/filepath" 9 | ) 10 | 11 | // FindGit returns the path to the git executable, or an empty string if it is 12 | // not available. 13 | func FindGit() string { 14 | git, err := exec.LookPath("git") 15 | if err != nil { 16 | return "" 17 | } 18 | return git 19 | } 20 | 21 | // gitRepo returns the path to the repository in which a file belongs. 22 | func gitRepo(git, file string) string { 23 | b, err := exec.Command(git, "-C", filepath.Dir(file), "rev-parse", "--show-toplevel").Output() 24 | if err != nil { 25 | return "" 26 | } 27 | return filepath.Clean(string(bytes.TrimRight(b, "\n"))) 28 | } 29 | 30 | // gitStatus returns the status of a file as known by git. A result of 1 means 31 | // the file is modified, 0 means the file may or may not exist, but is 32 | // untouched, -1 means untracked or ignored, and -2 means an error occurred. 33 | func gitStatus(git, repo, file string) int { 34 | if git == "" { 35 | return 0 36 | } 37 | b, err := exec.Command(git, "-C", repo, "status", "--porcelain", file).Output() 38 | if err != nil { 39 | return -2 40 | } 41 | if len(b) < 2 { 42 | return 0 43 | } 44 | if bytes.ContainsAny(b[0:2], "?!") { 45 | return -1 46 | } 47 | return 1 48 | } 49 | 50 | // GitRead attempts to read the most recently committed content of a file. 51 | func GitRead(git, file string) (b []byte, err error) { 52 | if git != "" { 53 | if repo := gitRepo(git, file); repo != "" { 54 | // File path relative to repo. 55 | rel, err := filepath.Rel(repo, file) 56 | if err != nil { 57 | goto nofile 58 | } 59 | rel = filepath.ToSlash(rel) 60 | 61 | status := gitStatus(git, repo, rel) 62 | switch status { 63 | case -1, -2: 64 | goto nofile 65 | case 1: 66 | // Read the file via git-show. 67 | if b, err = exec.Command(git, "-C", repo, "show", "HEAD:"+rel).Output(); err != nil { 68 | goto nofile 69 | } 70 | return b, nil 71 | } 72 | } 73 | } 74 | 75 | // Try reading the file normally. 76 | return ioutil.ReadFile(file) 77 | 78 | nofile: 79 | // Pretend the file does not exist. 80 | return nil, errors.New("file does not exist") 81 | } 82 | -------------------------------------------------------------------------------- /documents/markdown.go: -------------------------------------------------------------------------------- 1 | package documents 2 | 3 | import ( 4 | "bytes" 5 | "github.com/gomarkdown/markdown" 6 | "github.com/gomarkdown/markdown/ast" 7 | "github.com/gomarkdown/markdown/html" 8 | "github.com/gomarkdown/markdown/parser" 9 | "html/template" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | // MarkdownSection represents a portion of a markdown document. Sections are 16 | // delimited by headings. 17 | type MarkdownSection struct { 18 | // Heading is the name of the outer heading enclosing the section. 19 | Heading string 20 | // Level is the level of the outer heading enclosing the section. 21 | Level int 22 | // ID is "id" attribute of the outer heading enclosing the section. 23 | ID string 24 | // Document is the raw content of the section. 25 | Document *ast.Document 26 | // HeadingNode is the node of the outer heading enclosing the section. 27 | HeadingNode *ast.Heading 28 | // Sections contains each subsection. 29 | Sections []*MarkdownSection 30 | // Renderer specifies a custom renderer to use when rendering the section 31 | // content to HTML. If nil, the default HTML renderer is used. 32 | Renderer markdown.Renderer 33 | } 34 | 35 | // MarkdownHandler has a configurable FileHandler that parses a markdown file. 36 | type MarkdownHandler struct { 37 | // UseGit sets whether the handler is aware of git. If so, only committed 38 | // content will be used. That is, untracked files are ignored, and only 39 | // committed modifications to a file are used. 40 | UseGit bool 41 | 42 | // StripComments sets whether comments will be removed. 43 | StripComments bool 44 | } 45 | 46 | const commentPre = "" 48 | 49 | // Remove HTML comment text. 50 | func stripCommentText(b []byte) []byte { 51 | for n := 0; n < len(b); { 52 | i := bytes.Index(b[n:], []byte(commentPre)) 53 | if i < 0 { 54 | break 55 | } 56 | i += n 57 | 58 | j := bytes.Index(b[i+len(commentPre):], []byte(commentSuf)) 59 | if j < 0 { 60 | n = i + len(commentPre) 61 | continue 62 | } 63 | j += i + len(commentPre) + len(commentSuf) 64 | 65 | copy(b[i:], b[j:]) 66 | b = b[:len(b)-(j-i)] 67 | n = i 68 | } 69 | return b 70 | } 71 | 72 | // Remove ast.HTMLBlocks that are entirely comments. 73 | func stripCommentNodes(c *ast.Container) { 74 | if c == nil { 75 | return 76 | } 77 | children := c.Children[:0] 78 | for _, child := range c.Children { 79 | if leaf := child.AsLeaf(); leaf != nil { 80 | lit := bytes.TrimSpace(leaf.Literal) 81 | if bytes.HasPrefix(lit, []byte(commentPre)) && 82 | bytes.HasSuffix(lit, []byte(commentSuf)) { 83 | continue 84 | } 85 | leaf.Literal = stripCommentText(leaf.Literal) 86 | } 87 | children = append(children, child) 88 | stripCommentNodes(child.AsContainer()) 89 | } 90 | c.Children = children 91 | } 92 | 93 | // FileHandler is a FileHandler that parses a markdown file. 94 | func (h MarkdownHandler) FileHandler(dir string, info os.FileInfo, query string) Section { 95 | if info.IsDir() { 96 | return nil 97 | } 98 | ext := filepath.Ext(info.Name()) 99 | if ext != ".md" { 100 | return nil 101 | } 102 | base := filepath.Base(info.Name()) 103 | if base[:len(base)-len(ext)] != query { 104 | return nil 105 | } 106 | 107 | var b []byte 108 | var err error 109 | if path := filepath.Join(dir, info.Name()); h.UseGit { 110 | b, err = GitRead(FindGit(), path) 111 | } else { 112 | b, err = ioutil.ReadFile(path) 113 | } 114 | if err != nil { 115 | return nil 116 | } 117 | 118 | doc, ok := parser.NewWithExtensions( 119 | parser.CommonExtensions | parser.AutoHeadingIDs | parser.Footnotes, 120 | ).Parse(b).(*ast.Document) 121 | if !ok { 122 | return nil 123 | } 124 | if h.StripComments { 125 | stripCommentNodes(doc.AsContainer()) 126 | } 127 | return NewMarkdownSection(doc) 128 | } 129 | 130 | // MarkdownFileHandler is a FileHandler that parses a markdown file. 131 | func MarkdownFileHandler(dir string, info os.FileInfo, query string) Section { 132 | return MarkdownHandler{}.FileHandler(dir, info, query) 133 | } 134 | 135 | // getHeadingText returns the text from an ast.Heading. 136 | func getHeadingText(heading *ast.Heading) string { 137 | var text []byte 138 | for _, child := range heading.Children { 139 | if leaf := child.AsLeaf(); leaf != nil { 140 | text = append(text, leaf.Literal...) 141 | } 142 | } 143 | return string(text) 144 | } 145 | 146 | func parseMarkdownSection(section *MarkdownSection, level int, orphan bool) { 147 | children := section.Document.Children 148 | 149 | var i int 150 | var name string 151 | var node *ast.Heading 152 | var id string 153 | for k, child := range children { 154 | heading, ok := child.(*ast.Heading) 155 | if !ok || heading.Level > level { 156 | continue 157 | } 158 | sub := MarkdownSection{ 159 | Heading: name, 160 | HeadingNode: node, 161 | Level: level, 162 | ID: id, 163 | Document: &ast.Document{}, 164 | Renderer: section.Renderer, 165 | } 166 | if i < k { 167 | sub.Document.Children = children[i:k] 168 | } 169 | if !orphan { 170 | parseMarkdownSection(&sub, level+1, name == "") 171 | } 172 | section.Sections = append(section.Sections, &sub) 173 | i = k + 1 174 | name = getHeadingText(heading) 175 | node = heading 176 | id = heading.HeadingID 177 | } 178 | sub := MarkdownSection{ 179 | Heading: name, 180 | HeadingNode: node, 181 | Level: level, 182 | ID: id, 183 | Document: &ast.Document{}, 184 | Renderer: section.Renderer, 185 | } 186 | if i < len(children) { 187 | sub.Document.Children = children[i:] 188 | } 189 | if !orphan { 190 | parseMarkdownSection(&sub, level+1, name == "") 191 | } 192 | section.Sections = append(section.Sections, &sub) 193 | } 194 | 195 | // NewMarkdownSection creates a new MarkdownSection from an ast.Document. 196 | // 197 | // Subsections are created by outlining the headings of the document; each 198 | // subheading corresponds to a subsection, which can be queried by the name of 199 | // the heading. Also included are "orphaned" sections, which enclose parts of 200 | // the document without a heading. These can be queried with an empty string. 201 | // 202 | // Only headings which are direct children of the document are outlined. Note 203 | // that all subsections share the same underlying document. i.e. if a node 204 | // within a section is modified, the parent section will be affected. 205 | func NewMarkdownSection(document *ast.Document) *MarkdownSection { 206 | section := &MarkdownSection{Document: document} 207 | parseMarkdownSection(section, 1, false) 208 | return section 209 | } 210 | 211 | func (s *MarkdownSection) Name() string { 212 | return s.Heading 213 | } 214 | 215 | func (s *MarkdownSection) Query(name ...string) Section { 216 | if len(name) == 0 { 217 | return nil 218 | } 219 | for _, sub := range s.Sections { 220 | if sub.Heading != name[0] { 221 | continue 222 | } 223 | if len(name) > 1 { 224 | return sub.Query(name[1:]...) 225 | } 226 | return sub 227 | } 228 | return nil 229 | } 230 | 231 | func (s *MarkdownSection) Subsections() []Section { 232 | subs := make([]Section, len(s.Sections)) 233 | for i, sub := range s.Sections { 234 | subs[i] = sub 235 | } 236 | return subs 237 | } 238 | 239 | // SetRenderer sets the Renderer field of the section and all subsections. 240 | func (s *MarkdownSection) SetRender(renderer markdown.Renderer) { 241 | s.Renderer = renderer 242 | for _, sub := range s.Sections { 243 | sub.SetRender(renderer) 244 | } 245 | } 246 | 247 | func (s *MarkdownSection) Render() template.HTML { 248 | renderer := s.Renderer 249 | if renderer == nil { 250 | renderer = html.NewRenderer(html.RendererOptions{}) 251 | } 252 | render := markdown.Render(s.Document, renderer) 253 | for _, b := range render { 254 | switch b { 255 | case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0: 256 | continue 257 | } 258 | return template.HTML(render) 259 | } 260 | // Return empty string if all characters are spaces. 261 | return "" 262 | } 263 | 264 | // AdjustLevel adjusts the level of each heading node in the document such 265 | // that RootLevel returns the given value. This does not affect the Level 266 | // field of the section and subsections. 267 | func (s *MarkdownSection) AdjustLevels(level int) { 268 | root := s.RootLevel() 269 | if root < 0 { 270 | return 271 | } 272 | delta := level - root 273 | for _, child := range s.Document.GetChildren() { 274 | if heading, ok := child.(*ast.Heading); ok { 275 | heading.Level += delta 276 | } 277 | } 278 | } 279 | 280 | // RootLevel returns the level of the root heading, which is defined as one 281 | // less than the lowest heading level present in the document. Returns -1 if 282 | // there are no headings in the document. Heading levels are assumed to be 283 | // positive. 284 | func (s *MarkdownSection) RootLevel() (level int) { 285 | for _, child := range s.Document.GetChildren() { 286 | if heading, ok := child.(*ast.Heading); ok && (level == 0 || heading.Level < level) { 287 | level = heading.Level 288 | } 289 | } 290 | return level - 1 291 | } 292 | 293 | func (s *MarkdownSection) HeadingID() string { 294 | return s.ID 295 | } 296 | 297 | func (s *MarkdownSection) SetHeadingID(id string) { 298 | s.ID = id 299 | if s.HeadingNode != nil { 300 | s.HeadingNode.HeadingID = id 301 | } 302 | } 303 | 304 | func getLinks(node ast.Node, walk func(string)) { 305 | for _, child := range node.GetChildren() { 306 | switch node := child.(type) { 307 | case *ast.Link: 308 | walk(string(node.Destination)) 309 | case *ast.Image: 310 | walk(string(node.Destination)) 311 | } 312 | getLinks(child, walk) 313 | } 314 | } 315 | 316 | func (s *MarkdownSection) Links(walk func(string)) { 317 | getLinks(s.Document, walk) 318 | } 319 | 320 | func setLinks(node ast.Node, walk func(string) string) { 321 | for _, child := range node.GetChildren() { 322 | switch node := child.(type) { 323 | case *ast.Link: 324 | node.Destination = []byte(walk(string(node.Destination))) 325 | case *ast.Image: 326 | node.Destination = []byte(walk(string(node.Destination))) 327 | } 328 | setLinks(child, walk) 329 | } 330 | } 331 | 332 | func (s *MarkdownSection) SetLinks(walk func(string) string) { 333 | setLinks(s.Document, walk) 334 | } 335 | 336 | func (s *MarkdownSection) Count() int { 337 | return len(s.Document.Children) 338 | } 339 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/robloxapi/rbxapiref 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alecthomas/chroma v0.6.7 7 | github.com/anaminus/but v0.2.0 8 | github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83 9 | github.com/jessevdk/go-flags v1.4.0 10 | github.com/robloxapi/rbxapi v0.1.0 11 | github.com/robloxapi/rbxdhist v0.3.0 12 | github.com/robloxapi/rbxfile v0.1.2 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= 2 | github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= 3 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= 4 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 5 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 6 | github.com/alecthomas/chroma v0.6.7 h1:1hKci+AyKOxJrugR9veaocu9DQGR2/GecI72BpaO0Rg= 7 | github.com/alecthomas/chroma v0.6.7/go.mod h1:zVlgtbRS7BJDrDY9SB238RmpoCBCYFlLmcfZ3durxTk= 8 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= 9 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 10 | github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= 11 | github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= 12 | github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= 13 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= 14 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 15 | github.com/anaminus/but v0.2.0 h1:UPKY6UtvTZH8seod0rfVRsQxP8qssz+P6VE9a2AYeNY= 16 | github.com/anaminus/but v0.2.0/go.mod h1:44z5qYo/3MWnZDi6ifH3IgrFWa1VFfdTttL3IYN/9R4= 17 | github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= 18 | github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= 19 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= 20 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= 25 | github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 26 | github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83 h1:w5VNUHB0SP2tr1+boQJWKvnyn3P61UFErZ2e2ih6x0A= 27 | github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= 28 | github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= 29 | github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= 30 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 31 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 32 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 33 | github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= 34 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 35 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 36 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 37 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 38 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 39 | github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= 40 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 41 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 42 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 44 | github.com/robloxapi/rbxapi v0.1.0 h1:xcve5EgsdXRBfRu6e5y03lHbcELkAjTuRCDw8OX060I= 45 | github.com/robloxapi/rbxapi v0.1.0/go.mod h1:YOxBbPE55ssodC1fGi8urHCDwntYrJg9FVjyY3sRX5o= 46 | github.com/robloxapi/rbxdhist v0.3.0 h1:KL4schibnTnRCDvvbqJ2Q3yG4fkV4Gn8T7PG/7mTjTU= 47 | github.com/robloxapi/rbxdhist v0.3.0/go.mod h1:Pe77lcedyojev/k/FZDKJ5IJ903/KPei7Ip92DYFyCc= 48 | github.com/robloxapi/rbxfile v0.1.2 h1:tbOa5LitKkFI1x3Nd0PiBlOugezZcoFe9Sd0LsjTpxc= 49 | github.com/robloxapi/rbxfile v0.1.2/go.mod h1:WxL42b4u+4douat8iRiomMkX02AfcdRlvju3+D9uxCk= 50 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 51 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 54 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 55 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 56 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 57 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 58 | golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= 59 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= 60 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | -------------------------------------------------------------------------------- /internal/binio/bits.go: -------------------------------------------------------------------------------- 1 | package binio 2 | 3 | // SetBit sets bit i within p to v, returning the new value of p. 4 | func SetBit(p uint64, i int, v bool) uint64 { 5 | if v { 6 | return p | 1<>uint(i))&1 == 1 20 | } 21 | 22 | // GetBits gets bits i through j within p. 23 | func GetBits(p uint64, i, j int) int { 24 | return int((p >> uint(i)) & (1<= 1<<8 { 102 | panic("string too large") 103 | } 104 | if !w.Number(uint8(len(data))) { 105 | return false 106 | } 107 | return w.Bytes([]byte(data)) 108 | } 109 | -------------------------------------------------------------------------------- /resources/about.css: -------------------------------------------------------------------------------- 1 | .small { 2 | font-size : smaller; 3 | } 4 | blockquote { 5 | margin : var(--baseline) var(--indent); 6 | } 7 | blockquote::before { 8 | content : "\201C"; 9 | } 10 | blockquote::after { 11 | content : "\201D"; 12 | } 13 | 14 | main { 15 | --font-size : 12pt; 16 | font-size : var(--font-size); 17 | margin-top : var(--baseline); 18 | display : grid; 19 | grid-column-gap : var(--section-spacing); 20 | grid-template-columns : auto auto; 21 | grid-template-areas : 22 | "header who" 23 | "about who" 24 | "other who" 25 | ; 26 | } 27 | main header { grid-area : header } 28 | #who { grid-area : who } 29 | #about { grid-area : about } 30 | #other { grid-area : other } 31 | 32 | #who { 33 | border-left : 1px solid var(--theme-border); 34 | padding-left : var(--section-spacing); 35 | } 36 | #who li a::before { 37 | content : "on "; 38 | } 39 | #who ul { 40 | padding-left : var(--indent-half); 41 | } 42 | 43 | #about > P:first-child { 44 | margin-top : 0; 45 | } 46 | 47 | @media screen and (max-width: 600px) { 48 | main { 49 | grid-column-gap : 0; 50 | grid-template-columns : auto auto; 51 | grid-template-areas : 52 | "header" 53 | "about" 54 | "who" 55 | "other" 56 | ; 57 | } 58 | #who { 59 | border-left : 0 none; 60 | padding-left : 0; 61 | } 62 | #who li a::before { 63 | content : none; 64 | } 65 | #who > ul { 66 | list-style : none; 67 | display : flex; 68 | flex-flow : wrap row; 69 | margin : 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /resources/actions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | class Actions { 4 | constructor(funcs) { 5 | this.funcs = new Map(funcs); 6 | this.targets = new Map(); 7 | }; 8 | // Link a target element with an action. 9 | Link(target, autoupdate, caller) { 10 | if (!(caller instanceof Array)) { 11 | throw new Error("caller must be an Array"); 12 | }; 13 | let func = this.funcs.get(caller[0]); 14 | if (!func) { 15 | throw new Error("unknown function " + caller[0]); 16 | }; 17 | 18 | if (this.targets.get(target)) { 19 | this.Unlink(target); 20 | }; 21 | 22 | let callArgs = caller.slice(1); 23 | let badArg = false; 24 | for (let i = 0; i < func.arguments.length; i++) { 25 | let callArg = callArgs[i] 26 | let funcArg = func.arguments[i]; 27 | if (funcArg instanceof Array) { 28 | let okay = false; 29 | for (let type of funcArg) { 30 | if (type === undefined) { 31 | if (callArg === undefined) { 32 | okay = true; 33 | break; 34 | }; 35 | } else if (type === null) { 36 | if (callArg === null) { 37 | okay = true; 38 | break; 39 | }; 40 | } else if (typeof type === "string") { 41 | if (typeof callArg === type) { 42 | okay = true; 43 | break; 44 | }; 45 | } else if (callArg instanceof type) { 46 | okay = true; 47 | break; 48 | }; 49 | }; 50 | if (!okay) { 51 | badArg = i; 52 | break; 53 | }; 54 | } else if (typeof funcArg === "string") { 55 | if (typeof callArg !== funcArg) { 56 | badArg = i; 57 | break; 58 | }; 59 | } else if (!(callArg instanceof funcArg)) { 60 | badArg = i; 61 | break; 62 | }; 63 | }; 64 | if (badArg) { 65 | throw new Error("bad argument #" + (badArg+1) + " to " + caller[0]); 66 | }; 67 | 68 | let update = func.update.bind(undefined, target, callArgs); 69 | let data = { 70 | autoupdate: !!autoupdate, 71 | func: func, 72 | args: callArgs, 73 | update: update, 74 | } 75 | if (autoupdate) { 76 | data.value = func.construct(callArgs, update); 77 | }; 78 | this.targets.set(target, data); 79 | update(); 80 | }; 81 | // Unlink a target. 82 | Unlink(target) { 83 | let data = this.targets.get(target); 84 | if (!data) { 85 | return; 86 | }; 87 | this.targets.delete(target); 88 | data.func.destruct(data.args, data.update, data.value); 89 | }; 90 | // Set auto-updating state of target. 91 | Autoupdate(target, enabled) { 92 | let data = this.targets.get(target); 93 | if (!data) { 94 | return; 95 | }; 96 | if (data.autoupdate === enabled) { 97 | return; 98 | }; 99 | data.autoupdate = !!enabled; 100 | if (enbled) { 101 | data.value = data.func.construct(data.args, data.update); 102 | } else { 103 | data.func.destruct(data.args, data.update, data.value); 104 | data.value = undefined; 105 | }; 106 | }; 107 | // Manually update a target. 108 | Update() { 109 | for (let target of arguments) { 110 | let data = this.targets.get(target); 111 | if (!data) { 112 | return; 113 | }; 114 | data.update(); 115 | }; 116 | }; 117 | // Manually update all targets. 118 | UpdateAll() { 119 | for (let data of Array.from(this.targets.values())) { 120 | data.update(); 121 | }; 122 | }; 123 | // Create a link with a target and referent from selectors. 124 | QuickLink(targetSelector, referentSelector, caller) { 125 | let target = document.querySelector(targetSelector); 126 | let referent = document.querySelector(referentSelector); 127 | if (!target || !referent) { 128 | return; 129 | }; 130 | caller.splice(1, 0, referent); 131 | rbxapiActions.Link(target, false, caller); 132 | }; 133 | }; 134 | 135 | function selectChildrenOrQuery(root, selector) { 136 | if (selector[0] === ">") { 137 | selector = selector.slice(1); 138 | return Array.from(root.children).filter(function(v) { 139 | return v.matches(selector); 140 | }); 141 | }; 142 | return root.querySelectorAll(selector); 143 | }; 144 | 145 | function countVisible(root, selector) { 146 | let i = 0; 147 | for (let element of selectChildrenOrQuery(root, selector)) { 148 | if (getComputedStyle(element).display !== "none") { 149 | i++; 150 | }; 151 | }; 152 | return i; 153 | }; 154 | 155 | function initActions() { 156 | let actions = new Actions([ 157 | ["HideIfZero", { 158 | arguments: [ 159 | Element, // root 160 | "string", // selector 161 | ], 162 | construct: function(args, update) { 163 | let observer = new IntersectionObserver(update, {root: args[0]}) 164 | for (let element of selectChildrenOrQuery(args[0], args[1])) { 165 | observer.observe(element); 166 | }; 167 | return observer; 168 | }, 169 | destruct: function(args, update, observer) { 170 | if (observer.takeRecords().length > 0) { 171 | update(); 172 | }; 173 | observer.disconnect(); 174 | }, 175 | update: function(target, args) { 176 | if (countVisible(args[0], args[1]) === 0) { 177 | target.style.display = "none"; 178 | } else { 179 | target.style.display = ""; 180 | }; 181 | }, 182 | }], 183 | ["Count", { 184 | arguments: [ 185 | Element, // root 186 | "string", // selector 187 | [Function, undefined], // singular 188 | [Function, undefined], // plural 189 | ], 190 | construct: function(args, update) { 191 | let observer = new IntersectionObserver(update, {root: args[0]}) 192 | for (let element of selectChildrenOrQuery(args[0], args[1])) { 193 | observer.observe(element); 194 | }; 195 | return observer; 196 | }, 197 | destruct: function(args, update, observer) { 198 | if (observer.takeRecords().length > 0) { 199 | update(); 200 | }; 201 | observer.disconnect(); 202 | }, 203 | update: function(target, args) { 204 | let count = countVisible(args[0], args[1]) 205 | if (args[2] && args[3]) { 206 | if (count === 1) { 207 | count = args[2](count); 208 | } else { 209 | count = args[3](count); 210 | }; 211 | } else if (args[2]) { 212 | count = args[2](count); 213 | }; 214 | target.innerText = count; 215 | }, 216 | }], 217 | ]); 218 | 219 | window.rbxapiActions = actions; 220 | window.dispatchEvent(new Event("rbxapiActions")); 221 | }; 222 | initActions(); 223 | }; 224 | -------------------------------------------------------------------------------- /resources/ana.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/class.css: -------------------------------------------------------------------------------- 1 | main { 2 | display : grid; 3 | grid-gap : 0 var(--section-spacing); 4 | } 5 | main > header { grid-area : header } 6 | main > nav { grid-area : nav } 7 | main > #summary { grid-area : summary } 8 | main > #tree { grid-area : tree } 9 | main > #members-index { grid-area : index } 10 | main > #removed-members-index { grid-area : rindex } 11 | main > #details { grid-area : details } 12 | main > #constructors { grid-area : constructors } 13 | main > #examples { grid-area : examples } 14 | main > #history { grid-area : history } 15 | main > #members { grid-area : members } 16 | main > #removed-members { grid-area : rmembers } 17 | main > #references { grid-area : references } 18 | 19 | main { 20 | grid-template-columns : auto; 21 | grid-template-areas : 22 | "header " 23 | "summary " 24 | "nav " 25 | "tree " 26 | "index " 27 | "rindex " 28 | "details " 29 | "constructors" 30 | "examples " 31 | "history " 32 | "members " 33 | "rmembers " 34 | "references " 35 | ; 36 | } 37 | main > nav > section { 38 | overflow-y : auto; 39 | padding-right : var(--scrollbar-width); 40 | max-height : 61.8vh; 41 | --indent : var(--indent-half); 42 | } 43 | main > nav .toc-members { 44 | display : none; 45 | } 46 | @media screen and (min-width: 800px) { 47 | main { 48 | grid-template-columns : 1fr auto; 49 | grid-template-areas : 50 | "header header" 51 | "summary nav " 52 | "tree nav " 53 | "index nav " 54 | "rindex nav " 55 | "details nav " 56 | "constructors nav " 57 | "examples nav " 58 | "history nav " 59 | "members nav " 60 | "rmembers nav " 61 | "references nav " 62 | ; 63 | justify-content : start; 64 | grid-template-rows : repeat(10,auto) 1fr; 65 | } 66 | main > nav { 67 | border-left : 1px solid var(--theme-border); 68 | padding-left : var(--section-spacing); 69 | } 70 | main > nav .toc-members { 71 | display : block; 72 | } 73 | @supports (position:sticky) { 74 | main > nav > section { 75 | position : sticky; 76 | top : 0; 77 | max-height : calc(100vh - var(--baseline) - var(--header-height)); 78 | } 79 | } 80 | } 81 | @media screen and (min-width: 1000px) { 82 | main { 83 | grid-template-columns : auto 1fr auto; 84 | grid-template-areas : 85 | "header header header" 86 | "tree summary nav " 87 | "index index nav " 88 | "rindex rindex nav " 89 | "details details nav " 90 | "constructors constructors nav " 91 | "examples examples nav " 92 | "history history nav " 93 | "members members nav " 94 | "rmembers rmembers nav " 95 | "references references nav " 96 | ; 97 | justify-content : start; 98 | grid-template-rows : repeat(9,auto) 1fr; 99 | } 100 | main > #tree { 101 | border-right : 1px solid var(--theme-border); 102 | padding-right : var(--section-spacing); 103 | } 104 | } 105 | 106 | #members-sections > section, 107 | #removed-members-sections > section { 108 | border-bottom : 1px solid var(--theme-border); 109 | } 110 | -------------------------------------------------------------------------------- /resources/class.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function expandMemberList(element, force) { 4 | let placehold = element.closest(".inherited-members"); 5 | if (placehold === null) { 6 | return; 7 | }; 8 | let head = placehold.closest("thead"); 9 | if (head === null || !head.parentElement.classList.contains("index-card")) { 10 | return; 11 | }; 12 | 13 | // Get the a subsequent sibling that matches query. Stop when a sibling 14 | // matching boundary is encountered. 15 | function nextMatching(element, query, boundary) { 16 | do { 17 | element = element.nextElementSibling; 18 | if (element === null) { 19 | break; 20 | }; 21 | if (element.matches(query)) { 22 | return element; 23 | }; 24 | } while (boundary && !element.matches(boundary)); 25 | return null; 26 | }; 27 | 28 | // Attempt to toggle a list that was loaded previously. 29 | let body = nextMatching(head, "tbody.inherited-members-list", "thead"); 30 | if (body !== null) { 31 | if (typeof(force) === "boolean") { 32 | if (force) { 33 | body.style.display = ""; 34 | } else { 35 | body.style.display = "none"; 36 | }; 37 | rbxapiActions.Update(head, element); 38 | return; 39 | }; 40 | if (body.style.display === "none") { 41 | body.style.display = ""; 42 | } else { 43 | body.style.display = "none"; 44 | }; 45 | rbxapiActions.Update(head, element); 46 | return; 47 | } 48 | 49 | if (force === false) { 50 | return; 51 | }; 52 | 53 | let link = placehold.querySelector("a.element-link"); 54 | if (link === null || link.href.length === 0) { 55 | return; 56 | }; 57 | let url = link.href 58 | 59 | { 60 | // Create a message indicating that data is being loaded. Also use 61 | // this message as a "lock", which will usually prevent multiple 62 | // requests at once by this element. 63 | let loader = nextMatching(head, "tbody.loading-message", "thead") 64 | if (loader !== null) { 65 | return; 66 | }; 67 | head.insertAdjacentHTML("afterend", '
Loading...'); 68 | }; 69 | 70 | function clearLoader(event) { 71 | let loader = nextMatching(head, "tbody.loading-message", "thead"); 72 | if (loader === null) { 73 | return; 74 | }; 75 | loader.parentElement.removeChild(loader); 76 | }; 77 | 78 | function formatSingular(c) { 79 | return c + " member"; 80 | }; 81 | function formatPlural(c) { 82 | return c + " members"; 83 | }; 84 | function onLoaded(event) { 85 | if (event.target.response === null) { 86 | return; 87 | }; 88 | let body = nextMatching(head, "tbody.inherited-members-list", "thead"); 89 | if (body !== null) { 90 | return; 91 | }; 92 | body = event.target.response.querySelector("#members-index .index-card tbody"); 93 | if (body === null) { 94 | return; 95 | }; 96 | body.classList.add("inherited-members-list"); 97 | head.insertAdjacentElement("afterend", body); 98 | window.rbxapiActions.Link(head, false, ["HideIfZero", body, ">*"]) 99 | window.rbxapiActions.Link(element, false, ["Count", body, ">*", formatSingular, formatPlural]); 100 | rbxapiActions.Update(head, element); 101 | }; 102 | 103 | let req = new XMLHttpRequest(); 104 | req.addEventListener("load", function(event) { 105 | onLoaded(event); 106 | clearLoader(event); 107 | }); 108 | req.addEventListener("error", clearLoader); 109 | req.addEventListener("abort", clearLoader); 110 | req.open("GET", url); 111 | req.responseType = "document"; 112 | req.send(); 113 | }; 114 | 115 | function settingsLoaded() { 116 | window.rbxapiSettings.Listen("ExpandMembers", function(name, value, initial) { 117 | if (initial && value) { 118 | let id = document.location.hash.slice(1); 119 | if (id !== "") { 120 | if (document.getElementById(id)) { 121 | // Don't auto-expand if there's a target. 122 | return; 123 | }; 124 | }; 125 | }; 126 | for (let count of document.querySelectorAll(".inherited-members a.member-count")) { 127 | expandMemberList(count, value); 128 | }; 129 | }); 130 | }; 131 | 132 | function domLoaded() { 133 | for (let parent of document.getElementsByClassName("inherited-members")) { 134 | let count = parent.querySelector("a.member-count"); 135 | if (count === null) { 136 | continue; 137 | }; 138 | count.href = "#"; 139 | count.title = "Click to toggle visibility of members."; 140 | count.addEventListener("click", function(event) { 141 | // Prevent clicked anchor from doing anything else. 142 | event.preventDefault(); 143 | expandMemberList(event.target); 144 | }); 145 | }; 146 | 147 | // ToC 148 | rbxapiActions.QuickLink( 149 | "#toc-superclasses", 150 | "#superclasses > ul", 151 | ["HideIfZero", ">*"] 152 | ); 153 | rbxapiActions.QuickLink( 154 | "#toc-subclasses", 155 | "#subclasses > ul", 156 | ["HideIfZero", ">*"] 157 | ); 158 | rbxapiActions.QuickLink( 159 | "#toc-class-tree", 160 | "#toc-class-tree > ol", 161 | ["HideIfZero", ">*"] 162 | ); 163 | rbxapiActions.QuickLink( 164 | "#toc-removed-members-index", 165 | "#removed-members-index > .index-card > tbody:first-of-type", 166 | ["HideIfZero", ">:not(.empty)"] 167 | ); 168 | rbxapiActions.QuickLink( 169 | "#toc-members", 170 | "#members-sections", 171 | ["HideIfZero", ">*"] 172 | ); 173 | rbxapiActions.QuickLink( 174 | "#toc-removed-members", 175 | "#removed-members-sections", 176 | ["HideIfZero", ">*"] 177 | ); 178 | rbxapiActions.QuickLink( 179 | "#toc-classes", 180 | "#classes > ul", 181 | ["HideIfZero", ">*"] 182 | ); 183 | rbxapiActions.QuickLink( 184 | "#toc-enums", 185 | "#enums > ul", 186 | ["HideIfZero", ">*"] 187 | ); 188 | rbxapiActions.QuickLink( 189 | "#toc-referrers", 190 | "#referrers > ul", 191 | ["HideIfZero", ">*"] 192 | ); 193 | rbxapiActions.QuickLink( 194 | "#toc-references", 195 | "#toc-references > ol", 196 | ["HideIfZero", ">*"] 197 | ); 198 | 199 | // Sections 200 | rbxapiActions.QuickLink( 201 | "#superclasses", 202 | "#superclasses > ul", 203 | ["HideIfZero", ">*"] 204 | ); 205 | rbxapiActions.QuickLink( 206 | "#subclasses", 207 | "#subclasses > ul", 208 | ["HideIfZero", ">*"] 209 | ); 210 | rbxapiActions.QuickLink( 211 | "#tree", 212 | "#tree", 213 | ["HideIfZero", ">*"] 214 | ); 215 | rbxapiActions.QuickLink( 216 | "#removed-members-index", 217 | "#removed-members-index > .index-card > tbody:first-of-type", 218 | ["HideIfZero", ">:not(.empty)"] 219 | ); 220 | rbxapiActions.QuickLink( 221 | "#members", 222 | "#members-sections", 223 | ["HideIfZero", ">*"] 224 | ); 225 | rbxapiActions.QuickLink( 226 | "#removed-members", 227 | "#removed-members-sections", 228 | ["HideIfZero", ">*"] 229 | ); 230 | rbxapiActions.QuickLink( 231 | "#classes", 232 | "#classes > ul", 233 | ["HideIfZero", ">*"] 234 | ); 235 | rbxapiActions.QuickLink( 236 | "#enums", 237 | "#enums > ul", 238 | ["HideIfZero", ">*"] 239 | ); 240 | rbxapiActions.QuickLink( 241 | "#referrers", 242 | "#referrers > ul", 243 | ["HideIfZero", ">*"] 244 | ); 245 | 246 | // Counters 247 | function formatCount(c) { 248 | return "(" + c + ")"; 249 | }; 250 | rbxapiActions.QuickLink( 251 | "#superclasses > header .element-count", 252 | "#superclasses > ul", 253 | ["Count", ">*", formatCount] 254 | ); 255 | rbxapiActions.QuickLink( 256 | "#subclasses > header .element-count", 257 | "#subclasses > ul", 258 | ["Count", ">*", formatCount] 259 | ); 260 | rbxapiActions.QuickLink( 261 | "#members-index > header .element-count", 262 | "#members-index > .index-card > tbody:first-of-type", 263 | ["Count", ">:not(.empty)", formatCount] 264 | ); 265 | rbxapiActions.QuickLink( 266 | "#removed-members-index > header .element-count", 267 | "#removed-members-index > .index-card > tbody:first-of-type", 268 | ["Count", ">:not(.empty)", formatCount] 269 | ); 270 | rbxapiActions.QuickLink( 271 | "#classes > header .element-count", 272 | "#classes > ul", 273 | ["Count", ">*", formatCount] 274 | ); 275 | rbxapiActions.QuickLink( 276 | "#enums > header .element-count", 277 | "#enums > ul", 278 | ["Count", ">*", formatCount] 279 | ); 280 | rbxapiActions.QuickLink( 281 | "#referrers > header .element-count", 282 | "#referrers > ul", 283 | ["Count", ">*", formatCount] 284 | ); 285 | 286 | if (window.rbxapiSettings) { 287 | settingsLoaded(); 288 | } else { 289 | window.addEventListener("rbxapiSettings", settingsLoaded); 290 | }; 291 | }; 292 | 293 | function actionsLoaded() { 294 | if (document.readyState === "loading") { 295 | window.addEventListener("DOMContentLoaded", domLoaded); 296 | } else { 297 | domLoaded(); 298 | }; 299 | }; 300 | 301 | if (window.rbxapiActions) { 302 | actionsLoaded(); 303 | } else { 304 | window.addEventListener("rbxapiActions", actionsLoaded); 305 | }; 306 | }; 307 | -------------------------------------------------------------------------------- /resources/doc.css: -------------------------------------------------------------------------------- 1 | .doc pre:not(.chroma) { 2 | background-color : var(--theme-table); 3 | border-left : 2px solid var(--theme-border); 4 | padding : 1em 1.618em; 5 | white-space : pre-wrap; 6 | } 7 | .doc :not(pre):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) code { 8 | background-color : var(--theme-table); 9 | padding : 0 0.5ch; 10 | } 11 | 12 | .doc > div.chroma { 13 | background-color : var(--theme-table); 14 | border-left : 2px solid var(--theme-border); 15 | margin : var(--baseline) 0; 16 | } 17 | .doc > div.chroma pre { 18 | background-color : unset; 19 | -moz-tab-size : var(--indent); 20 | -o-tab-size : var(--indent); 21 | tab-size : var(--indent); 22 | } 23 | .doc > div.chroma .lntd:last-child { 24 | display : block; 25 | overflow : auto; 26 | } 27 | .doc table { 28 | background-color : var(--theme-border); 29 | color : var(--theme-text); 30 | border-collapse : separate; 31 | border-spacing : 1px; 32 | } 33 | .doc table td { 34 | background-color : var(--theme-table); 35 | color : var(--theme-table-text); 36 | padding : 0.5ch 1ch; 37 | } 38 | .doc table th { 39 | background-color : var(--theme-table-header); 40 | color : var(--theme-table-header-text); 41 | padding : 0.5ch 1ch; 42 | } 43 | 44 | /*////////////////////////////////////////////////////////////////*/ 45 | /* Syntax highlighting style */ 46 | 47 | /* Background */ .chroma { 48 | background-color : var(--syntax-background); 49 | -moz-tab-size : 4; 50 | -o-tab-size : 4; 51 | tab-size : 4; 52 | } 53 | /* LineTableTD */ 54 | .chroma .lntd:last-child { 55 | width : 100%; 56 | } 57 | /* Error */ .chroma .err { 58 | color : var(--syntax-error); 59 | } 60 | /* LineTableTD */ 61 | .chroma .lntd { 62 | vertical-align : top; 63 | padding : 0; 64 | margin : 0; 65 | border : 0; 66 | } 67 | /* LineTable */ 68 | .chroma .lntable { 69 | border-spacing : 0; 70 | padding : 0; 71 | margin : 0; 72 | border : 0; 73 | width : auto; 74 | overflow : auto; 75 | display : block; 76 | max-height : 38.2vh; 77 | background-color : var(--theme-table); 78 | } 79 | /* LineHighlight */ 80 | .chroma .hl { 81 | display : block; 82 | width : 100%; 83 | background-color : var(--syntax-line-highlight); 84 | } 85 | /* LineNumbersTable */ 86 | .chroma .lnt { 87 | margin-right : 0.4em; 88 | padding : 0 0.4em 0 0.4em; 89 | color : var(--syntax-line-numbers-table); 90 | } 91 | /* LineNumbers */ 92 | .chroma .ln { 93 | margin-right : 0.4em; 94 | padding : 0 0.4em 0 0.4em; 95 | color : var(--syntax-line-numbers); 96 | } 97 | /* Keyword */ 98 | .chroma .k { 99 | color : var(--syntax-keyword); 100 | font-weight : bold; 101 | } 102 | /* KeywordConstant */ 103 | .chroma .kc { 104 | color : var(--syntax-keyword); 105 | font-weight : bold; 106 | } 107 | /* KeywordDeclaration */ 108 | .chroma .kd { 109 | color : var(--syntax-keyword); 110 | font-weight : bold; 111 | } 112 | /* KeywordNamespace */ 113 | .chroma .kn { 114 | color : var(--syntax-keyword); 115 | font-weight : bold; 116 | } 117 | /* KeywordPseudo */ 118 | .chroma .kp { 119 | color : var(--syntax-keyword); 120 | font-weight : bold; 121 | } 122 | /* KeywordReserved */ 123 | .chroma .kr { 124 | color : var(--syntax-keyword); 125 | font-weight : bold 126 | } 127 | /* KeywordType */ 128 | .chroma .kt { 129 | color : var(--syntax-keyword); 130 | font-weight : bold; 131 | } 132 | /* Name */ 133 | .chroma .n { 134 | color : var(--syntax-name); 135 | } 136 | /* NameAttribute */ 137 | .chroma .na { 138 | color : var(--syntax-name); 139 | } 140 | /* NameBuiltin */ 141 | .chroma .nb { 142 | color : var(--syntax-name); 143 | } 144 | /* NameBuiltinPseudo */ 145 | .chroma .bp { 146 | color : var(--syntax-name); 147 | } 148 | /* NameClass */ 149 | .chroma .nc { 150 | color : var(--syntax-name); 151 | } 152 | /* NameConstant */ 153 | .chroma .no { 154 | color : var(--syntax-name); 155 | } 156 | /* NameDecorator */ 157 | .chroma .nd { 158 | color : var(--syntax-name); 159 | } 160 | /* NameEntity */ 161 | .chroma .ni { 162 | color : var(--syntax-name); 163 | } 164 | /* NameException */ 165 | .chroma .ne { 166 | color : var(--syntax-name); 167 | } 168 | /* NameFunction */ 169 | .chroma .nf { 170 | color : var(--syntax-name); 171 | } 172 | /* NameFunctionMagic */ 173 | .chroma .fm { 174 | color : var(--syntax-name); 175 | } 176 | /* NameLabel */ 177 | .chroma .nl { 178 | color : var(--syntax-name); 179 | } 180 | /* NameNamespace */ 181 | .chroma .nn { 182 | color : var(--syntax-name); 183 | } 184 | /* NameOther */ 185 | .chroma .nx { 186 | color : var(--syntax-name); 187 | } 188 | /* NameProperty */ 189 | .chroma .py { 190 | color : var(--syntax-name); 191 | } 192 | /* NameTag */ 193 | .chroma .nt { 194 | color : var(--syntax-name); 195 | } 196 | /* NameVariable */ 197 | .chroma .nv { 198 | color : var(--syntax-name); 199 | } 200 | /* NameVariableClass */ 201 | .chroma .vc { 202 | color : var(--syntax-name); 203 | } 204 | /* NameVariableGlobal */ 205 | .chroma .vg { 206 | color : var(--syntax-name); 207 | } 208 | /* NameVariableInstance */ 209 | .chroma .vi { 210 | color : var(--syntax-name); 211 | } 212 | /* NameVariableMagic */ 213 | .chroma .vm { 214 | color : var(--syntax-name); 215 | } 216 | /* LiteralString */ 217 | .chroma .s { 218 | color : var(--syntax-literal-string); 219 | } 220 | /* LiteralStringAffix */ 221 | .chroma .sa { 222 | color : var(--syntax-literal-string); 223 | } 224 | /* LiteralStringBacktick */ 225 | .chroma .sb { 226 | color : var(--syntax-literal-string); 227 | } 228 | /* LiteralStringChar */ 229 | .chroma .sc { 230 | color : var(--syntax-literal-string); 231 | } 232 | /* LiteralStringDelimiter */ 233 | .chroma .dl { 234 | color : var(--syntax-literal-string); 235 | } 236 | /* LiteralStringDoc */ 237 | .chroma .sd { 238 | color : var(--syntax-literal-string); 239 | } 240 | /* LiteralStringDouble */ 241 | .chroma .s2 { 242 | color : var(--syntax-literal-string); 243 | } 244 | /* LiteralStringEscape */ 245 | .chroma .se { 246 | color : var(--syntax-literal-string); 247 | } 248 | /* LiteralStringHeredoc */ 249 | .chroma .sh { 250 | color : var(--syntax-literal-string); 251 | } 252 | /* LiteralStringInterpol */ 253 | .chroma .si { 254 | color : var(--syntax-literal-string); 255 | } 256 | /* LiteralStringOther */ 257 | .chroma .sx { 258 | color : var(--syntax-literal-string); 259 | } 260 | /* LiteralStringRegex */ 261 | .chroma .sr { 262 | color : var(--syntax-literal-string); 263 | } 264 | /* LiteralStringSingle */ 265 | .chroma .s1 { 266 | color : var(--syntax-literal-string); 267 | } 268 | /* LiteralStringSymbol */ 269 | .chroma .ss { 270 | color : var(--syntax-literal-string); 271 | } 272 | /* LiteralNumber */ 273 | .chroma .m { 274 | color : var(--syntax-literal-number); 275 | } 276 | /* LiteralNumberBin */ 277 | .chroma .mb { 278 | color : var(--syntax-literal-number); 279 | } 280 | /* LiteralNumberFloat */ 281 | .chroma .mf { 282 | color : var(--syntax-literal-number); 283 | } 284 | /* LiteralNumberHex */ 285 | .chroma .mh { 286 | color : var(--syntax-literal-number); 287 | } 288 | /* LiteralNumberInteger */ 289 | .chroma .mi { 290 | color : var(--syntax-literal-number); 291 | } 292 | /* LiteralNumberIntegerLong */ 293 | .chroma .il { 294 | color : var(--syntax-literal-number); 295 | } 296 | /* LiteralNumberOct */ 297 | .chroma .mo { 298 | color : var(--syntax-literal-number); 299 | } 300 | /* Operator */ 301 | .chroma .o { 302 | color : var(--syntax-operator); 303 | } 304 | /* OperatorWord */ 305 | .chroma .ow { 306 | color : var(--syntax-operator-word); 307 | font-weight : bold; 308 | } 309 | /* Punctuation */ 310 | .chroma .p { 311 | color : var(--syntax-punctuation); 312 | } 313 | /* Comment */ 314 | .chroma .c { 315 | color : var(--syntax-comment); 316 | } 317 | /* CommentHashbang */ 318 | .chroma .ch { 319 | color : var(--syntax-comment); 320 | } 321 | /* CommentMultiline */ 322 | .chroma .cm { 323 | color : var(--syntax-comment); 324 | } 325 | /* CommentSingle */ 326 | .chroma .c1 { 327 | color : var(--syntax-comment); 328 | } 329 | /* CommentSpecial */ 330 | .chroma .cs { 331 | color : var(--syntax-comment); 332 | } 333 | /* CommentPreproc */ 334 | .chroma .cp { 335 | color : var(--syntax-comment-preproc); 336 | } 337 | /* CommentPreprocFile */ 338 | .chroma .cpf { 339 | color : var(--syntax-comment-preproc); 340 | } 341 | -------------------------------------------------------------------------------- /resources/docmon.css: -------------------------------------------------------------------------------- 1 | summary { 2 | cursor : pointer; 3 | } 4 | details { 5 | margin-bottom : var(--baseline); 6 | } 7 | 8 | .sortable { 9 | cursor : pointer; 10 | } 11 | .dsc:after { 12 | content : " ▼"; 13 | } 14 | .asc:after { 15 | content : " ▲"; 16 | } 17 | 18 | #status { 19 | table-layout : fixed; 20 | text-align : center; 21 | } 22 | #status tr > :nth-child(1) { 23 | text-align : right; 24 | } 25 | #status tr > :nth-child(2), 26 | #status tr > :nth-child(3) { 27 | text-align : left; 28 | } 29 | #status th:nth-child(1) { width : 5ch } 30 | #status th:nth-child(2) { width : 15ch } 31 | #status th:nth-child(3) { width : 67ch } 32 | #status th:nth-child(4) { width : 10ch } 33 | #status th:nth-child(5) { width : 10ch } 34 | #status th:nth-child(6) { width : 11ch } 35 | #status th:nth-child(7) { width : 12ch } 36 | #status tr.p > :nth-child(1), 37 | #status tr.p > :nth-child(2), 38 | #status tr.p > :nth-child(3) { 39 | background-color : var(--theme-table-header); 40 | color : var(--theme-table-header-text); 41 | } 42 | 43 | table { 44 | font-family : var(--monospace); 45 | color : var(--theme-table-text); 46 | } 47 | th { 48 | background-color : var(--theme-table-header); 49 | color : var(--theme-table-header-text); 50 | } 51 | td { 52 | background-color : var(--theme-table); 53 | } 54 | 55 | .d { 56 | background-color : var(--theme-table-header); 57 | color : var(--theme-table-header-text); 58 | } 59 | .c { 60 | background-color : var(--theme-patch-remove); 61 | color : var(--theme-patch-remove-text); 62 | } 63 | .b { 64 | background-color : var(--theme-patch-change); 65 | color : var(--theme-patch-change-text); 66 | } 67 | .a { 68 | background-color : var(--theme-patch-add); 69 | color : var(--theme-patch-add-text); 70 | } 71 | 72 | .cell { 73 | padding : 0 0.5ch; 74 | } 75 | 76 | #coverage { 77 | font-size : larger; 78 | font-weight : bold; 79 | } 80 | #coverage .value { 81 | background-image : linear-gradient(var(--max-color) calc(var(--baseline)*1.2*256*(var(--value) - 1)), var(--min-color) calc(var(--baseline)*1.2*256*var(--value))); 82 | } 83 | -------------------------------------------------------------------------------- /resources/docmon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function getNumber(cell) { 4 | return Number(cell.textContent); 5 | }; 6 | 7 | function getString(cell) { 8 | return cell.textContent; 9 | }; 10 | 11 | function getCell(cell) { 12 | return cell; 13 | }; 14 | 15 | function getPercent(cell) { 16 | return Number(cell.textContent.slice(0, -1)); 17 | }; 18 | 19 | function presortNumber(i, j) { 20 | return this[i] - this[j]; 21 | }; 22 | 23 | function presortString(i, j) { 24 | if (this[i] < this[j]) { 25 | return -1; 26 | } else if (this[i] > this[j]) { 27 | return 1; 28 | }; 29 | return 0; 30 | }; 31 | 32 | function presortType(i, j) { 33 | if (this[i].textContent < this[j].textContent) { 34 | return -1; 35 | } else if (this[i].textContent > this[j].textContent) { 36 | return 1; 37 | }; 38 | if (this[i].nextElementSibling.textContent < this[j].nextElementSibling.textContent) { 39 | return -1; 40 | } else if (this[i].nextElementSibling.textContent > this[j].nextElementSibling.textContent) { 41 | return 1; 42 | }; 43 | return 0; 44 | }; 45 | 46 | function presortSectionSummary(i, j) { 47 | if (this[i].className < this[j].className) { 48 | return 1; 49 | } else if (this[i].className > this[j].className) { 50 | return -1; 51 | }; 52 | if (this[i].textContent < this[j].textContent) { 53 | return -1; 54 | } else if (this[i].textContent > this[j].textContent) { 55 | return 1; 56 | }; 57 | return 0; 58 | }; 59 | 60 | function presortSectionNumber(i, j) { 61 | if (this[i].className < this[j].className) { 62 | return 1; 63 | } else if (this[i].className > this[j].className) { 64 | return -1; 65 | }; 66 | return Number(this[i].textContent) - Number(this[j].textContent); 67 | }; 68 | 69 | function sortAsc(parent, rows, indexes) { 70 | for (let i = 0; i < indexes.length; i++) { 71 | parent.appendChild(rows[indexes[i]]); 72 | }; 73 | }; 74 | 75 | function sortDsc(parent, rows, indexes) { 76 | for (let i = indexes.length-1; i >= 0; i--) { 77 | parent.appendChild(rows[indexes[i]]); 78 | }; 79 | }; 80 | 81 | function initDocmon() { 82 | let coverage = document.querySelector("#coverage .value"); 83 | if (coverage) { 84 | let value = Number(coverage.firstChild.data.slice(0, -1))/100; 85 | if (!Number.isNaN(value)) { 86 | let start, end; 87 | if (value >= 0.5) { 88 | start = "var(--theme-patch-change)" 89 | end = "var(--theme-patch-add)" 90 | value = value*2 - 1; 91 | } else { 92 | start = "var(--theme-patch-remove)" 93 | end = "var(--theme-patch-change)" 94 | value = value*2; 95 | }; 96 | coverage.style.setProperty("--value", String(value)); 97 | coverage.style.setProperty("--min-color", start); 98 | coverage.style.setProperty("--max-color", end); 99 | }; 100 | }; 101 | 102 | let cols = [ 103 | [getNumber, presortNumber], 104 | [getCell, presortType], 105 | [getString, presortString], 106 | [getCell, presortSectionSummary], 107 | [getCell, presortSectionNumber], 108 | [getCell, presortSectionNumber], 109 | [getPercent, presortNumber], 110 | ]; 111 | 112 | let table = document.getElementById("status"); 113 | if (!table) { 114 | return; 115 | }; 116 | let head = table.querySelector("thead"); 117 | if (!head) { 118 | return; 119 | }; 120 | let body = table.querySelector("tbody"); 121 | if (!body) { 122 | return; 123 | }; 124 | 125 | let headers = head.rows[0].children; 126 | let rows = Array.prototype.slice.call(body.rows); 127 | let sorters = []; 128 | sorters.length = cols.length; 129 | for (let i = 0; i < sorters.length; i++) { 130 | let indexes = []; 131 | indexes.length = rows.length; 132 | let values = []; 133 | values.length = rows.length; 134 | for (let j = 0; j < values.length; j++) { 135 | indexes[j] = j; 136 | values[j] = cols[i][0](rows[j].children[i]); 137 | }; 138 | indexes.sort(cols[i][1].bind(values)); 139 | sorters[i] = indexes; 140 | 141 | headers[i].classList.add("sortable"); 142 | headers[i].onclick = function(event) { 143 | let header = event.target; 144 | if (header.classList.contains("asc")) { 145 | for (let h of headers) { 146 | h.classList.remove("asc"); 147 | h.classList.remove("dsc"); 148 | }; 149 | header.classList.add("dsc"); 150 | sortDsc(body, rows, indexes); 151 | } else if (header.classList.contains("dsc")) { 152 | for (let h of headers) { 153 | h.classList.remove("asc"); 154 | h.classList.remove("dsc"); 155 | }; 156 | sortAsc(body, rows, sorters[0]); 157 | } else { 158 | for (let h of headers) { 159 | h.classList.remove("asc"); 160 | h.classList.remove("dsc"); 161 | }; 162 | header.classList.add("asc"); 163 | sortAsc(body, rows, indexes); 164 | }; 165 | }; 166 | }; 167 | }; 168 | 169 | 170 | if (document.readyState === "loading") { 171 | document.addEventListener("DOMContentLoaded", initDocmon); 172 | } else { 173 | initDocmon(); 174 | }; 175 | }; 176 | -------------------------------------------------------------------------------- /resources/enum.css: -------------------------------------------------------------------------------- 1 | main { 2 | display : grid; 3 | grid-gap : 0 var(--section-spacing); 4 | } 5 | main > header { grid-area : header } 6 | main > #summary { grid-area : summary } 7 | main > nav { grid-area : nav } 8 | main > #members-index { grid-area : index } 9 | main > #details { grid-area : details } 10 | main > #examples { grid-area : examples } 11 | main > #members { grid-area : members } 12 | main > #removed-members { grid-area : rmembers } 13 | main > #history { grid-area : history } 14 | main > #referrers { grid-area : referrers } 15 | 16 | main { 17 | grid-template-columns : auto; 18 | grid-template-areas : 19 | "header " 20 | "summary " 21 | "nav " 22 | "index " 23 | "details " 24 | "examples " 25 | "members " 26 | "rmembers " 27 | "history " 28 | "referrers" 29 | ; 30 | } 31 | main > nav > section { 32 | overflow-y : auto; 33 | padding-right : var(--scrollbar-width); 34 | max-height : 61.8vh; 35 | --indent : var(--indent-half); 36 | } 37 | main > nav .toc-members { 38 | display : none; 39 | } 40 | @media screen and (min-width: 800px) { 41 | main { 42 | grid-template-columns : 1fr auto; 43 | grid-template-areas : 44 | "header header" 45 | "summary nav " 46 | "index nav " 47 | "details nav " 48 | "examples nav " 49 | "history nav " 50 | "members nav " 51 | "rmembers nav " 52 | "referrers nav " 53 | ; 54 | justify-content : start; 55 | grid-template-rows : repeat(8,auto) 1fr; 56 | } 57 | main > nav { 58 | border-left : 1px solid var(--theme-border); 59 | padding-left : var(--section-spacing); 60 | } 61 | main > nav .toc-members { 62 | display : block; 63 | } 64 | @supports (position:sticky) { 65 | main > nav > section { 66 | position : sticky; 67 | top : 0; 68 | max-height : calc(100vh - var(--baseline) - var(--header-height)); 69 | } 70 | } 71 | } 72 | @media screen and (min-width: 1000px) { 73 | main:not(.descriptive) { 74 | grid-template-columns : auto 1fr auto; 75 | grid-template-areas : 76 | "header header header" 77 | "index summary nav " 78 | "index details nav " 79 | "index examples nav " 80 | "index history nav " 81 | "index members nav " 82 | "index rmembers nav " 83 | "index referrers nav " 84 | ; 85 | justify-content : start; 86 | grid-template-rows : repeat(7,auto) 1fr; 87 | } 88 | } 89 | 90 | #members > section, 91 | #removed-members > section { 92 | border-bottom : 1px solid var(--theme-border); 93 | } 94 | -------------------------------------------------------------------------------- /resources/enum.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function domLoaded() { 4 | // ToC 5 | rbxapiActions.QuickLink( 6 | "#toc-members", 7 | "#members-sections", 8 | ["HideIfZero", ">*"] 9 | ); 10 | rbxapiActions.QuickLink( 11 | "#toc-removed-members", 12 | "#removed-members-sections", 13 | ["HideIfZero", ">*"] 14 | ); 15 | rbxapiActions.QuickLink( 16 | "#toc-referrers", 17 | "#referrers > ul", 18 | ["HideIfZero", ">*"] 19 | ); 20 | 21 | // Sections 22 | rbxapiActions.QuickLink( 23 | "#removed-members-index", 24 | "#removed-members-index > .index-card > tbody:first-of-type", 25 | ["HideIfZero", ">:not(.empty)"] 26 | ); 27 | rbxapiActions.QuickLink( 28 | "#members", 29 | "#members-sections", 30 | ["HideIfZero", ">*"] 31 | ); 32 | rbxapiActions.QuickLink( 33 | "#removed-members", 34 | "#removed-members-sections", 35 | ["HideIfZero", ">*"] 36 | ); 37 | rbxapiActions.QuickLink( 38 | "#referrers", 39 | "#referrers > ul", 40 | ["HideIfZero", ">*"] 41 | ); 42 | 43 | // Counters 44 | function formatCount(c) { 45 | return "(" + c + ")"; 46 | }; 47 | rbxapiActions.QuickLink( 48 | "#members-index > header .element-count", 49 | "#members-index > .index-card > tbody:first-of-type", 50 | ["Count", ">:not(.empty)", formatCount] 51 | ); 52 | rbxapiActions.QuickLink( 53 | "#removed-members-index > header .element-count", 54 | "#removed-members-index > .index-card > tbody:first-of-type", 55 | ["Count", ">:not(.empty)", formatCount] 56 | ); 57 | rbxapiActions.QuickLink( 58 | "#referrers > header .element-count", 59 | "#referrers > ul", 60 | ["Count", ">*", formatCount] 61 | ); 62 | }; 63 | 64 | function actionsLoaded() { 65 | if (document.readyState === "loading") { 66 | window.addEventListener("DOMContentLoaded", domLoaded); 67 | } else { 68 | domLoaded(); 69 | }; 70 | }; 71 | 72 | if (window.rbxapiActions) { 73 | actionsLoaded(); 74 | } else { 75 | window.addEventListener("rbxapiActions", actionsLoaded); 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /resources/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /resources/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /resources/favicons/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/favicons/favicon-48x48.png -------------------------------------------------------------------------------- /resources/favicons/favicon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/favicons/favicon-512x512.png -------------------------------------------------------------------------------- /resources/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/favicons/favicon.ico -------------------------------------------------------------------------------- /resources/icon-devhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/icon-devhub.png -------------------------------------------------------------------------------- /resources/icon-objectbrowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/icon-objectbrowser.png -------------------------------------------------------------------------------- /resources/index.css: -------------------------------------------------------------------------------- 1 | #main-header { 2 | position : relative; 3 | z-index : var(--layer-foreground); 4 | } 5 | main { 6 | display : flex; 7 | flex-flow : wrap row; 8 | column-gap : var(--section-spacing); 9 | margin : var(--baseline); 10 | margin-top : 0; 11 | } 12 | 13 | #toc { 14 | position : fixed; 15 | position : sticky; 16 | top : 0; 17 | right : 0; 18 | z-index : var(--layer-below-foreground); 19 | pointer-events : none; 20 | margin : 0; 21 | } 22 | #toc > ul { 23 | display : flex; 24 | flex-flow : wrap row; 25 | justify-content : end; 26 | list-style-type : none; 27 | padding : 0; 28 | margin : 0; 29 | } 30 | #toc > ul > li { 31 | pointer-events : auto; 32 | } 33 | #toc .header-block { 34 | --block-color : var(--theme-header); 35 | --block-text-color : var(--theme-header-text); 36 | --block-color-dim : var(--theme-header-hover); 37 | --block-text-color-dim : var(--theme-header-text-hover); 38 | } 39 | @media screen and (min-width: 1200px) { 40 | #toc { 41 | display : none; 42 | } 43 | } 44 | 45 | /* Class sort controls */ 46 | .class-list-controls { 47 | display : flex; 48 | margin-top : var(--baseline); 49 | margin-bottom : var(--baseline); 50 | } 51 | .class-list-controls input { 52 | display : none; 53 | } 54 | .class-list-controls label { 55 | --border-size : 2px; 56 | flex-grow : 1; 57 | display : inline-block; 58 | cursor : pointer; 59 | background-color : var(--theme-radio); 60 | font-size : var(--font-size); 61 | line-height : var(--line-height); 62 | padding-top : calc(var(--baseline)/2); 63 | padding-bottom : calc(var(--baseline)/2 - var(--border-size)); 64 | border-bottom : var(--border-size) solid transparent; 65 | transition : border-bottom 100ms linear; 66 | text-align : center; 67 | } 68 | .class-list-controls input:checked + label { 69 | border-bottom-color : var(--theme-radio-decor); 70 | } 71 | #class-list ul { 72 | border-left : 1px dotted var(--theme-border); 73 | } 74 | 75 | @media screen and (max-width: 600px) { 76 | #toc { 77 | display: block; 78 | } 79 | main { 80 | display: block; 81 | } 82 | main > article > ul { 83 | padding-left : 0; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /resources/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function clearList(list) { 4 | while (list.lastChild) { 5 | list.removeChild(list.lastChild); 6 | }; 7 | }; 8 | 9 | function sortByTree(list, classes, parents) { 10 | clearList(list); 11 | for (let item of parents) { 12 | item[1].appendChild(item[0]); 13 | }; 14 | }; 15 | 16 | function sortByName(list, classes, parents) { 17 | clearList(list); 18 | for (let item of classes) { 19 | list.appendChild(item[0]); 20 | }; 21 | }; 22 | 23 | function initSortClasses() { 24 | let list = document.getElementById("class-list"); 25 | if (list === null) { 26 | return; 27 | }; 28 | let classes = []; 29 | let parents = []; 30 | for (let li of list.querySelectorAll("li")) { 31 | classes.push([li, li.querySelector(".element-link").text]); 32 | parents.push([li, li.parentNode]); 33 | }; 34 | classes.sort(function(a, b) { 35 | return a[1].localeCompare(b[1]); 36 | }); 37 | 38 | let methods = [ 39 | [sortByTree, "Tree", true], 40 | [sortByName, "Name"] 41 | ]; 42 | 43 | const storageKey = "ClassSort" 44 | let defaultSort = window.localStorage.getItem(storageKey); 45 | if (defaultSort !== null) { 46 | for (let method of methods) { 47 | if (method[1] == defaultSort) { 48 | for (let method of methods) { 49 | method[2] = method[1] == defaultSort; 50 | }; 51 | break; 52 | }; 53 | }; 54 | }; 55 | 56 | let controls = document.createElement("div"); 57 | controls.className = "class-list-controls"; 58 | list.insertAdjacentElement("beforebegin", controls); 59 | for (let method of methods) { 60 | let input = document.createElement("input"); 61 | input.type = "radio"; 62 | input.id = "class-sort-" + method[1]; 63 | input.name = "sort"; 64 | input.value = method[1]; 65 | input.checked = method[2]; 66 | controls.appendChild(input); 67 | let label = document.createElement("label"); 68 | label.htmlFor = input.id; 69 | label.appendChild(document.createTextNode(method[1])); 70 | controls.appendChild(label); 71 | let update = function(event) { 72 | method[0](list, classes, parents); 73 | window.localStorage.setItem(storageKey, method[1]); 74 | } 75 | input.addEventListener("click", update); 76 | if (method[2]) { 77 | update(); 78 | }; 79 | }; 80 | }; 81 | 82 | function domLoaded() { 83 | function formatCount(c) { 84 | return "(" + c + ")"; 85 | }; 86 | rbxapiActions.QuickLink( 87 | "#classes > header .element-count", 88 | "#class-list", 89 | ["Count", "li > .element-link", formatCount] 90 | ); 91 | rbxapiActions.QuickLink( 92 | "#removed-classes > header .element-count", 93 | "#removed-class-list", 94 | ["Count", ">*", formatCount] 95 | ); 96 | rbxapiActions.QuickLink( 97 | "#removed-classes", 98 | "#removed-class-list", 99 | ["HideIfZero", ">*"] 100 | ); 101 | rbxapiActions.QuickLink( 102 | "#enums > header .element-count", 103 | "#enum-list", 104 | ["Count", ">*", formatCount] 105 | ); 106 | rbxapiActions.QuickLink( 107 | "#removed-enums > header .element-count", 108 | "#removed-enum-list", 109 | ["Count", ">*", formatCount] 110 | ); 111 | rbxapiActions.QuickLink( 112 | "#removed-enums", 113 | "#removed-enum-list", 114 | ["HideIfZero", ">*"] 115 | ); 116 | rbxapiActions.QuickLink( 117 | "#removed-types > header .element-count", 118 | "#removed-type-list", 119 | ["Count", ">*", formatCount] 120 | ); 121 | rbxapiActions.QuickLink( 122 | "#removed-types", 123 | "#removed-type-list", 124 | ["HideIfZero", ">*"] 125 | ); 126 | 127 | initSortClasses(); 128 | }; 129 | 130 | function actionsLoaded() { 131 | if (document.readyState === "loading") { 132 | document.addEventListener("DOMContentLoaded", domLoaded); 133 | } else { 134 | domLoaded(); 135 | }; 136 | }; 137 | 138 | if (window.rbxapiActions) { 139 | actionsLoaded(); 140 | } else { 141 | window.addEventListener("rbxapiActions", actionsLoaded); 142 | }; 143 | }; 144 | -------------------------------------------------------------------------------- /resources/license-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobloxAPI/rbxapiref/cc166140251626dfbc2e5e5e264117878894a5c5/resources/license-badge.png -------------------------------------------------------------------------------- /resources/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const securityIdentities = [ 3 | "All", 4 | "Server", 5 | "CoreScript", 6 | "BuiltinPlugin", 7 | "Command", 8 | "Plugin", 9 | "Script", 10 | ]; 11 | const securityPermissions = new Map([ 12 | // ALL SVR CSC BPL CMD PLG SCR 13 | ["None" , [ 1 , 1 , 1 , 1 , 1 , 1 , 1 ]], 14 | ["RobloxPlaceSecurity" , [ 1 , 1 , 1 , 1 , 1 , 1 , 0 ]], 15 | ["PluginSecurity" , [ 1 , 1 , 1 , 1 , 1 , 1 , 0 ]], 16 | ["LocalUserSecurity" , [ 1 , 1 , 1 , 0 , 1 , 0 , 0 ]], 17 | ["RobloxScriptSecurity" , [ 1 , 1 , 1 , 1 , 0 , 0 , 0 ]], 18 | ["RobloxSecurity" , [ 1 , 1 , 0 , 0 , 0 , 0 , 0 ]], 19 | ["NotAccessibleSecurity" , [ 1 , 0 , 0 , 0 , 0 , 0 , 0 ]], 20 | ]); 21 | const securityContexts = Array.from(securityPermissions.keys()); 22 | function secIDHasContext(id, ctx) { 23 | if (ctx instanceof Array) { 24 | // Return true if any context returns true. 25 | for (let c of ctx) { 26 | if (secIDHasContext(id, c)) { 27 | return true; 28 | }; 29 | }; 30 | return false; 31 | } else if (typeof(ctx) === "number") { 32 | ctx = securityContexts[ctx]; 33 | if (!ctx) { 34 | return false; 35 | }; 36 | }; 37 | if (typeof(id) === "string") { 38 | id = securityIdentities.indexOf(id); 39 | if (id < 0) { 40 | return false; 41 | }; 42 | }; 43 | return securityPermissions.get(ctx)[id] === 1; 44 | }; 45 | { 46 | function initTopNav() { 47 | let topnav = document.getElementById("top-nav"); 48 | if (topnav === null) { 49 | return; 50 | }; 51 | function updateTopNav() { 52 | topnav.style.visibility = window.pageYOffset === 0 ? "hidden" : "visible"; 53 | }; 54 | window.addEventListener("scroll", updateTopNav); 55 | updateTopNav(); 56 | }; 57 | 58 | function initHistoryToggle() { 59 | function toggleAll(show) { 60 | for (let item of document.querySelectorAll("#history > .patch-list > li")) { 61 | let diffElement = item.attributes["diff-element"]; 62 | if (diffElement === undefined || diffElement.value === "Class" || diffElement.value === "Enum") { 63 | continue; 64 | }; 65 | if (show) { 66 | item.style.display = "none"; 67 | } else { 68 | item.style.display = ""; 69 | }; 70 | }; 71 | }; 72 | 73 | let controls = document.getElementById("history-controls"); 74 | if (controls !== null) { 75 | controls.insertAdjacentHTML("beforeend", ''); 76 | }; 77 | 78 | let hideMemberChanges = document.getElementById("hide-member-changes"); 79 | if (hideMemberChanges !== null) { 80 | hideMemberChanges.addEventListener("click", function(event) { 81 | toggleAll(event.target.checked, false); 82 | }); 83 | toggleAll(hideMemberChanges.checked, true); 84 | } else {; 85 | toggleAll(false, true); 86 | }; 87 | }; 88 | 89 | let settingsLoaded = false; 90 | function initSettings() { 91 | let head = document.head; 92 | 93 | window.rbxapiSettings.Listen("Theme", function(name, value, initial) { 94 | if (initial) { 95 | // Handled by quick-theme.js. 96 | return; 97 | }; 98 | document.documentElement.className = value; 99 | }); 100 | 101 | let showDeprecated = document.createElement("style"); 102 | showDeprecated.innerHTML = 103 | ".api-deprecated { display: none; }\n" + 104 | "#class-list .api-deprecated { display: unset; }\n" + 105 | "#class-list .api-deprecated > .element-link { display: none; }\n" + 106 | "#class-list .api-deprecated > ul { padding-left:0; border-left:none; }\n"; 107 | window.rbxapiSettings.Listen("ShowDeprecated", function(name, value, initial) { 108 | if (value) { 109 | showDeprecated.remove(); 110 | } else { 111 | head.appendChild(showDeprecated); 112 | }; 113 | rbxapiActions.UpdateAll(); 114 | }); 115 | 116 | let showNotBrowsable = document.createElement("style"); 117 | showNotBrowsable.innerHTML = 118 | ".api-not-browsable { display: none; }\n" + 119 | "#class-list .api-not-browsable { display: unset; }\n" + 120 | "#class-list .api-not-browsable > .element-link { display: none; }\n" + 121 | "#class-list .api-not-browsable > ul { padding-left:0; border-left:none; }\n"; 122 | window.rbxapiSettings.Listen("ShowNotBrowsable", function(name, value, initial) { 123 | if (value) { 124 | showNotBrowsable.remove(); 125 | } else { 126 | head.appendChild(showNotBrowsable); 127 | }; 128 | rbxapiActions.UpdateAll(); 129 | }); 130 | 131 | let showHidden = document.createElement("style"); 132 | showHidden.innerHTML = 133 | ".api-hidden { display: none; }\n" + 134 | "#class-list .api-hidden { display: unset; }\n" + 135 | "#class-list .api-hidden > .element-link { display: none; }\n" + 136 | "#class-list .api-hidden > ul { padding-left:0; border-left:none; }\n"; 137 | window.rbxapiSettings.Listen("ShowHidden", function(name, value, initial) { 138 | if (value) { 139 | showHidden.remove(); 140 | } else { 141 | head.appendChild(showHidden); 142 | }; 143 | rbxapiActions.UpdateAll(); 144 | }); 145 | 146 | let showRemoved = document.createElement("style"); 147 | showRemoved.innerHTML = 148 | ".api-removed { display: none; }\n" + 149 | "#class-list .api-removed { display: unset; }\n" + 150 | "#class-list .api-removed > .element-link { display: none; }\n" + 151 | "#class-list .api-removed > ul { padding-left:0; border-left:none; }\n"; 152 | window.rbxapiSettings.Listen("ShowRemoved", function(name, value, initial) { 153 | if (value) { 154 | showRemoved.remove(); 155 | } else { 156 | head.appendChild(showRemoved); 157 | }; 158 | rbxapiActions.UpdateAll(); 159 | }); 160 | 161 | let security = new Map(); 162 | for (let i = 0; i < securityIdentities.length; i++) { 163 | let content = ""; 164 | for (let primary of securityPermissions) { 165 | if (primary[1][i] !== 0) { 166 | continue; 167 | }; 168 | content += ".api-sec-" + primary[0]; 169 | for (let secondary of securityPermissions) { 170 | if (secondary[1][i] !== 1) { 171 | continue; 172 | }; 173 | content += ":not(.api-sec-" + secondary[0] + ")"; 174 | }; 175 | content += ",\n"; 176 | }; 177 | if (content === "") { 178 | continue; 179 | }; 180 | content = content.slice(0, -2) + " {\n\tdisplay: none;\n}\n"; 181 | let style = document.createElement("style"); 182 | style.innerHTML = content; 183 | security.set(securityIdentities[i], style); 184 | console.log("CHECK", securityIdentities[i]); 185 | console.log(content); 186 | console.log("---------------------------------------------------"); 187 | }; 188 | window.rbxapiSettings.Listen("SecurityIdentity", function(name, value, initial) { 189 | for (let entry of security) { 190 | if (value === entry[0]) { 191 | head.appendChild(entry[1]); 192 | } else { 193 | entry[1].remove(); 194 | }; 195 | }; 196 | rbxapiActions.UpdateAll(); 197 | }); 198 | 199 | settingsLoaded = true; 200 | window.dispatchEvent(new Event("settingsLoaded")); 201 | }; 202 | 203 | function initActions() { 204 | if (window.rbxapiSettings) { 205 | initSettings(); 206 | } else { 207 | window.addEventListener("rbxapiSettings", initSettings); 208 | }; 209 | }; 210 | 211 | function fixTarget() { 212 | if (document.readState !== "completed") { 213 | let targetID = document.location.hash.slice(1); 214 | if (targetID !== "") { 215 | let target = document.getElementById(targetID); 216 | if (target) { 217 | target.scrollIntoView(true); 218 | }; 219 | }; 220 | }; 221 | }; 222 | 223 | function initLoad() { 224 | if (settingsLoaded) { 225 | fixTarget(); 226 | } else { 227 | window.addEventListener("settingsLoaded", fixTarget); 228 | }; 229 | }; 230 | 231 | if (document.readyState === "loading") { 232 | document.addEventListener("DOMContentLoaded", function() { 233 | initTopNav(); 234 | initHistoryToggle(); 235 | }); 236 | } else { 237 | initTopNav(); 238 | initHistoryToggle(); 239 | }; 240 | 241 | if (window.rbxapiActions) { 242 | initActions(); 243 | } else { 244 | window.addEventListener("rbxapiActions", initActions); 245 | }; 246 | 247 | if (document.readyState === "completed") { 248 | initLoad(); 249 | } else { 250 | window.addEventListener("load", initLoad); 251 | }; 252 | 253 | }; 254 | -------------------------------------------------------------------------------- /resources/quick-theme.js: -------------------------------------------------------------------------------- 1 | document.documentElement.className=window.localStorage.getItem("Theme") -------------------------------------------------------------------------------- /resources/settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | const settings = [ 4 | { 5 | "name" : "Theme", 6 | "type" : "radio", 7 | "default" : "Light", 8 | "options" : [ 9 | {"text": "Light", "value": "Light"}, 10 | {"text": "Dark", "value": "Dark"}, 11 | ], 12 | }, 13 | { 14 | "name" : "SecurityIdentity", 15 | "type" : "select", 16 | "default" : "0", 17 | "text" : "Permission", 18 | "options" : [ 19 | {"value": "All" }, 20 | {"value": "Server" }, 21 | {"value": "CoreScript" }, 22 | {"value": "BuiltinPlugin"}, 23 | {"value": "Command" }, 24 | {"value": "Plugin" }, 25 | {"value": "Script" }, 26 | ], 27 | "migrate" : function(storage) { 28 | let map = new Map([ 29 | ["0", "All"], 30 | ["7", "Server"], 31 | ["4", "CoreScript"], 32 | ["5", "Command"], 33 | ["6", "Plugin"], 34 | ["3", "Plugin"], // RobloxScript 35 | ["2", "Script"], 36 | ]); 37 | let value = storage.getItem("SecurityIdentity"); 38 | value = map.get(value); 39 | if (value) { 40 | storage.setItem("SecurityIdentity", value); 41 | }; 42 | }, 43 | }, 44 | { 45 | "name" : "ExpandMembers", 46 | "type" : "checkbox", 47 | "default" : false, 48 | "text" : "Expand all members", 49 | }, 50 | { 51 | "name" : "ShowDeprecated", 52 | "type" : "checkbox", 53 | "default" : true, 54 | "text" : "Show deprecated", 55 | }, 56 | { 57 | "name" : "ShowNotBrowsable", 58 | "type" : "checkbox", 59 | "default" : true, 60 | "text" : "Show unbrowsable", 61 | }, 62 | { 63 | "name" : "ShowHidden", 64 | "type" : "checkbox", 65 | "default" : true, 66 | "text" : "Show hidden", 67 | }, 68 | { 69 | "name" : "ShowRemoved", 70 | "type" : "checkbox", 71 | "default" : true, 72 | "text" : "Show removed", 73 | } 74 | ]; 75 | 76 | function generateMenu(parent, settings, changed) { 77 | const idPrefix = "setting-"; 78 | for (let setting of settings) { 79 | let value = window.localStorage.getItem(setting.name); 80 | if (value === null) { 81 | value = setting.default; 82 | }; 83 | let section = document.createElement("div"); 84 | section.className = setting.type; 85 | if (setting.type === "checkbox") { 86 | value = value === true || value === "true"; 87 | let input = document.createElement("input"); 88 | input.type = "checkbox"; 89 | input.id = idPrefix + setting.name; 90 | input.name = setting.name; 91 | input.disabled = setting.disabled; 92 | input.defaultChecked = value; 93 | // Fires on toggle. 94 | input.addEventListener("change", function(event) { 95 | changed(event.target.name, event.target.checked, false); 96 | }); 97 | 98 | let label = document.createElement("label"); 99 | label.htmlFor = input.id; 100 | label.textContent = setting.text; 101 | 102 | section.appendChild(input); 103 | section.appendChild(label); 104 | } else if (setting.type === "radio") { 105 | for (let option of setting.options) { 106 | let input = document.createElement("input"); 107 | input.type = "radio"; 108 | input.id = idPrefix + setting.name + "-" + option.value; 109 | input.name = setting.name; 110 | input.value = option.value; 111 | input.disabled = setting.disabled || option.disabled; 112 | input.defaultChecked = value === option.value; 113 | // Fires on checked. 114 | input.addEventListener("change", function(event) { 115 | changed(event.target.name, event.target.value, false); 116 | }); 117 | 118 | let label = document.createElement("label"); 119 | label.htmlFor = input.id; 120 | label.textContent = option.text || option.value; 121 | 122 | section.appendChild(input); 123 | section.appendChild(label); 124 | }; 125 | } else if (setting.type === "select") { 126 | let select = document.createElement("select"); 127 | select.id = idPrefix + setting.name; 128 | select.disabled = setting.disabled; 129 | for (let option of setting.options) { 130 | let element = document.createElement("option"); 131 | element.value = option.value; 132 | element.text = option.text || option.value; 133 | element.disabled = setting.disabled || option.disabled; 134 | element.defaultSelected = value === option.value; 135 | select.appendChild(element); 136 | }; 137 | // Fires on select. 138 | select.addEventListener("change", function(event) { 139 | // Unknown support for HTMLSelectElement.name. 140 | changed(setting.name, event.target.value, false); 141 | }); 142 | 143 | let label = document.createElement("label"); 144 | label.htmlFor = select.id; 145 | label.textContent = setting.text; 146 | 147 | section.appendChild(select); 148 | section.appendChild(label); 149 | }; 150 | parent.appendChild(section); 151 | }; 152 | }; 153 | 154 | class Settings { 155 | constructor() { 156 | this.settings = new Map(); 157 | }; 158 | Listen(name, listener) { 159 | let setting = this.settings.get(name); 160 | if (setting === undefined) { 161 | throw "unknown setting " + name; 162 | }; 163 | if (typeof(listener) !== "function") { 164 | throw "listener must be a function"; 165 | }; 166 | setting.listeners.push(listener); 167 | 168 | let value = window.localStorage.getItem(name); 169 | if (value === null) { 170 | value = setting.config.default; 171 | }; 172 | if (setting.config.type === "checkbox") { 173 | value = value === true || value === "true"; 174 | }; 175 | listener(name, value, true); 176 | }; 177 | Changed(name, value, initial) { 178 | let setting = this.settings.get(name); 179 | if (setting === undefined) { 180 | return; 181 | }; 182 | window.localStorage.setItem(name, value); 183 | for (let listener of setting.listeners) { 184 | listener(name, value, initial); 185 | }; 186 | }; 187 | } 188 | 189 | function initSettingsMenu() { 190 | let container = document.getElementById("main-header-right"); 191 | if (container === null) { 192 | return; 193 | }; 194 | container.insertAdjacentHTML('beforeend', '
'); 195 | container.insertAdjacentHTML('beforeend', ''); 196 | 197 | let button = document.getElementById("settings-button"); 198 | if (button === null) { 199 | return; 200 | }; 201 | let menu = document.getElementById("settings-menu"); 202 | if (menu === null) { 203 | return; 204 | }; 205 | 206 | generateMenu(menu, settings, function(name, value, initial) { 207 | rbxapiSettings.Changed(name, value, initial) 208 | }); 209 | 210 | button.addEventListener("click", function(event) { 211 | menu.style.display = "block"; 212 | const onClick = function(event) { 213 | if (!menu.contains(event.target) && menu.style.display !== "none") { 214 | menu.style.display = "none"; 215 | document.removeEventListener("click", onClick, true); 216 | event.preventDefault(); 217 | event.stopPropagation(); 218 | }; 219 | }; 220 | document.addEventListener("click", onClick, true); 221 | event.stopPropagation(); 222 | }); 223 | }; 224 | 225 | let rbxapiSettings = new Settings() 226 | for (let setting of settings) { 227 | rbxapiSettings.settings.set(setting.name, { 228 | "config": setting, 229 | "listeners": [], 230 | }); 231 | if (setting.migrate) { 232 | setting.migrate(window.localStorage); 233 | }; 234 | if (setting.disabled) { 235 | continue; 236 | }; 237 | if (window.localStorage.getItem(setting.name) === null) { 238 | window.localStorage.setItem(setting.name, setting.default); 239 | }; 240 | }; 241 | window.rbxapiSettings = rbxapiSettings; 242 | window.dispatchEvent(new Event("rbxapiSettings")); 243 | 244 | if (document.readyState === "loading") { 245 | document.addEventListener("DOMContentLoaded", initSettingsMenu); 246 | } else { 247 | initSettingsMenu(); 248 | }; 249 | }; 250 | -------------------------------------------------------------------------------- /resources/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/theme-dark.css: -------------------------------------------------------------------------------- 1 | /* Dark theme */ 2 | :root.Dark { 3 | --theme-sky : #010308; 4 | --theme-background : #111111; 5 | --theme-text : #8F8F8F; 6 | --theme-text-decor : rgb(143, 143, 143, 0.5); 7 | --theme-link : #8E9FC2; 8 | --theme-border : #404040; 9 | --theme-highlight : #FFB700; 10 | --theme-highlight-text : var(--theme-background); 11 | --theme-selection : #11A5D6; 12 | --theme-selection-text : #000000; 13 | --theme-scroll-track : var(--theme-table); 14 | --theme-scroll-thumb : var(--theme-border); 15 | --theme-symbol : #666666; 16 | --theme-symbol-hover : var(--theme-highlight); 17 | --theme-table : #242424; 18 | --theme-table-text : #A2A2A2; 19 | --theme-table-header : var(--theme-border); 20 | --theme-table-header-text : #BEBEBE; 21 | --theme-table-border : var(--theme-border); 22 | --theme-table-highlight : var(--theme-highlight); 23 | --theme-table-highlight-text : var(--theme-highlight-text); 24 | --theme-menu : var(--theme-table); 25 | --theme-field : var(--theme-background); 26 | --theme-header : var(--theme-table); 27 | --theme-header-text : var(--theme-text); 28 | --theme-header-hover : var(--theme-highlight); 29 | --theme-header-text-hover : var(--theme-highlight-text); 30 | --theme-footer : var(--theme-header); 31 | --theme-footer-text : var(--theme-header-text); 32 | --theme-footer-hover : var(--theme-header-hover); 33 | --theme-footer-text-hover : var(--theme-header-text-hover); 34 | --theme-status : var(--theme-table); 35 | --theme-status-text : var(--theme-text); 36 | --theme-status-border : var(--theme-border); 37 | --theme-status-high : #F71818; 38 | --theme-status-medium : #E89619; 39 | --theme-status-low : #11A5D6; 40 | --theme-patch-add : #27581E; 41 | --theme-patch-add-text : #BDFFB1; 42 | --theme-patch-change : #825500; 43 | --theme-patch-change-text : #FFE7BA; 44 | --theme-patch-remove : #731212; 45 | --theme-patch-remove-text : #FFB9B9; 46 | --theme-radio : var(--theme-table); 47 | --theme-radio-decor : var(--theme-highlight); 48 | 49 | --syntax-background : #252525; 50 | --syntax-line-highlight : #555555; 51 | --syntax-line-numbers-table : #B4B4B4; 52 | --syntax-line-numbers : #B4B4B4; 53 | --syntax-error : #FF0000; 54 | --syntax-keyword : #F86D7C; 55 | --syntax-name : #CCCCCC; 56 | --syntax-literal-string : #ADF195; 57 | --syntax-literal-number : #FFC600; 58 | --syntax-operator : #CCCCCC; 59 | --syntax-operator-word : #F86D7C; 60 | --syntax-punctuation : #CCCCCC; 61 | --syntax-comment : #666666; 62 | --syntax-comment-preproc : #66FFCC; 63 | } 64 | -------------------------------------------------------------------------------- /resources/theme-light.css: -------------------------------------------------------------------------------- 1 | /* Light theme */ 2 | :root, :root.Light { 3 | --theme-sky : #1F3F8E; 4 | --theme-background : #FFFFFF; 5 | --theme-text : #333333; 6 | --theme-text-decor : rgb(51, 51, 51, 0.5); 7 | --theme-link : #3E6DCC; 8 | --theme-border : #CCCCCC; 9 | --theme-highlight : #FFD700; 10 | --theme-highlight-text : var(--theme-text); 11 | --theme-selection : #82CBFF; 12 | --theme-selection-text : #000000; 13 | --theme-scroll-track : var(--theme-table); 14 | --theme-scroll-thumb : var(--theme-border); 15 | --theme-symbol : #AAAAAA; 16 | --theme-symbol-hover : #555555; 17 | --theme-table : #F4F4F4; 18 | --theme-table-text : var(--theme-text); 19 | --theme-table-header : var(--theme-border); 20 | --theme-table-header-text : var(--theme-text); 21 | --theme-table-border : var(--theme-border); 22 | --theme-table-highlight : var(--theme-highlight); 23 | --theme-table-highlight-text : var(--theme-highlight-text); 24 | --theme-menu : var(--theme-table); 25 | --theme-field : var(--theme-background); 26 | --theme-header : var(--theme-table); 27 | --theme-header-text : var(--theme-link); 28 | --theme-header-hover : #DCDCDC; 29 | --theme-header-text-hover : var(--theme-link); 30 | --theme-footer : var(--theme-sky); 31 | --theme-footer-text : var(--theme-background); 32 | --theme-footer-hover : #193272; 33 | --theme-footer-text-hover : var(--theme-footer-text); 34 | --theme-status : var(--theme-table); 35 | --theme-status-text : var(--theme-text); 36 | --theme-status-border : var(--theme-border); 37 | --theme-status-high : #FF7E7E; 38 | --theme-status-medium : #F2B729; 39 | --theme-status-low : #77D7F6; 40 | --theme-patch-add : #7EBA72; 41 | --theme-patch-add-text : #143E0C; 42 | --theme-patch-change : #DFCE88; 43 | --theme-patch-change-text : #543B00; 44 | --theme-patch-remove : #D8848C; 45 | --theme-patch-remove-text : #670E17; 46 | --theme-radio : var(--theme-table); 47 | --theme-radio-decor : var(--theme-border); 48 | 49 | --syntax-background : #FFFFFF; 50 | --syntax-line-highlight : #E2E6D6; 51 | --syntax-line-numbers-table : #7F7F7F; 52 | --syntax-line-numbers : #7F7F7F; 53 | --syntax-error : #FF0000; 54 | --syntax-keyword : #00007F; 55 | --syntax-name : #000000; 56 | --syntax-literal-string : #7F007F; 57 | --syntax-literal-number : #007F7F; 58 | --syntax-operator : #7F7F00; 59 | --syntax-operator-word : #00007F; 60 | --syntax-punctuation : #7F7F00; 61 | --syntax-comment : #007F00; 62 | --syntax-comment-preproc : #7F0000; 63 | } 64 | -------------------------------------------------------------------------------- /resources/type.css: -------------------------------------------------------------------------------- 1 | main { 2 | display : grid; 3 | grid-gap : 0 var(--section-spacing); 4 | } 5 | main > header { grid-area : header } 6 | main > #summary { grid-area : summary } 7 | main > nav { grid-area : nav } 8 | main > #details { grid-area : details } 9 | main > #constructors { grid-area : constructors } 10 | main > #fields { grid-area : fields } 11 | main > #methods { grid-area : methods } 12 | main > #operators { grid-area : operators } 13 | main > #examples { grid-area : examples } 14 | main > #referrers { grid-area : referrers } 15 | 16 | main { 17 | grid-template-columns : auto; 18 | grid-template-areas : 19 | "header " 20 | "summary " 21 | "nav " 22 | "details " 23 | "constructors" 24 | "fields " 25 | "methods " 26 | "operators " 27 | "examples " 28 | "referrers " 29 | ; 30 | } 31 | main > nav > section { 32 | overflow-y : auto; 33 | padding-right : var(--scrollbar-width); 34 | max-height : 61.8vh; 35 | --indent : var(--indent-half); 36 | } 37 | main > nav .toc-members { 38 | display : none; 39 | } 40 | @media screen and (min-width: 800px) { 41 | main { 42 | grid-template-columns : 1fr auto; 43 | grid-template-areas : 44 | "header header" 45 | "summary nav " 46 | "details nav " 47 | "constructors nav " 48 | "fields nav " 49 | "methods nav " 50 | "operators nav " 51 | "examples nav " 52 | "referrers nav " 53 | ; 54 | justify-content : start; 55 | grid-template-rows : repeat(4,auto) 1fr; 56 | } 57 | main > nav { 58 | border-left : 1px solid var(--theme-border); 59 | padding-left : var(--section-spacing); 60 | } 61 | main > nav .toc-members { 62 | display : block; 63 | } 64 | @supports (position:sticky) { 65 | main > nav > section { 66 | position : sticky; 67 | top : 0; 68 | max-height : calc(100vh - var(--baseline) - var(--header-height)); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /resources/type.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function domLoaded() { 4 | // ToC 5 | rbxapiActions.QuickLink( 6 | "#toc-referrers", 7 | "#referrers > ul", 8 | ["HideIfZero", ">*"] 9 | ); 10 | 11 | // Sections 12 | rbxapiActions.QuickLink( 13 | "#referrers", 14 | "#referrers > ul", 15 | ["HideIfZero", ">*"] 16 | ); 17 | 18 | // Counters 19 | function formatCount(c) { 20 | return "(" + c + ")"; 21 | }; 22 | rbxapiActions.QuickLink( 23 | "#referrers > header .element-count", 24 | "#referrers > ul", 25 | ["Count", ">*", formatCount] 26 | ); 27 | }; 28 | 29 | function actionsLoaded() { 30 | if (document.readyState === "loading") { 31 | window.addEventListener("DOMContentLoaded", domLoaded); 32 | } else { 33 | domLoaded(); 34 | }; 35 | }; 36 | 37 | if (window.rbxapiActions) { 38 | actionsLoaded(); 39 | } else { 40 | window.addEventListener("rbxapiActions", actionsLoaded); 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /resources/updates.css: -------------------------------------------------------------------------------- 1 | main { 2 | display : grid; 3 | justify-items : start; 4 | grid-gap : 0 var(--section-spacing); 5 | } 6 | main > header { 7 | grid-area : header; 8 | align-self : baseline; 9 | } 10 | #update-controls { 11 | grid-area : controls; 12 | justify-self : end; 13 | align-self : baseline; 14 | } 15 | #update-controls label { 16 | display : flex; 17 | } 18 | main > nav { 19 | grid-area : nav; 20 | } 21 | main > article { 22 | grid-area : content; 23 | } 24 | 25 | @media screen and (max-width: 600px) { 26 | main { 27 | grid-template-columns : auto auto; 28 | grid-template-areas : 29 | "header controls" 30 | "nav controls" 31 | "content content " 32 | ; 33 | } 34 | } 35 | @media screen and (min-width: 600px) { 36 | main { 37 | grid-template-columns : min-content auto auto; 38 | grid-template-areas : 39 | "header header controls" 40 | "nav content content " 41 | ; 42 | } 43 | main > nav { 44 | border-right : 1px solid var(--theme-border); 45 | padding-right : var(--section-spacing); 46 | } 47 | } 48 | 49 | .update:target > .patch-list-toggle, 50 | .patch-list > li:target { 51 | background-color : var(--theme-highlight); 52 | color : var(--theme-highlight-text); 53 | } 54 | a.permalink { 55 | display : none; 56 | } 57 | *:hover > a.permalink { 58 | display : inline; 59 | } 60 | *:hover > a.permalink:before { 61 | content : ' ¶ '; 62 | } 63 | 64 | @media screen and (max-width: 600px) { 65 | a.permalink > span { 66 | display : none; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/updates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | { 3 | function toggleList(event) { 4 | let parent = event.target.closest(".update"); 5 | if (parent === null) { 6 | return; 7 | }; 8 | let list = parent.querySelector(".patch-list"); 9 | if (list === null) { 10 | return; 11 | }; 12 | if (list.style.display === "none") { 13 | list.style.display = ""; 14 | } else { 15 | list.style.display = "none"; 16 | }; 17 | }; 18 | 19 | function toggleAll(show, scroll) { 20 | let scrollTo; 21 | for (let item of document.querySelectorAll("#update-list > li .patch-list")) { 22 | let anchor = item.parentElement.querySelector(":target"); 23 | if (anchor !== null) { 24 | scrollTo = anchor; 25 | } 26 | if (show) { 27 | item.style.display = ""; 28 | } else { 29 | if (anchor !== null) { 30 | item.style.display = ""; 31 | } else { 32 | item.style.display = "none"; 33 | }; 34 | }; 35 | }; 36 | if (scroll && scrollTo !== undefined) { 37 | scrollTo.scrollIntoView(true); 38 | }; 39 | }; 40 | 41 | function initUpdates() { 42 | // Inject pointer style. 43 | function initStyle() { 44 | let style = document.getElementById("updates-style"); 45 | if (style !== null) { 46 | try { 47 | style.sheet.insertRule(".patch-list-toggle {cursor: pointer;}"); 48 | } catch (error) { 49 | }; 50 | }; 51 | }; 52 | if (document.readyState === "complete") { 53 | initStyle(); 54 | } else { 55 | window.addEventListener("load", initStyle); 56 | }; 57 | 58 | // Insert update controls. 59 | let controls = document.getElementById("update-controls"); 60 | if (controls !== null) { 61 | controls.insertAdjacentHTML("beforeend", ''); 62 | }; 63 | 64 | // Init visibility toggle. 65 | for (let item of document.querySelectorAll("#update-list > li .patch-list-toggle")) { 66 | item.addEventListener("click", toggleList); 67 | }; 68 | 69 | // Insert instructions. 70 | let list = document.getElementById("update-list"); 71 | if (list !== null) { 72 | let note = document.createElement("div"); 73 | note.innerText = "Click a date to expand or collapse changes."; 74 | list.parentElement.insertBefore(note, list); 75 | }; 76 | 77 | // Init expand-all control. 78 | let expandAll = document.getElementById("expand-all"); 79 | if (expandAll !== null) { 80 | expandAll.addEventListener("click", function(event) { 81 | toggleAll(event.target.checked, false); 82 | }); 83 | toggleAll(expandAll.checked, true); 84 | } else {; 85 | toggleAll(false, true); 86 | }; 87 | 88 | // Scroll to targeted patch item. 89 | let targetID = document.location.hash.slice(1); 90 | if (targetID !== "") { 91 | let target = document.getElementById(targetID); 92 | if (target) { 93 | if (target.parentElement.matches(".patch-list")) { 94 | target.parentElement.style.display = ""; 95 | // TODO: The browser should automatically scroll to the target 96 | // at some point, but this might race. 97 | 98 | // Enabling scrollIntoView cancels the automatic scroll by the 99 | // browser, but then misses the target. Probably because the 100 | // scroll position is set before the list expansion is rendered. 101 | 102 | // target.scrollIntoView(true); 103 | return; 104 | }; 105 | let list = target.querySelector(".patch-list") 106 | if (list) { 107 | list.style.display = ""; 108 | return; 109 | }; 110 | }; 111 | }; 112 | 113 | // No specific update is being targeted; expand latest updates. 114 | for (let update of document.querySelectorAll("#update-list .update")) { 115 | let list = update.querySelector(".patch-list"); 116 | if (list === null) { 117 | continue; 118 | }; 119 | list.style.display = ""; 120 | // Expand up to first non-empty update. 121 | if (list.querySelector(".no-changes") === null) { 122 | break; 123 | }; 124 | }; 125 | }; 126 | 127 | if (document.readyState === "loading") { 128 | document.addEventListener("DOMContentLoaded", initUpdates); 129 | } else { 130 | initUpdates(); 131 | }; 132 | }; 133 | -------------------------------------------------------------------------------- /settings/const.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/alecthomas/chroma" 7 | "github.com/alecthomas/chroma/styles" 8 | "github.com/robloxapi/rbxapiref/builds" 9 | "github.com/robloxapi/rbxapiref/fetch" 10 | ) 11 | 12 | const ( 13 | ToolName = "rbxapiref" 14 | FileName = "settings.json" 15 | ) 16 | 17 | const ( 18 | ArchiveURL = "https://raw.githubusercontent.com/RobloxAPI/archive/master/" 19 | CDNURL = "https://setup.rbxcdn.com/" 20 | DevHubURL = "developer.roblox.com/api-reference" 21 | ) 22 | 23 | const ( 24 | ClassPath = "class" 25 | EnumPath = "enum" 26 | TypePath = "type" 27 | FileExt = ".html" 28 | MemberAnchorPrefix = "member-" 29 | SectionAnchorPrefix = "section-" 30 | MainTitle = "Roblox API Reference" 31 | TitleSep = "-" 32 | ) 33 | 34 | func mustParseURL(rawurl string) url.URL { 35 | u, err := url.Parse(rawurl) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return *u 40 | } 41 | 42 | var Default = &Settings{ 43 | Input: Input{ 44 | Resources: "resources", 45 | Templates: "templates", 46 | }, 47 | Output: Output{ 48 | Root: ".", 49 | Sub: "ref", 50 | Resources: "res", 51 | DocResources: "docres", 52 | Manifest: "manifest", 53 | }, 54 | Build: builds.Settings{ 55 | Configs: map[string]fetch.Config{ 56 | "Archive": { 57 | Builds: fetch.NewLocations(ArchiveURL + "builds.json"), 58 | Latest: fetch.NewLocations(ArchiveURL + "latest.json"), 59 | APIDump: fetch.NewLocations(ArchiveURL + "data/api-dump/json/$HASH.json"), 60 | ReflectionMetadata: fetch.NewLocations(ArchiveURL + "data/reflection-metadata/xml/$HASH.xml"), 61 | ExplorerIcons: fetch.NewLocations( 62 | CDNURL+"$HASH-content-textures2.zip#ClassImages.PNG", 63 | CDNURL+"$HASH-RobloxStudio.zip#RobloxStudioBeta.exe", 64 | ), 65 | }, 66 | "Production": { 67 | Builds: fetch.NewLocations(CDNURL + "DeployHistory.txt"), 68 | Latest: fetch.NewLocations(CDNURL + "versionQTStudio"), 69 | APIDump: fetch.NewLocations(CDNURL + "$HASH-API-Dump.json"), 70 | ReflectionMetadata: fetch.NewLocations(CDNURL + "$HASH-RobloxStudio.zip#ReflectionMetadata.xml"), 71 | ExplorerIcons: fetch.NewLocations( 72 | CDNURL+"$HASH-content-textures2.zip#ClassImages.PNG", 73 | CDNURL+"$HASH-RobloxStudio.zip#RobloxStudioBeta.exe", 74 | ), 75 | Live: []fetch.Location{ 76 | fetch.Location{ 77 | Format: ".json", 78 | URL: mustParseURL("https://versioncompatibility.api.roblox.com/GetCurrentClientVersionUpload/?apiKey=76e5a40c-3ae1-4028-9f10-7c62520bd94f&binaryType=WindowsStudio"), 79 | }, 80 | fetch.Location{ 81 | Format: ".json", 82 | URL: mustParseURL("https://versioncompatibility.api.roblox.com/GetCurrentClientVersionUpload/?apiKey=76e5a40c-3ae1-4028-9f10-7c62520bd94f&binaryType=WindowsStudio64"), 83 | }, 84 | }, 85 | }, 86 | }, 87 | UseConfigs: []string{ 88 | "Archive", 89 | "Production", 90 | }, 91 | }, 92 | } 93 | 94 | // Roblox studio light colors: 95 | // Background Color #FFFFFF 96 | // Built-in Function Color #00007F 97 | // Comment Color #007F00 98 | // Error Color #FF0000 99 | // Find Selection Background Color #F6B93F 100 | // Keyword Color #00007F 101 | // Matching Word Background Color #E2E6D6 102 | // Number Color #007F7F 103 | // Operator Color #7F7F00 104 | // Preprocessor Color #7F0000 105 | // Selection Background Color #6EA1F1 106 | // Selection Color #FFFFFF 107 | // String Color #7F007F 108 | // Text Color #000000 109 | // Warning Color #0000FF 110 | 111 | // StyleRobloxLight is a light theme for code syntax highlighting. 112 | var StyleRobloxLight = styles.Register(chroma.MustNewStyle("roblox-light", chroma.StyleEntries{ 113 | chroma.Background: "bg:#FFFFFF", 114 | chroma.LineHighlight: "bg:#E2E6D6", 115 | chroma.LineNumbersTable: "#7F7F7F", 116 | chroma.LineNumbers: "#7F7F7F", 117 | chroma.Error: "#FF0000", 118 | chroma.Keyword: "#00007F bold", 119 | chroma.Name: "#000000", 120 | chroma.LiteralString: "#7F007F", 121 | chroma.LiteralNumber: "#007F7F", 122 | chroma.Operator: "#7F7F00", 123 | chroma.OperatorWord: "#00007F bold", 124 | chroma.Punctuation: "#7F7F00", 125 | chroma.Comment: "#007F00", 126 | chroma.CommentPreproc: "#7F0000", 127 | })) 128 | 129 | // Roblox studio dark colors: 130 | // Background Color #252525 131 | // Built-in Function Color #84D6F7 132 | // Comment Color #666666 133 | // Error Color #FF0000 134 | // Find Selection Background Color #FFF550 135 | // Keyword Color #F86D7C 136 | // Matching Word Background Color #555555 137 | // Number Color #FFC600 138 | // Operator Color #CCCCCC 139 | // Preprocessor Color #66FFCC 140 | // Selection Background Color #2A2A2A 141 | // Selection Color #999999 142 | // String Color #ADF195 143 | // Text Color #CCCCCC 144 | // Warning Color #FF7315 145 | 146 | // StyleRobloxDark is a dark theme for code syntax highlighting. 147 | var StyleRobloxDark = styles.Register(chroma.MustNewStyle("roblox-dark", chroma.StyleEntries{ 148 | chroma.Background: "bg:#252525", 149 | chroma.LineHighlight: "bg:#555555", 150 | chroma.LineNumbersTable: "#B4B4B4", 151 | chroma.LineNumbers: "#B4B4B4", 152 | chroma.Error: "#FF0000", 153 | chroma.Keyword: "#F86D7C bold", 154 | chroma.Name: "#CCCCCC", 155 | chroma.LiteralString: "#ADF195", 156 | chroma.LiteralNumber: "#FFC600", 157 | chroma.Operator: "#CCCCCC", 158 | chroma.OperatorWord: "#F86D7C bold", 159 | chroma.Punctuation: "#CCCCCC", 160 | chroma.Comment: "#666666", 161 | chroma.CommentPreproc: "#66FFCC", 162 | })) 163 | -------------------------------------------------------------------------------- /settings/output.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | "path/filepath" 7 | "strings" 8 | "unicode" 9 | ) 10 | 11 | type Output struct { 12 | // Root is the directory to which generated files will be written. 13 | Root string 14 | // Sub is a path that follows the output directory and precedes a 15 | // generated file path. 16 | Sub string 17 | // Resources is the path relative to Sub where generated resource files 18 | // will be written. 19 | Resources string 20 | // DocResources is the path relative to Sub where document resource files 21 | // will be written. 22 | DocResources string 23 | // Manifest is the path relative to Sub that points to the manifest file. 24 | Manifest string 25 | // Host is the host part of the absolute URL of the site. 26 | Host string 27 | } 28 | 29 | // Escape once to escape the file name, then again to escape the URL. 30 | func doubleEscape(s string) string { 31 | return url.PathEscape(url.PathEscape(s)) 32 | } 33 | 34 | func pathText(text string) string { 35 | var s []rune 36 | var dash bool 37 | for _, r := range text { 38 | if unicode.IsLetter(r) || unicode.IsDigit(r) { 39 | s = append(s, r) 40 | dash = false 41 | } else if !dash && unicode.IsSpace(r) { 42 | s = append(s, '-') 43 | dash = true 44 | } 45 | } 46 | return string(s) 47 | } 48 | 49 | func anchorText(text string) string { 50 | var s []rune 51 | var dash bool 52 | for _, r := range text { 53 | if unicode.IsLetter(r) || unicode.IsDigit(r) { 54 | s = append(s, unicode.ToLower(r)) 55 | dash = false 56 | } else if !dash { 57 | s = append(s, '-') 58 | dash = true 59 | } 60 | } 61 | return string(s) 62 | } 63 | 64 | // FileLink generates a URL, relative to an arbitrary host. 65 | func (o *Output) FileLink(linkType string, args ...string) (s string) { 66 | retry: 67 | switch strings.ToLower(linkType) { 68 | case "index": 69 | s = "index" + FileExt 70 | case "resource": 71 | s = path.Join(o.Resources, path.Join(args...)) 72 | case "docres": 73 | s = path.Join(o.DocResources, path.Join(args...)) 74 | case "updates": 75 | if len(args) > 0 { 76 | s = path.Join("updates", doubleEscape(args[0])+FileExt) 77 | } else { 78 | s = "updates" + FileExt 79 | } 80 | case "class": 81 | s = path.Join(ClassPath, doubleEscape(args[0])+FileExt) 82 | case "member": 83 | if len(args) == 1 { 84 | return (&url.URL{Fragment: MemberAnchorPrefix + args[0]}).String() 85 | } else if len(args) == 2 { 86 | s = path.Join(ClassPath, doubleEscape(args[0])+FileExt) + 87 | (&url.URL{Fragment: MemberAnchorPrefix + args[1]}).String() 88 | } 89 | case "enum": 90 | s = path.Join(EnumPath, doubleEscape(args[0])+FileExt) 91 | case "enumitem": 92 | if len(args) == 1 { 93 | return (&url.URL{Fragment: MemberAnchorPrefix + args[0]}).String() 94 | } else if len(args) == 2 { 95 | s = path.Join(EnumPath, doubleEscape(args[0])+FileExt) + 96 | (&url.URL{Fragment: MemberAnchorPrefix + args[1]}).String() 97 | } 98 | case "type": 99 | if len(args) == 1 { 100 | s = path.Join(TypePath, doubleEscape(args[0])+FileExt) 101 | } else if len(args) == 2 { 102 | switch strings.ToLower(args[0]) { 103 | case "class", "enum": 104 | a := make([]string, 2) 105 | linkType, a[0] = args[0], args[1] 106 | args = a 107 | goto retry 108 | } 109 | s = path.Join(TypePath, doubleEscape(args[1])+FileExt) 110 | } 111 | case "about": 112 | s = "about" + FileExt 113 | case "docmon": 114 | s = "docmon" + FileExt 115 | case "search": 116 | s = "search.db" 117 | case "manifest": 118 | s = o.Manifest 119 | case "devhub": 120 | switch linkType = strings.ToLower(args[0]); linkType { 121 | case "class", "enum": 122 | return "https://" + path.Join(DevHubURL, linkType, pathText(args[1])) 123 | case "property", "function", "event", "callback": 124 | return "https://" + path.Join(DevHubURL, linkType, pathText(args[1]), pathText(args[2])) 125 | case "enumitem": 126 | return "https://" + path.Join(DevHubURL, "enum", pathText(args[1])) + "#" + anchorText(args[2]) 127 | case "type": 128 | return "https://" + path.Join(DevHubURL, "datatype", pathText(args[1])) 129 | } 130 | } 131 | s = path.Join("/", o.Sub, s) 132 | return s 133 | } 134 | 135 | // FilePath generates a file path relative to the output root directory. On a 136 | // web server serving static files, the returned path is meant to point to the 137 | // same file as the file pointed to by the URL generated by FileLink. 138 | func (o *Output) FilePath(typ string, args ...string) string { 139 | return o.PathFromLink(o.FileLink(typ, args...)) 140 | } 141 | 142 | // AbsFilePath generates an absolute path located in the Output. On a web 143 | // server serving static files, the returned path is meant to point to the 144 | // same file as the file pointed to by the URL generated by FileLink. 145 | func (o *Output) AbsFilePath(typ string, args ...string) string { 146 | return o.AbsPathFromLink(o.FileLink(typ, args...)) 147 | } 148 | 149 | // LinkFromPath transforms a path into a link, if possible. 150 | func (o *Output) LinkFromPath(p string) string { 151 | if l, err := filepath.Rel(o.Root, p); err == nil { 152 | return path.Clean(l) 153 | } 154 | return path.Clean(p) 155 | } 156 | 157 | // PathFromLink transforms a link into a path, if possible. 158 | func (o *Output) PathFromLink(l string) string { 159 | l, _ = url.PathUnescape(l) 160 | l = strings.TrimPrefix(l, "/") 161 | return filepath.Clean(l) 162 | } 163 | 164 | // AbsPathFromLink transforms a link into an absolute path, if possible. 165 | func (o *Output) AbsPathFromLink(l string) string { 166 | l, _ = url.PathUnescape(l) 167 | l = strings.TrimPrefix(l, "/") 168 | return o.AbsPath(l) 169 | } 170 | 171 | // AbsPath transforms a relative path into an absolute path. 172 | func (o *Output) AbsPath(p string) string { 173 | if filepath.IsAbs(p) { 174 | return p 175 | } 176 | return filepath.Join(o.Root, p) 177 | } 178 | 179 | func unescapeURLPath(path string) string { 180 | p, err := url.PathUnescape(path) 181 | if err != nil { 182 | return path 183 | } 184 | return p 185 | } 186 | 187 | func (o *Output) ParseDocReference(ref string) (scheme, path, link string) { 188 | colon := strings.IndexByte(ref, ':') 189 | if colon < 0 { 190 | return "", "", ref 191 | } 192 | switch scheme, path = ref[:colon], ref[colon+1:]; scheme { 193 | case "res": 194 | link = o.FileLink("docres", path) 195 | return 196 | case "class": 197 | slash := strings.IndexByte(path, '/') 198 | if slash < 0 { 199 | link = o.FileLink("class", unescapeURLPath(path)) 200 | return 201 | } 202 | link = o.FileLink("member", unescapeURLPath(path[:slash]), unescapeURLPath(path[slash+1:])) 203 | return 204 | case "enum": 205 | slash := strings.IndexByte(path, '/') 206 | if slash < 0 { 207 | link = o.FileLink("enum", unescapeURLPath(path)) 208 | return 209 | } 210 | link = o.FileLink("enumitem", unescapeURLPath(path[:slash]), unescapeURLPath(path[slash+1:])) 211 | return 212 | case "type": 213 | link = o.FileLink("type", unescapeURLPath(path)) 214 | return 215 | case "member": 216 | link = o.FileLink("member", unescapeURLPath(path)) 217 | return 218 | } 219 | return "", "", ref 220 | } 221 | -------------------------------------------------------------------------------- /settings/settings.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/robloxapi/rbxapiref/builds" 11 | "github.com/robloxapi/rbxapiref/fetch" 12 | "github.com/robloxapi/rbxapiref/internal/binio" 13 | ) 14 | 15 | type Settings struct { 16 | // Input specifies input settings. 17 | Input Input 18 | // Output specifies output settings. 19 | Output Output 20 | // Build specifies build settings. 21 | Build builds.Settings 22 | } 23 | 24 | type Input struct { 25 | // Resources is the directory containing resource files. 26 | Resources string 27 | // Templates is the directory containing template files. 28 | Templates string 29 | // Documents is the directory containing document files. 30 | Documents string 31 | // DocResources is the directory containing document resource files. 32 | DocResources string 33 | // UseGit sets whether document parsing is aware of git. If so, only 34 | // committed content will be used. That is, untracked files are ignored, and 35 | // only committed modifications to a file are used. 36 | UseGit bool 37 | } 38 | 39 | func (settings *Settings) ReadFrom(r io.Reader) (n int64, err error) { 40 | dw := binio.NewReader(r) 41 | var jsettings struct { 42 | Input struct { 43 | Resources *string 44 | Templates *string 45 | Documents *string 46 | DocResources *string 47 | UseGit *bool 48 | } 49 | Output struct { 50 | Root *string 51 | Sub *string 52 | Resources *string 53 | DocResources *string 54 | Manifest *string 55 | Host *string 56 | } 57 | Build struct { 58 | Configs map[string]fetch.Config 59 | UseConfigs []string 60 | DisableRewind *bool 61 | } 62 | } 63 | err = json.NewDecoder(dw).Decode(&jsettings) 64 | if err != nil { 65 | return dw.BytesRead(), fmt.Errorf("decode settings file: %w", err) 66 | } 67 | 68 | wd, _ := os.Getwd() 69 | 70 | mergeString := func(dst, src *string, path bool) { 71 | if src != nil && *src != "" { 72 | *dst = *src 73 | } 74 | if path && !filepath.IsAbs(*dst) { 75 | *dst = filepath.Join(wd, *dst) 76 | } 77 | } 78 | mergeBool := func(dst, src *bool) { 79 | if src != nil && *src { 80 | *dst = *src 81 | } 82 | } 83 | mergeString(&settings.Input.Resources, jsettings.Input.Resources, true) 84 | mergeString(&settings.Input.Templates, jsettings.Input.Templates, true) 85 | mergeString(&settings.Input.Documents, jsettings.Input.Documents, true) 86 | mergeString(&settings.Input.DocResources, jsettings.Input.DocResources, true) 87 | mergeBool(&settings.Input.UseGit, jsettings.Input.UseGit) 88 | mergeBool(&settings.Build.DisableRewind, jsettings.Build.DisableRewind) 89 | mergeString(&settings.Output.Root, jsettings.Output.Root, true) 90 | mergeString(&settings.Output.Sub, jsettings.Output.Sub, false) 91 | mergeString(&settings.Output.Manifest, jsettings.Output.Manifest, false) 92 | mergeString(&settings.Output.Resources, jsettings.Output.Resources, false) 93 | mergeString(&settings.Output.DocResources, jsettings.Output.DocResources, false) 94 | mergeString(&settings.Output.Host, jsettings.Output.Host, false) 95 | for k, v := range jsettings.Build.Configs { 96 | settings.Build.Configs[k] = v 97 | } 98 | if len(jsettings.Build.UseConfigs) > 0 { 99 | settings.Build.UseConfigs = append(settings.Build.UseConfigs[:0], jsettings.Build.UseConfigs...) 100 | } 101 | 102 | return dw.End() 103 | } 104 | 105 | func (settings *Settings) WriteTo(w io.Writer) (n int64, err error) { 106 | ew := binio.NewWriter(w) 107 | je := json.NewEncoder(ew) 108 | je.SetEscapeHTML(true) 109 | je.SetIndent("", "\t") 110 | err = je.Encode(settings) 111 | if err != nil { 112 | return ew.BytesWritten(), fmt.Errorf("encode settings file: %w", err) 113 | } 114 | return ew.End() 115 | } 116 | 117 | func (settings *Settings) filename(name string) (string, error) { 118 | // User-defined. 119 | if name != "" { 120 | return name, nil 121 | } 122 | 123 | // Portable, if present. 124 | name = FileName 125 | if _, err := os.Stat(name); !os.IsNotExist(err) { 126 | return name, nil 127 | } 128 | 129 | // Local config. 130 | config, err := os.UserConfigDir() 131 | if err != nil { 132 | return "", err 133 | } 134 | name = filepath.Join(config, ToolName, FileName) 135 | return name, nil 136 | } 137 | 138 | func (settings *Settings) ReadFile(filename string) error { 139 | filename, err := settings.filename(filename) 140 | if err != nil { 141 | return fmt.Errorf("settings file name: %w", err) 142 | } 143 | file, err := os.Open(filename) 144 | if err != nil { 145 | if os.IsNotExist(err) { 146 | return nil 147 | } 148 | return fmt.Errorf("open settings file: %w", err) 149 | } 150 | defer file.Close() 151 | _, err = settings.ReadFrom(file) 152 | return err 153 | } 154 | 155 | func (settings *Settings) WriteFile(filename string) error { 156 | filename, err := settings.filename(filename) 157 | if err != nil { 158 | return fmt.Errorf("settings file name: %w", err) 159 | } 160 | file, err := os.Create(filename) 161 | if err != nil { 162 | return fmt.Errorf("create settings file: %w", err) 163 | } 164 | defer file.Close() 165 | _, err = settings.WriteTo(file) 166 | return err 167 | } 168 | 169 | func (settings *Settings) Copy() *Settings { 170 | c := *settings 171 | c.Build.Configs = make(map[string]fetch.Config, len(settings.Build.Configs)) 172 | for k, v := range settings.Build.Configs { 173 | c.Build.Configs[k] = v 174 | } 175 | c.Build.UseConfigs = make([]string, len(settings.Build.UseConfigs)) 176 | copy(c.Build.UseConfigs, settings.Build.UseConfigs) 177 | return &c 178 | } 179 | -------------------------------------------------------------------------------- /templates/about.gohtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |

About

4 |
5 |
6 |

7 | The Roblox API Reference project aims to provide a quick and accessible reference for the 8 | Roblox Lua API. 9 |

10 |

11 | This site is generated by the rbxapiref tool, the source of 12 | which is available on GitHub. The 13 | rbxapiref tool parses the machine-generated "API dumps" 14 | from each version of Roblox, and presents the derived information as a 15 | human-readable website. An API dump is a file containing data about the 16 | elements of the API, including classes, members, enums, types, and more. 17 |

18 |

19 | Hand-written, supplementary documentation may be found in generated 20 | pages, derived from the robloxapi/doc 21 | repository. The documentation status 22 | page shows the progress for documentation across the site. 23 |

24 |

25 | This project is an alternative to the 26 | Roblox Developer Hub API Reference Manual. The DevHub provides official, canonical, 27 | up-to-date documentation for the Roblox Lua API. 28 |

29 |

30 | This site is hosted statically on GitHub Pages. 31 |

32 |

33 | If you find a bug, have a suggestion, or otherwise wish to contribute, you may post an issue 34 | or make a pull request on the issues page. 35 |

36 |
37 | 49 |
50 |

Roblox

51 |
52 | Roblox is the best place to 53 | Imagine with Friends. With the largest user-generated online gaming platform, and over 15 54 | million games created by users, Roblox is the #1 gaming site for kids and teens (comScore). 55 | Every day, virtual explorers come to Roblox to create adventures, play games, role play, and 56 | learn with their friends in a family-friendly, immersive, 3D environment. 57 |
58 |

Lua

59 |
60 | Lua is a powerful, efficient, lightweight, 61 | embeddable scripting language. It supports procedural programming, object-oriented 62 | programming, functional programming, data-driven programming, and data description. 63 |
64 |

Licenses

65 |

66 | The source code of the rbxapiref tool is available under the MIT license. 68 |

69 |

70 | Creative Commons License
This site and non-generated documentation is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 71 |

72 |

73 | This site is not affiliated with Roblox Corporation. 74 |

75 |
76 |
77 | -------------------------------------------------------------------------------- /templates/class.gohtml: -------------------------------------------------------------------------------- 1 | {{- $class := .ID -}} 2 | {{- $summary := document . "Summary" -}} 3 | {{- if not $summary -}}{{- $summary = document . "" -}}{{- end -}} 4 | {{- $details := document . "Details" -}} 5 | {{- $constructors := document . "Constructors" -}} 6 | {{- $examples := document . "Examples" -}} 7 | {{- $history := history . false false -}} 8 | {{- $superclasses := filter .Superclasses "Added" -}} 9 | {{- $subclasses := filter .Subclasses "Added" -}} 10 | {{- $members := filter .MemberList "Added" }} 11 | {{- $removed := filter .MemberList "Removed" -}} 12 | {{- $membersSorted := sortedlist $members }} 13 | {{- $removedSorted := sortedlist $removed }} 14 | {{- $classes := filter .ReferenceList "Class" -}} 15 | {{- $enums := filter .ReferenceList "Enum" -}} 16 | {{- $referrers := filter .ReferrerList "ImplicitAdded" -}} 17 |
18 |
19 |

{{icon .Element}}{{.ID}}{{if not .Removed}} {{template "devhub-link" link "devhub" "class" $class}}{{end}}

20 |
21 |
22 |
23 |

Summary

24 |
25 | {{- if $summary }} 26 |
{{renderdoc $summary 2}}
27 | {{- end }} 28 | {{- template "status-boxes" .Element -}} 29 | 30 | 31 | 32 | {{- template "metadata" . }} 33 | 34 | 35 | {{- if .Element.Tags }} 36 |

Tags: {{tostring .Element.Tags}}

37 | {{- end -}} 38 |
39 | 109 | {{- if or $superclasses $subclasses }} 110 |
111 | {{- if $superclasses }} 112 |
113 |
114 |

Inherits ({{len $superclasses}})

115 |
116 |
    117 | {{- range $superclasses -}} 118 | {{- $status := status false . }} 119 | {{icon .Element}}{{.ID}} 120 | {{- end }} 121 |
122 |
123 | {{- end -}} 124 | {{- if $subclasses }} 125 |
126 |
127 |

Inherited by ({{len $subclasses}})

128 |
129 |
    130 | {{- range $subclasses -}} 131 | {{- $status := status false . }} 132 | {{icon .Element}}{{.ID}} 133 | {{- end }} 134 |
135 |
136 | {{- end }} 137 |
138 | {{- end }} 139 |
140 |
141 |

Member index ({{len $members}})

142 |
143 | {{template "member-index-table" pack $class $members .Superclasses}} 144 |
145 | {{- if $removed }} 146 |
147 |
148 |

Removed member index ({{len $removed}})

149 |
150 | {{template "member-index-table" pack $class $removed}} 151 |
152 | {{- end }} 153 | {{- if $details }} 154 |
155 |
156 |

Details

157 |
158 |
{{renderdoc $details 2}}
159 |
160 | {{- end -}} 161 | {{- if $constructors }} 162 |
163 |
164 |

Constructors

165 |
166 |
{{renderdoc $constructors 2}}
167 |
168 | {{- end -}} 169 | {{- if $examples }} 170 |
171 |
172 |

Examples

173 |
174 |
{{renderdoc $examples 2}}
175 |
176 | {{- end -}} 177 | {{- if $history }} 178 |
179 |
180 |

History

181 | 182 |
183 | {{$history}} 184 |
185 | {{- end -}} 186 | {{- if $membersSorted }} 187 |
188 |
189 |

Members

190 |
191 |
192 | {{- range $membersSorted -}} 193 | {{- template "member-section" . -}} 194 | {{- end }} 195 |
196 |
197 | {{- end }} 198 | {{- if $removedSorted }} 199 |
200 |
201 |

Removed members

202 |
203 |
204 | {{- range $removedSorted -}} 205 | {{- template "member-section" . -}} 206 | {{- end }} 207 |
208 |
209 | {{- end -}} 210 | {{- if or $classes $enums $referrers }} 211 |
212 | {{- if $classes }} 213 |
214 |
215 |

Relevant classes ({{len $classes}})

216 |
217 |
    218 | {{- range $classes -}} 219 | {{- $status := status false . }} 220 | {{icon .Element}}{{.ID}} 221 | {{- end }} 222 |
223 |
224 | {{- end -}} 225 | {{- if $enums }} 226 |
227 |
228 |

Relevant enums ({{len $enums}})

229 |
230 |
    231 | {{- range $enums -}} 232 | {{- $status := status false . }} 233 | {{icon .Element}}{{.ID}} 234 | {{- end }} 235 |
236 |
237 | {{- end -}} 238 | {{- template "referrers" pack . $referrers -}} 239 |
240 | {{- end }} 241 |
242 | -------------------------------------------------------------------------------- /templates/devhub-link.gohtml: -------------------------------------------------------------------------------- 1 | On DevHub{{- /**/ -}} 2 | -------------------------------------------------------------------------------- /templates/docmon.gohtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Documentation monitor

4 |
5 |
6 |
7 | Legend 8 | 9 |

Entities

10 |

The table lists each entity in the API. Primary entities are 11 | highlighted.

12 | 13 |

Sections

14 |

The Summary, Details, and Example columns indicate the status of the 15 | corresponding document sections for each entity. They are colored as 16 | follows:

17 |
    18 |
  • No file: The entity does not have an 19 | associated document file.
  • 20 |
  • No section: The entity has a document 21 | file, but there is no section for the corresponding column.
  • 22 |
  • Empty: There is a section, but it has no 23 | content.
  • 24 |
  • Filled: The section contains at least 25 | some content.
  • 26 |
27 | 28 |

Summary

29 |

If the summary of a primary entity is within the orphaned section, then 30 | * is displayed, indicating that the summary should be within 31 | the explicit Summary section. If the summary of a secondary entity is within 32 | the Summary section, then ** will be displayed, indicating that 33 | the summary should be within the implicit orphaned section.

34 |

All entity types require a Summary section.

35 | 36 |

Details

37 |

When filled, the number of direct elements within the section will be 38 | displayed, giving a rough estimate of the amount of content.

39 |

All entity types except EnumItems require a Details section.

40 | 41 |

Examples

42 |

When filled, the number of direct elements within the section will be 43 | displayed, giving a rough estimate of the amount of content.

44 |

All entity types except Enums and EnumItems require an Examples 45 | section.

46 | 47 |

Aggregate

48 | 49 |

The Aggregate column shows the coverage of documentation for the entity 50 | overall. For secondary entities, it is the percentage of filled required 51 | sections of the entity. For primary entities, it is the percentage of filled 52 | required sections of the entity and all of its secondary entities.

53 | 54 |

Colors in the Aggregate column have the following meaning:

55 |
    56 |
  • No file: No associated file.
  • 57 |
  • None: No items are filled.
  • 58 |
  • Some: Some items are filled.
  • 59 |
  • All: All items are filled.
  • 60 |
61 |
62 |
63 |
64 |

Global coverage: {{.CoverageString}}

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {{- range $i, $entity := .ListAll -}} 79 | {{- $status := docstatus $entity -}} 80 | {{- $type := "" -}} 81 | {{- if istype $entity "*entities.Class" -}} 82 | {{- $type = "p" -}} 83 | {{- else if istype $entity "*entities.Enum" -}} 84 | {{- $type = "p" -}} 85 | {{- else if istype $entity "entities.TypeCategory" -}} 86 | {{- $type = "p" -}} 87 | {{- end }} 88 | 89 | 90 | {{- if istype $entity "*entities.Class"}} 91 | 92 | 93 | {{if and (ge $status.SummaryStatus 3) $status.SummaryOrphaned}}*{{end}} 94 | {{if ge $status.DetailsStatus 3}}{{$status.DetailsSections}}{{end}} 95 | {{if ge $status.ExamplesStatus 3}}{{$status.ExampleCount}}{{end}} 96 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 97 | {{- else if istype $entity "*entities.Member"}} 98 | 99 | 100 | {{if and (ge $status.SummaryStatus 3) (not $status.SummaryOrphaned)}}**{{end}} 101 | {{if ge $status.DetailsStatus 3}}{{$status.DetailsSections}}{{end}} 102 | {{if ge $status.ExamplesStatus 3}}{{$status.ExampleCount}}{{end}} 103 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 104 | {{- else if istype $entity "*entities.Enum"}} 105 | 106 | 107 | {{if and (ge $status.SummaryStatus 3) $status.SummaryOrphaned}}*{{end}} 108 | {{if ge $status.DetailsStatus 3}}{{$status.DetailsSections}}{{end}} 109 | {{if ge $status.ExamplesStatus 3}}{{$status.ExampleCount}}{{end}} 110 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 111 | {{- else if istype $entity "*entities.EnumItem"}} 112 | 113 | 114 | {{if and (ge $status.SummaryStatus 3) (not $status.SummaryOrphaned)}}**{{end}} 115 | {{if ge $status.DetailsStatus 3}}{{$status.DetailsSections}}{{end}} 116 | {{if ge $status.ExamplesStatus 3}}{{$status.ExampleCount}}{{end}} 117 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 118 | {{- else if istype $entity "entities.TypeCategory"}} 119 | 120 | 121 | 122 | 123 | 124 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 125 | {{- else if istype $entity "*entities.Type"}} 126 | 127 | 128 | {{if and (ge $status.SummaryStatus 3) $status.SummaryOrphaned}}*{{end}} 129 | {{if ge $status.DetailsStatus 3}}{{$status.DetailsSections}}{{end}} 130 | {{if ge $status.ExamplesStatus 3}}{{$status.ExampleCount}}{{end}} 131 | {{if ge $status.AggregateStatus 1}}{{$status.ProgressString}}{{end}} 132 | {{- end }} 133 | 134 | {{- end }} 135 | 136 |
#TypeNameSummaryDetailsExamplesAggregate
{{$i}}Class{{$entity.ID}}{{$entity.Element.GetMemberType}}{{index $entity.ID 0}}.{{index $entity.ID 1}}Enum{{$entity.ID}}EnumItem{{index $entity.ID 0}}.{{index $entity.ID 1}}TypeCategory{{$entity.Name}}Type{{template "value" $entity.Element}}
137 |
138 |
139 | -------------------------------------------------------------------------------- /templates/enum.gohtml: -------------------------------------------------------------------------------- 1 | {{- $enum := .ID -}} 2 | {{- $summary := document . "Summary" -}} 3 | {{- if not $summary -}}{{- $summary = document . "" -}}{{- end -}} 4 | {{- $details := document . "Details" -}} 5 | {{- $examples := document . "Examples" }} 6 | {{- $history := history . false false -}} 7 | {{- $members := filter .ItemList "Added" }} 8 | {{- $removed := filter .ItemList "Removed" -}} 9 | {{- $membersSorted := sortedlist (filter .ItemList "Added" "Documented") }} 10 | {{- $removedSorted := sortedlist (filter .ItemList "Removed" "Documented") }} 11 | {{- $referrers := filter .ReferrerList "ImplicitAdded" -}} 12 | 13 |
14 |

{{icon .}}{{.ID}}{{if not .Removed}} {{template "devhub-link" link "devhub" "enum" $enum}}{{end}}

15 |
16 | {{- if or $summary .Element.Tags }} 17 |
18 |
19 |

Summary

20 |
21 | {{- if $summary }} 22 |
{{renderdoc $summary 2}}
23 | {{- end }} 24 | {{- template "status-boxes" .Element -}} 25 | {{- if .Element.Tags }} 26 |

Tags: {{tostring .Element.Tags}}

27 | {{- end }} 28 |
29 | {{- end }} 30 | 77 |
78 |
79 |

{{if $membersSorted}}Item index{{else}}Items{{end}} ({{len $members}})

80 |
81 | {{template "enumitem-index-table" pack $enum $members $membersSorted}} 82 | {{- if $removed }} 83 |
84 |
85 |

{{if $removedSorted}}Removed item index{{else}}Removed items{{end}} ({{len $removed}})

86 |
87 | {{template "enumitem-index-table" pack $enum $removed $removedSorted}} 88 |
89 | {{- end }} 90 |
91 | {{- if $details }} 92 |
93 |
94 |

Details

95 |
96 |
{{renderdoc $details 2}}
97 |
98 | {{- end -}} 99 | {{- if $examples }} 100 |
101 |
102 |

Examples

103 |
104 |
{{renderdoc $examples 2}}
105 |
106 | {{- end -}} 107 | {{- if $membersSorted }} 108 |
109 |
110 |

Items

111 |
112 |
113 | {{- range $membersSorted -}} 114 | {{- template "enumitem-section" . -}} 115 | {{- end }} 116 |
117 |
118 | {{- end }} 119 | {{- if $removedSorted }} 120 |
121 |
122 |

Removed items

123 |
124 |
125 | {{- range $removedSorted -}} 126 | {{- template "enumitem-section" . -}} 127 | {{- end }} 128 |
129 |
130 | {{- end -}} 131 | {{- if $history }} 132 |
133 |
134 |

History

135 | 136 |
137 | {{$history}} 138 |
139 | {{- end -}} 140 | {{- template "referrers" pack . $referrers }} 141 | 142 | -------------------------------------------------------------------------------- /templates/enumitem-index-table.gohtml: -------------------------------------------------------------------------------- 1 | {{- with unpack . "Enum" "Items" "Descriptive" -}} 2 | {{- $descriptive := .Descriptive -}} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{- if $descriptive }} 11 | 12 | {{- end }} 13 | 14 | 15 | 16 | {{- $enum := .Enum -}} 17 | {{- range .Items -}} 18 | {{- $entity := . -}} 19 | {{- $summary := document . "Summary" -}} 20 | {{- if not $summary -}}{{- $summary = document . "" -}}{{- end -}} 21 | {{- $status := status true . -}} 22 | {{- with .Element }} 23 | 24 | 25 | 26 | 27 | 28 | {{- if $descriptive }} 29 | 36 | {{- end }} 37 | 38 | {{- end -}} 39 | {{- else }} 40 | 41 | 42 | 43 | {{- end }} 44 | 45 |
{{icon "enum"}}NameValueHistoryDescription
{{icon "enumitem"}}{{.Name}}{{.Value}}{{history $entity true true}} 30 | {{- if $summary }} 31 |
{{renderdoc $summary 3}}
32 | {{- else }} 33 | No description. 34 | {{- end }} 35 |
No items defined by {{$enum}}.
46 | {{- end -}} 47 | -------------------------------------------------------------------------------- /templates/enumitem-section.gohtml: -------------------------------------------------------------------------------- 1 | {{- $entity := . -}} 2 | {{- $details := document . "Details" -}} 3 | {{- $examples := document . "Examples" -}} 4 | {{- if or $details $examples -}} 5 | {{- $status := status false . }} 6 | {{- with .Element }} 7 |
8 |
9 |

{{icon .}}{{.Name}}{{if not $entity.Removed}} {{template "devhub-link" link "devhub" "enumitem" $entity.Parent.ID .Name}}{{end}}

10 |
11 | {{- template "status-boxes" . -}} 12 | {{- if $details }} 13 |
{{renderdoc $details 3}}
14 | {{- end }} 15 | {{- if $examples }} 16 |

Examples

17 |
{{renderdoc $examples 3}}
18 | {{- end }} 19 | {{- $history := history $entity false false -}} 20 | {{- if $history }} 21 |

History

22 | {{$history}} 23 | {{- end }} 24 | {{- if .Tags }} 25 |

Tags: {{tostring .Tags}}

26 | {{- end -}} 27 |
28 | {{- end -}} 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /templates/history.gohtml: -------------------------------------------------------------------------------- 1 | {{- $first := .First -}} 2 | {{if .Button -}} 3 | 4 | {{- range .Patches -}} 5 | {{- if not (.Info.Equal $first) -}} 6 | {{- $info := .Info -}} 7 | {{- range .Actions }} 8 | {{$info.Version.Minor}} 9 | {{- end -}} 10 | {{- end -}} 11 | {{- end }} 12 | 13 | {{- else -}} 14 |
    15 | {{- range .Patches -}} 16 | {{- if not (.Info.Equal $first) -}} 17 | {{- $info := .Info }} 18 | {{- range .Actions }} 19 | {{template "update-action" pack . $info false true}} 20 | {{- end -}} 21 | {{- end -}} 22 | {{- end }} 23 |
24 | {{- end -}} 25 | -------------------------------------------------------------------------------- /templates/index.gohtml: -------------------------------------------------------------------------------- 1 | 8 |
9 |
10 | {{- $classes := filter .Entities.ClassList "Added" }} 11 |
12 |

Classes ({{len $classes}})

13 |
14 |
    15 | {{- define "tree" -}} 16 | {{- range . -}} 17 | {{- $status := status false . }} 18 | {{icon .}}{{.ID}} 19 | {{- if .Subclasses }} 20 |
      21 | {{- template "tree" .Subclasses }} 22 |
    23 | {{- end -}} 24 | 25 | {{- end -}} 26 | {{- end -}} 27 | {{- template "tree" .Entities.TreeRoots }} 28 |
29 | {{- $removed := filter .Entities.ClassList "Removed" -}} 30 | {{- if $removed }} 31 |
32 |
33 |

Removed classes ({{len $removed}})

34 |
35 |
    36 | {{- range $removed -}} 37 | {{- $status := status false . }} 38 | {{icon .}}{{.ID}} 39 | {{- end -}} 40 |
41 |
42 | {{- end }} 43 |
44 |
45 | {{- $enums := filter .Entities.EnumList "Added" }} 46 |
47 |

Enums ({{len $enums}})

48 |
49 |
    50 | {{- range $enums -}} 51 | {{- $status := status false . }} 52 | {{icon .}}{{.ID}} 53 | {{- end }} 54 |
55 | {{- $removed := filter .Entities.EnumList "Removed" -}} 56 | {{- if $removed }} 57 |
58 |
59 |

Removed enums ({{len $removed}})

60 |
61 |
    62 | {{- range $removed -}} 63 | {{- $status := status false . }} 64 | {{icon .}}{{.ID}} 65 | {{- end -}} 66 |
67 |
68 | {{- end }} 69 |
70 |
71 | {{- $types := filter .Entities.TypeList "Added" }} 72 |
73 |

Types ({{len $types}}/{{len .Entities.TypeCats}})

74 |
75 |
    76 | {{- range .Entities.TypeCats }} 77 |
  • {{.Name}} 78 |
      79 | {{- range filter .Types "Added" }} 80 |
    • {{template "value" .Element}}
    • 81 | {{- end }} 82 |
    83 |
  • 84 | {{- end }} 85 |
86 | {{- $removed := filter .Entities.TypeList "Removed" -}} 87 | {{- if $removed }} 88 |
89 |
90 |

Removed types ({{len $removed}})

91 |
92 |
    93 | {{- range $removed }} 94 |
  • {{template "value" .Element}}
  • 95 | {{- end -}} 96 |
97 |
98 | {{- end }} 99 |
100 |
101 | -------------------------------------------------------------------------------- /templates/main.gohtml: -------------------------------------------------------------------------------- 1 | {{- $year := .Data.Time.Year -}} 2 | {{- $main := .MainPage -}} 3 | {{- with .Page -}} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{if .Meta.Title}}{{.Meta.Title}}{{else}}{{$main.Meta.Title}}{{end}} 13 | 14 | 15 | {{- range resources $main.Styles }} 16 | {{.}} 17 | {{- end -}} 18 | {{- range resources .Styles }} 19 | {{.}} 20 | {{- end -}} 21 | {{- range resources $main.Scripts }} 22 | {{.}} 23 | {{- end -}} 24 | {{- range resources .Scripts }} 25 | {{.}} 26 | {{- end }} 27 | {{- range cards $main . }} 28 | {{.}} 29 | {{- end }} 30 | 31 | 32 | 33 |
34 |
35 |

Roblox API Reference

36 | 42 | 47 |
48 |
49 |
50 | {{ execute .Template .Data -}} 51 | {{/**/}} 54 |
55 | 66 |
Content is licensed under CC BY-SA-4.0.
67 | {{- $pubyear := 2018 }} 68 |
{{embed "ana.svg"}} {{$pubyear}}{{if gt $year $pubyear}}–{{$year}}{{end}} Anaminus
69 |
70 | 71 | 72 | {{- end }} 73 | -------------------------------------------------------------------------------- /templates/member-index-table.gohtml: -------------------------------------------------------------------------------- 1 | {{- with unpack . "Class" "Members" "Superclasses" -}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{- $class := .Class -}} 13 | {{- range .Members -}} 14 | {{- $entity := . -}} 15 | {{- $status := status true . -}} 16 | {{- with .Element }} 17 | 18 | {{- if eq .GetMemberType "Property" }} 19 | 20 | 21 | 22 | 23 | {{- else if eq .GetMemberType "Function" }} 24 | 25 | 26 | 27 | 28 | {{- else if eq .GetMemberType "Event" }} 29 | 30 | 31 | 32 | 33 | {{- else if eq .GetMemberType "Callback" }} 34 | 35 | 36 | 37 | 38 | {{- end }} 39 | 40 | {{- end -}} 41 | {{- else }} 42 | 43 | 44 | 45 | {{- end }} 46 | 47 | {{- range .Superclasses -}} 48 | {{- $members := filter .MemberList "Added" -}} 49 | {{- if $members }} 50 | 51 | 52 | 53 | 54 | 55 | {{- end -}} 56 | {{- end }} 57 |
TypeMemberHistory
{{template "value" .ValueType}}{{icon .}}{{.Name}}{{history $entity true true}}{{template "value" .ReturnType}}{{icon .}}{{.Name}}{{template "value" .Parameters}}{{history $entity true true}}{{icon .}}{{.Name}}{{template "value" .Parameters}}{{history $entity true true}}{{template "value" .ReturnType}}{{icon .}}{{.Name}}{{template "value" .Parameters}}{{history $entity true true}}
No members defined by {{$class}}.
{{len $members}} {{quantity $members "member" "members"}} inherited from {{icon .Element}}{{.ID}}
58 | {{- end -}} 59 | -------------------------------------------------------------------------------- /templates/member-section.gohtml: -------------------------------------------------------------------------------- 1 | {{- $entity := . -}} 2 | {{- $summary := document . "Summary" -}} 3 | {{- if not $summary -}}{{- $summary = document . "" -}}{{- end -}} 4 | {{- $details := document . "Details" -}} 5 | {{- $examples := document . "Examples" -}} 6 | {{- $status := status false . -}} 7 | {{- with .Element }} 8 |
9 |
10 |

{{icon .}}{{.GetName}}{{if not $entity.Removed}} {{template "devhub-link" link "devhub" .GetMemberType $entity.Parent.ID .GetName}}{{end}}

11 |
12 | {{- if $summary }} 13 |
{{renderdoc $summary 3}}
14 | {{- end }} 15 | {{- template "status-boxes" . -}} 16 | {{- if eq .GetMemberType "Property" }} 17 | 18 | 19 | 20 | {{- if eq .ReadSecurity .WriteSecurity -}} 21 | {{- if and .ReadSecurity (ne .ReadSecurity "None")}} 22 | 23 | {{- end -}} 24 | {{- else -}} 25 | {{- if and .ReadSecurity (ne .ReadSecurity "None")}} 26 | 27 | {{- end -}} 28 | {{- if and .WriteSecurity (ne .WriteSecurity "None")}} 29 | 30 | {{- end -}} 31 | {{- end }} 32 | 33 | 34 | 35 | {{template "metadata" $entity}} 36 | 37 | 38 | {{- else if eq .GetMemberType "Function" }} 39 | 40 | 41 | 42 | 43 | 44 | {{template "param-table" .Parameters}} 45 | 46 | 47 | 48 | {{- if and .Security (ne .Security "None") }} 49 | 50 | {{- end -}} 51 | {{template "metadata" $entity}} 52 | 53 | 54 | {{- else if eq .GetMemberType "Event" -}} 55 | 56 | 57 | 58 | 59 | 60 | {{template "param-table" .Parameters}} 61 | {{- if and .Security (ne .Security "None") }} 62 | 63 | 64 | 65 | {{template "metadata" $entity}} 66 | 67 | 68 | {{- end -}} 69 | {{- else if eq .GetMemberType "Callback" }} 70 | 71 | 72 | 73 | 74 | 75 | {{template "param-table" .Parameters}} 76 | 77 | 78 | 79 | {{- if and .Security (ne .Security "None") }} 80 | 81 | {{- end -}} 82 | {{template "metadata" $entity}} 83 | 84 | 85 | {{- end -}} 86 | {{- if $details }} 87 |
{{renderdoc $details 3}}
88 | {{- end }} 89 | {{- if $examples }} 90 |

Examples

91 |
{{renderdoc $examples 3}}
92 | {{- end }} 93 | {{- $history := history $entity false false -}} 94 | {{- if $history }} 95 |

History

96 | {{$history}} 97 | {{- end }} 98 | {{- if .Tags }} 99 |

Tags: {{tostring .Tags}}

100 | {{- end -}} 101 |
102 | {{- end }} 103 | -------------------------------------------------------------------------------- /templates/metadata.gohtml: -------------------------------------------------------------------------------- 1 | {{- if .Metadata.Instance -}} 2 | {{- range $name, $value := .Metadata.Properties -}} 3 | {{- if eq $name "Name" "summary" "Browsable" "Deprecated" -}} 4 | {{- else if eq $name "ExplorerImageIndex" }} 5 | {{$name}}{{$value}} () 6 | {{- else if eq $name "PreferredParent" "PreferredParents" }} 7 | {{- $list := list $value -}} 8 | {{- if $list }} 9 | 10 | {{$name}} 11 | 12 | {{- range $list }} 13 | {{icon "class" .}}{{.}} 14 | {{- end }} 15 | 16 | 17 | {{- else }} 18 | {{$name}}{{$value}} 19 | {{- end }} 20 | {{- else }} 21 | {{$name}}{{$value}} 22 | {{- end -}} 23 | {{- end -}} 24 | {{- end -}} 25 | -------------------------------------------------------------------------------- /templates/outline.gohtml: -------------------------------------------------------------------------------- 1 | {{- $ok := false -}} 2 | {{- range . -}} 3 | {{- if .Name -}} 4 | {{- $ok = true -}} 5 | {{- end -}} 6 | {{- end -}} 7 | {{- if $ok }} 8 |
    9 | {{- range . -}} 10 | {{- if .Name }} 11 |
  1. {{.Name}} 12 | {{- template "outline" .Subsections }} 13 |
  2. 14 | {{- end -}} 15 | {{- end -}} 16 |
17 | {{- end -}} 18 | -------------------------------------------------------------------------------- /templates/param-table.gohtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{- range . }} 11 | 12 | 13 | 14 | 15 | 16 | {{- else }} 17 | 18 | 19 | 20 | {{- end }} 21 | 22 |
NameTypeDefault
{{.Name}}{{template "value" .Type}}{{if .HasDefault}}{{template "value" .Default}}{{else}}none{{end}}
No parameters.
23 | -------------------------------------------------------------------------------- /templates/parameters.gohtml: -------------------------------------------------------------------------------- 1 | ( 2 | {{- range $i, $p := $ -}} 3 | {{template "value" $p.Type}} {{$p.Name}}{{if $p.HasDefault}} = {{$p.Default}}{{end}} 4 | {{- if ne $i (last $) -}} 5 | , 6 | {{- end -}} 7 | 8 | {{- end -}} 9 | ) 10 | {{- /**/ -}} 11 | -------------------------------------------------------------------------------- /templates/referrers.gohtml: -------------------------------------------------------------------------------- 1 | {{- with unpack . "Entity" "Referrers" "Removed" -}} 2 | {{- if .Referrers }} 3 |
4 |
5 |

{{if .Removed}}Formerly relevant{{else}}Relevant{{end}} members ({{len .Referrers}})

6 |
7 |
    8 | {{- range .Referrers -}} 9 | {{- $class := (index .Member.ID 0) -}} 10 | {{- $member := (index .Member.ID 1) -}} 11 | {{- $status := status false .Member }} 12 | {{icon .Member}}{{$class}}.{{$member}}{{if .Parameter}} ({{.Parameter.Name}}){{end}} 13 | {{- end }} 14 |
15 |
16 | {{- end -}} 17 | {{- end -}} 18 | -------------------------------------------------------------------------------- /templates/resource.gohtml: -------------------------------------------------------------------------------- 1 | {{- if eq .Type ".css" -}} 2 | {{- if .Resource.Embed -}} 3 | {{.Content}} 4 | {{- else -}} 5 | 6 | {{- end -}} 7 | {{- else if eq .Type ".js" -}} 8 | {{- if .Resource.Embed -}} 9 | {{.Content}} 10 | {{- else -}} 11 | 12 | {{- end -}} 13 | {{- else -}} 14 | {{- if .Resource.Embed -}} 15 | {{.Content}} 16 | {{- end -}} 17 | {{- end -}} 18 | -------------------------------------------------------------------------------- /templates/status-box.gohtml: -------------------------------------------------------------------------------- 1 | {{- with unpack . "Level" "Header" "Text" -}} 2 |
{{if .Text}}{{.Header}} {{.Text}}{{else}}{{.Header}}{{end}}
3 | {{- end -}} 4 | -------------------------------------------------------------------------------- /templates/status-boxes.gohtml: -------------------------------------------------------------------------------- 1 | {{- if len .Tags -}} 2 |
3 | {{- if .GetTag "NotCreatable" -}} 4 | {{- template "status-box" pack "low" "This class is not creatable." "An object of this class cannot be created with Instance.new." -}} 5 | {{- end -}} 6 | {{- if .GetTag "PlayerReplicated" -}} 7 | {{- template "status-box" pack "low" "This class is player-replicated." "Its data is replicated between the server and only a single client." -}} 8 | {{- end -}} 9 | {{- if .GetTag "Service" -}} 10 | {{- template "status-box" pack "low" "This class is a service." "It is a singleton that may be acquired with GetService." -}} 11 | {{- end -}} 12 | {{- if .GetTag "Settings" -}} 13 | {{- template "status-box" pack "low" "This class is a settings container." "It contains settings that affect the behavior of the peer." -}} 14 | {{- end -}} 15 | {{- if .GetTag "ReadOnly" -}} 16 | {{- template "status-box" pack "low" "This property is read-only." "Its value can be read, but it cannot be modified." -}} 17 | {{- end -}} 18 | {{- if .GetTag "noyield" -}} 19 | {{- template "status-box" pack "medium" "This callback cannot yield." "Attempting to yield within this callback will result in an error." -}} 20 | {{- end -}} 21 | {{- if .GetTag "CanYield" -}} 22 | {{- template "status-box" pack "medium" "This function can yield." "It may or may not block the calling thread until completion." -}} 23 | {{- end -}} 24 | {{- /* 25 | {{ - if .GetTag "CustomLuaState" -} } 26 | {{- template "status-box" pack "low" "This function has a custom lua state." "It may behave in a non-standard way." -} } 27 | {{- end -} } 28 | */ -}} 29 | {{- if .GetTag "Yields" -}} 30 | {{- template "status-box" pack "medium" "This function yields." "It will block the calling thread until completion." -}} 31 | {{- end -}} 32 | {{- if .GetTag "Hidden" -}} 33 | {{- template "status-box" pack "low" "This member is hidden." "It is not meant to be used, and may have unresolved issues." -}} 34 | {{- end -}} 35 | {{- if .GetTag "NotScriptable" -}} 36 | {{- template "status-box" pack "medium" "This member is not scriptable." "It cannot be accessed by Lua code." -}} 37 | {{- end -}} 38 | {{- if .GetTag "Deprecated" -}} 39 | {{- template "status-box" pack "high" "This item is deprecated." "It exists for backwards-compatibility only, and should not be used for new work." -}} 40 | {{- end -}} 41 | {{- if .GetTag "NotBrowsable" -}} 42 | {{- template "status-box" pack "low" "This item is not browsable." "It is not visible in Studio's object browser." -}} 43 | {{- end -}} 44 | {{- if .GetTag "NotReplicated" -}} 45 | {{- template "status-box" pack "medium" "This item is not replicated." "Its interface does not cross the network boundary." -}} 46 | {{- end -}} 47 |
48 | {{- end -}} 49 | -------------------------------------------------------------------------------- /templates/type.gohtml: -------------------------------------------------------------------------------- 1 | {{- $summary := document . "Summary" -}} 2 | {{- if not $summary -}}{{- $summary = document . "" -}}{{- end -}} 3 | {{- $details := document . "Details" -}} 4 | {{- $constructors := document . "Constructors" -}} 5 | {{- $fields := document . "Fields" -}} 6 | {{- $methods := document . "Methods" -}} 7 | {{- $operators := document . "Operators" -}} 8 | {{- $examples := document . "Examples" -}} 9 | {{- $referrers := filter .ReferrerList "ImplicitAdded" -}} 10 | {{- $removed := false -}} 11 | {{- if not $referrers -}} 12 | {{- $referrers = .RemovedRefList -}} 13 | {{- $removed = true -}} 14 | {{- end -}} 15 |
16 |
17 |

{{.ID}}{{if not .Removed}} {{template "devhub-link" link "devhub" "type" .ID}}{{end}}

18 |
19 | {{- if $summary }} 20 |
21 |
{{renderdoc $summary 2}}
22 |
23 | {{- end -}} 24 | {{- if or $details $examples }} 25 | 57 | {{- end -}} 58 | {{- if $details }} 59 |
60 |
61 |

Details

62 |
63 |
{{renderdoc $details 2}}
64 |
65 | {{- end -}} 66 | {{- if $constructors }} 67 |
68 |
69 |

Constructors

70 |
71 |
{{renderdoc $constructors 2}}
72 |
73 | {{- end -}} 74 | {{- if $fields }} 75 |
76 |
77 |

Fields

78 |
79 |
{{renderdoc $fields 2}}
80 |
81 | {{- end -}} 82 | {{- if $methods }} 83 |
84 |
85 |

Methods

86 |
87 |
{{renderdoc $methods 2}}
88 |
89 | {{- end -}} 90 | {{- if $operators }} 91 |
92 |
93 |

Operators

94 |
95 |
{{renderdoc $operators 2}}
96 |
97 | {{- end -}} 98 | {{- if $examples }} 99 |
100 |
101 |

Examples

102 |
103 |
{{renderdoc $examples 2}}
104 |
105 | {{- end -}} 106 | {{- template "referrers" pack . $referrers $removed }} 107 |
108 | -------------------------------------------------------------------------------- /templates/update-action.gohtml: -------------------------------------------------------------------------------- 1 | {{- with unpack . "Action" "Info" "Subactions" "Button" -}} 2 | {{- $info := .Info -}} 3 | {{- $sub := .Subactions -}} 4 | {{- $button := .Button -}} 5 | {{- $status := status false .Action -}} 6 | {{- with .Action }} 7 |
  • 8 | {{- if $button }} 9 | {{$info.Version.Minor}} 10 | {{ end -}} 11 | {{- if and .Class .GetMember -}} 12 | {{- if eq .Type 0 -}} 13 | {{.Type.String}} {{.Field}} of {{icon .GetMember}}{{.Class.GetName}}.{{.GetMember.GetName}} 14 | from {{template "value" .GetPrev}} to {{template "value" .GetNext}} 15 | {{- else -}} 16 | {{.Type.String}} {{icon .GetMember}}{{.Class.Name}}.{{.GetMember.GetName}} 17 | {{- end -}} 18 | {{- else if .Class -}} 19 | {{- if eq .Type 0 -}} 20 | {{.Type.String}} {{.Field}} of {{icon .Class false}}{{.Class.Name}} 21 | from {{template "value" .GetPrev}} to {{template "value" .GetNext}} 22 | {{- else -}} 23 | {{.Type.String}} {{icon .Class}}{{.Class.Name}} 24 | 32 | {{- end -}} 33 | {{- else if and .Enum .EnumItem -}} 34 | {{- if eq .Type 0 -}} 35 | {{.Type.String}} {{.Field}} of {{icon .EnumItem false}}{{.Enum.Name}}.{{.EnumItem.Name}} 36 | from {{template "value" .GetPrev}} to {{template "value" .GetNext}} 37 | {{- else -}} 38 | {{.Type.String}} {{icon .EnumItem}}{{.Enum.Name}}.{{.EnumItem.Name}} 39 | {{- end -}} 40 | {{- else if .Enum -}} 41 | {{- if eq .Type 0 -}} 42 | {{.Type.String}} {{.Field}} of {{icon .Enum false}}{{.Enum.Name}} 43 | from {{template "value" .GetPrev}} to {{template "value" .GetNext}} 44 | {{- else -}} 45 | {{.Type.String}} {{icon .Enum}}{{.Enum.Name}} 46 |
      47 | {{- if $sub -}} 48 | {{- range subactions . -}} 49 | {{- $status := status false .EnumItem }} 50 | {{.Type.String}} {{icon .EnumItem}}{{.EnumItem.Name}} 51 | {{- end -}} 52 | {{- end }} 53 |
    54 | {{- end -}} 55 | {{- else -}} 56 | {{.String}} 57 | {{- end -}} 58 |
  • 59 | {{- end -}} 60 | {{- end -}} 61 | -------------------------------------------------------------------------------- /templates/updates.gohtml: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    {{if not .Year}}Recent {{end}}API Updates{{if .Year}} in {{.Year}}{{end}}

    4 |
    5 | 7 | 24 |
    25 |
      26 | {{- with .Patches -}} 27 | {{- range . }} 28 |
    • 29 |
      30 | (v{{.Info.Version}}) 31 | 32 |
        33 | {{- $info := .Info }} 34 | {{- range .Actions }} 35 | {{template "update-action" pack . $info true}} 36 | {{- else }} 37 |
      • No changes
      • 38 | {{- end }} 39 |
      40 |
      41 |
    • 42 | {{- else -}} 43 |
    • No updates
    • 44 | {{- end -}} 45 | {{- end }} 46 |
    47 |
    48 |
    49 | -------------------------------------------------------------------------------- /templates/value.gohtml: -------------------------------------------------------------------------------- 1 | {{- if istype . "rbxapijson.Type" -}} 2 | {{.Name}} 3 | {{- else if istype . "rbxapijson.Parameters" -}} 4 | {{template "parameters" .List}} 5 | {{- else if istype . "[]rbxapijson.Parameter" -}} 6 | {{template "parameters" .}} 7 | {{- else if istype . "string" -}} 8 | {{- if . -}} 9 | {{- tostring . -}} 10 | {{- else -}} 11 | 12 | {{- end -}} 13 | {{- else -}} 14 | {{- tostring . -}} 15 | {{- end -}} 16 | --------------------------------------------------------------------------------