├── .gitmodules ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── package.json ├── cdn ├── solarized-dark.min.css ├── codetabs.js ├── dissectionAnimation.js ├── vanilla-back-to-top.min.js ├── focus-visible.min.js ├── scrollSpy.js ├── sdk.js ├── codeblocks.js ├── embed.js ├── tabs.js ├── button.js ├── buttons.js ├── docsearch.min.css ├── analytics.js └── widgets.js ├── script ├── data.js └── replace.js └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "react-native"] 2 | path = react-native 3 | url = https://github.com/facebook/react-native.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [jaywcjlove] 4 | custom: https://jaywcjlove.github.io/sponsor.html 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | web 3 | 4 | npm-debug.log* 5 | lerna-debug.log 6 | yarn-error.log 7 | package-lock.json 8 | 9 | .DS_Store 10 | .cache 11 | .vscode 12 | .idea 13 | .env 14 | 15 | *.bak 16 | *.tem 17 | *.temp 18 | #.swp 19 | *.*~ 20 | ~*.* 21 | 22 | # IDEA 23 | *.iml 24 | *.ipr 25 | *.iws 26 | .idea/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-doc", 3 | "version": "1.0.0", 4 | "description": "React Native Doc", 5 | "main": "index.js", 6 | "scripts": { 7 | "server": "sgo -d web --fallback index.html", 8 | "start": "node ./script/replace.js", 9 | "deploy": "gh-pages -d web" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "fs-extra": "^8.1.0", 16 | "gh-pages": "^2.1.1", 17 | "sgo": "^2.1.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cdn/solarized-dark.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /cdn/codetabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // Turn off ESLint for this file because it's sent down to users as-is. 9 | /* eslint-disable */ 10 | window.addEventListener('load', function() { 11 | // add event listener for all tab 12 | document.querySelectorAll('.nav-link').forEach(function(el) { 13 | el.addEventListener('click', function(e) { 14 | const groupId = e.target.getAttribute('data-group'); 15 | document 16 | .querySelectorAll(`.nav-link[data-group=${groupId}]`) 17 | .forEach(function(el) { 18 | el.classList.remove('active'); 19 | }); 20 | document 21 | .querySelectorAll(`.tab-pane[data-group=${groupId}]`) 22 | .forEach(function(el) { 23 | el.classList.remove('active'); 24 | }); 25 | e.target.classList.add('active'); 26 | document 27 | .querySelector(`#${e.target.getAttribute('data-tab')}`) 28 | .classList.add('active'); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy react-native-doc 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - v* 8 | jobs: 9 | build-deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Checkout Submodules 14 | shell: bash 15 | run: | 16 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 17 | git submodule sync --recursive 18 | cat .gitmodules 19 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --remote --force --recursive --checkout react-native 20 | 21 | - name: Setup Node 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: '10.x' 25 | 26 | # - name: Cache dependencies 27 | # uses: actions/cache@v1 28 | # with: 29 | # path: ~/.npm 30 | # key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 31 | # restore-keys: | 32 | # ${{ runner.os }}-node- 33 | 34 | - run: npm install 35 | - run: npm run start 36 | - run: rm -rf ./web/CNAME 37 | 38 | - name: Build and Deploy 39 | uses: peaceiris/actions-gh-pages@v2 40 | env: 41 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 42 | PUBLISH_BRANCH: gh-pages 43 | PUBLISH_DIR: ./web 44 | with: 45 | emptyCommits: false 46 | -------------------------------------------------------------------------------- /cdn/dissectionAnimation.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | const section = document.querySelector('.NativeDevelopment'); 3 | const dissection = document.querySelector('.NativeDevelopment .dissection'); 4 | const images = dissection.children; 5 | const numImages = images.length; 6 | 7 | const fadeDistance = 40; 8 | const navbarHeight = 60; 9 | 10 | function clamp(val, min, max) { 11 | return Math.min(max, Math.max(min, val)); 12 | } 13 | 14 | // Scale the percent so that `min` goes to 0% and `max` goes to 100% 15 | function scalePercent(percent, min, max) { 16 | const scale = max - min; 17 | return clamp((percent - min) / scale, 0, 1); 18 | } 19 | 20 | // Get the percentage that the image should be on the screen given 21 | // how much the entire container is scrolled 22 | // so we can fine-tune at what screen % the animation starts and stops 23 | function getImagePercent(index, scrollPercent) { 24 | const start = index / numImages; 25 | return clamp((scrollPercent - start) * numImages, 0, 1); 26 | } 27 | 28 | window.addEventListener('scroll', () => { 29 | const elPos = section.getBoundingClientRect().top - navbarHeight; 30 | const height = window.innerHeight; 31 | const screenPercent = 1 - clamp(elPos / height, 0, 1); 32 | const scaledPercent = scalePercent(screenPercent, 0.2, 0.9); 33 | for (let i = 0; i < numImages; i++) { 34 | const imgPercent = getImagePercent(i, scaledPercent); 35 | images[i].style.opacity = imgPercent; 36 | 37 | const translation = fadeDistance * (1 - imgPercent); 38 | images[i].style.left = `${translation}px`; 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /script/data.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | // ["https://platform.twitter.com/widgets.js", ''], 3 | ['src="/react-native/', 'src="/'], 4 | ['href="/react-native/', 'href="/'], 5 | ['', ''], 6 | [``, ''], 8 | ["https://connect.facebook.net/en_US/sdk.js", "/cdn/sdk.js"], 9 | ["https://facebook.github.io/react-native/blog/atom.xml", "/blog/atom.xml"], 10 | ["https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css", '/cdn/docsearch.min.css'], 11 | ["//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-dark.min.css", '/cdn/solarized-dark.min.css'], 12 | ["https://platform.twitter.com/widgets.js", "/cdn/widgets.js"], 13 | ["https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js", '/cdn/docsearch.min.js'], 14 | ["https://cdn.jsdelivr.net/npm/focus-visible@5.0.2/dist/focus-visible.min.js", '/cdn/focus-visible.min.js'], 15 | ["https://snack.expo.io/embed.js", '/cdn/embed.js'], 16 | ["https://facebook.github.io/react-native/js/codeblocks.js", "/cdn/codeblocks.js"], 17 | ["https://facebook.github.io/react-native/js/tabs.js", "/cdn/tabs.js"], 18 | ["https://unpkg.com/vanilla-back-to-top@7.1.14/dist/vanilla-back-to-top.min.js", "/cdn/vanilla-back-to-top.min.js"], 19 | ["https://facebook.github.io/react-native/js/scrollSpy.js", "/cdn/scrollSpy.js"], 20 | ["https://facebook.github.io/react-native/js/codetabs.js", "/cdn/codetabs.js"], 21 | ["https://facebook.github.io/react-native/js/dissectionAnimation.js", "/cdn/dissectionAnimation.js"], 22 | ["https://www.google-analytics.com/analytics.js", "/cdn/analytics.js"], 23 | ] -------------------------------------------------------------------------------- /script/replace.js: -------------------------------------------------------------------------------- 1 | const data = require("./data"); 2 | const fs = require("fs-extra"); 3 | const path = require("path"); 4 | 5 | const rnPath = path.join(process.cwd(), 'react-native'); 6 | const webPath = path.join(process.cwd(), 'web'); 7 | const cdnPath = path.join(process.cwd(), 'cdn'); 8 | const webCdnPath = path.join(process.cwd(), 'web/cdn'); 9 | 10 | async function format(dir) { 11 | const pathArr = await fs.readdir(dir); 12 | pathArr.forEach(async (fileName) => { 13 | const pathTo = path.join(dir, fileName); 14 | const stat = await fs.stat(pathTo); 15 | if (stat.isFile() && /.html$/.test(fileName)) { 16 | let htmlStr = await fs.readFile(pathTo); 17 | htmlStr = htmlStr.toString(); 18 | let isReplace = false; 19 | data.forEach((htmlArr) => { 20 | if (htmlStr.indexOf(htmlArr[0])) { 21 | isReplace = true; 22 | } 23 | htmlStr = htmlStr.replace(new RegExp(htmlArr[0], 'g'), htmlArr[1] || ''); 24 | }); 25 | await fs.writeFile(pathTo, htmlStr); 26 | console.log(`${isReplace ? '√' : ''}::>`, pathTo); 27 | } else if(stat.isDirectory()){ 28 | await format(pathTo); 29 | } 30 | }); 31 | } 32 | 33 | ;(async () => { 34 | try { 35 | await fs.ensureDir(webPath); 36 | await fs.emptyDir(webPath); 37 | await fs.copy(rnPath, webPath, { 38 | filter: (src) => !regDocPath().test(src) && !regGitPath().test(src) && /(\.circleci|)$/g.test(src) 39 | }); 40 | await fs.copy(cdnPath, webCdnPath); 41 | await format(webPath); 42 | } catch (error) { 43 | console.log('error:', error); 44 | } 45 | })(); 46 | 47 | function regGitPath() { 48 | return /\/react\-native\/(\.git)(?=(\/|$))/; 49 | } 50 | 51 | function regDocPath() { 52 | return /\/react\-native\/docs\/(0.5|0.6|0.7|0.8|0.9|0.10|0.11|0.12|0.13|0.14|0.15|0.16|0.17|0.18|0.19|0.20|0.21|0.22|0.23|0.24|0.25|0.26|0.27|0.28|0.29|0.30|0.31|0.32|0.33|0.34|0.35|0.36|0.37|0.38|0.39|0.40|0.41|0.42|0.43|0.44|0.45|0.46|0.47|0.48|0.49|0.50|0.51|0.52|0.53|0.54|0.55|0.56|0.57|0.58|0.59)(?=(\/|$))/; 53 | } 54 | -------------------------------------------------------------------------------- /cdn/vanilla-back-to-top.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function addBackToTop(){var o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=o.id,e=void 0===t?"back-to-top":t,n=o.showWhenScrollTopIs,i=void 0===n?1:n,r=o.onClickScrollTo,d=void 0===r?0:r,c=o.scrollDuration,a=void 0===c?100:c,s=o.innerHTML,l=void 0===s?'':s,u=o.diameter,m=void 0===u?56:u,p=o.size,b=void 0===p?m:p,h=o.cornerOffset,v=void 0===h?20:h,f=o.backgroundColor,x=void 0===f?"#000":f,w=o.textColor,g=void 0===w?"#fff":w,k=o.zIndex,y=void 0===k?1:k;!function(){var o=Math.round(.43*b),t=Math.round(.29*b),n="#"+e+"{background:"+x+";-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;bottom:"+v+"px;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.26);-moz-box-shadow:0 2px 5px 0 rgba(0,0,0,.26);box-shadow:0 2px 5px 0 rgba(0,0,0,.26);color:"+g+";cursor:pointer;display:block;height:"+b+"px;opacity:1;outline:0;position:fixed;right:"+v+"px;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-transition:bottom .2s,opacity .2s;-o-transition:bottom .2s,opacity .2s;-moz-transition:bottom .2s,opacity .2s;transition:bottom .2s,opacity .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:"+b+"px;z-index:"+y+"}#"+e+" svg{display:block;fill:currentColor;height:"+o+"px;margin:"+t+"px auto 0;width:"+o+"px}#"+e+".hidden{bottom:-"+b+"px;opacity:0}",i=document.createElement("style");i.appendChild(document.createTextNode(n)),document.head.insertAdjacentElement("afterbegin",i)}();var T=function(){var o=document.createElement("div");return o.id=e,o.className="hidden",o.innerHTML=l,o.addEventListener("click",function(o){o.preventDefault(),function(){var o=window,t=o.performance,e=o.requestAnimationFrame;if(a<=0||void 0===t||void 0===e)return C(d);var n=t.now(),i=M(),r=i-d;e(function o(t){var d=t-n,c=Math.min(d/a,1);C(i-Math.round(c*r)),c<1&&e(o)})}()}),document.body.appendChild(o),o}(),E=!0;window.addEventListener("scroll",z),z();function z(){M()>=i?function(){if(!E)return;T.className="",E=!1}():function(){if(E)return;T.className="hidden",E=!0}()}function M(){return document.body.scrollTop||document.documentElement&&document.documentElement.scrollTop||0}function C(o){document.body.scrollTop=o,document.documentElement&&(document.documentElement.scrollTop=o)}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Native Doc 2 | === 3 | 4 |  [](https://hub.docker.com/r/wcjiang/react-native) [](https://hub.docker.com/r/wcjiang/react-native) 5 | 6 | 这里是本地离线预览 [React Native](https://github.com/facebook/react-native/) 官方文档的方法,解决因官网 CDN 资源导致无法打开官方文档网站,已经将处理好的 HTML 存放在 [gh-pages](https://github.com/jaywcjlove/react-native-doc/tree/gh-pages) 分支,只需克隆配合 [sgo](https://github.com/jaywcjlove/sgo) 工具预览即可。 7 | 8 | ## Docker 部署 9 | 10 | > Port: `60005` - [react-native-doc](https://facebook.github.io/react-native/) [@jaywcjlove/docs/react-native](https://github.com/jaywcjlove/docs) 11 | 12 | ```shell 13 | docker pull wcjiang/react-native:latest 14 | ``` 15 | 16 | Run Server 17 | 18 | ```shell 19 | docker run --name react-native -p 60005:60005 --restart=always -d wcjiang/react-native:latest 20 | ``` 21 | 22 | ## 下载工程 23 | 24 | ```bash 25 | # 克隆并下载带有 submodule 的项目 26 | git clone https://github.com/jaywcjlove/react-native-doc.git --depth=1 --recurse-submodules 27 | ``` 28 | 29 | 参数 `--recurse-submodules` 会克隆太久 `react-native`。 30 | 31 | ```bash 32 | # 克隆项目 33 | git clone https://github.com/jaywcjlove/react-native-doc.git --depth=1 34 | # 初始化 submodule 子项目 35 | git submodule update --depth 1 --init --recursive 36 | # 更新 submodule 子项目 37 | git submodule update --recursive --remote 38 | ``` 39 | 40 | 参数 `--depth` 只有 [git@2.23.0-rc2](https://github.com/git/git/commit/275cd184d52b5b81cb89e4ec33e540fb2ae61c1f) 支持 41 | 42 | ## 安装依赖 43 | 44 | ```bash 45 | npm install 46 | ``` 47 | 48 | ## 替换 CDN 资源 49 | 50 | 通过下面命令批量替换 CDN 资源,运行之前确保 `react-native` 目录下载完成,使用编辑器替换,内容太多会让编辑器卡死。替换内容在这里 [`script/data.js`](script/data.js) 51 | 52 | ```bash 53 | npm run start 54 | ``` 55 | 56 | 更新 React Native 主文档仓库 57 | 58 | ```bash 59 | # 进入 React Native 仓库 60 | cd react-native/ 61 | # 放弃本地修改内容 62 | git reset --hard 63 | cd ../ 64 | # 更新 submodule 子项目 65 | git submodule update --recursive --remote 66 | ``` 67 | 68 | ## 启动服务 69 | 70 | ``` 71 | npm run server 72 | ``` 73 | 74 | ## 注意 75 | 76 | 虽然本地预览静态服务,但仍有很多链接是走 `CDN`,通过运行脚本来替换 77 | 78 | ## Links 79 | 80 | - [@jaywcjlove/docs](https://github.com/jaywcjlove/docs) 81 | - [@jaywcjlove/react-native-doc](https://github.com/jaywcjlove/react-native-doc) 82 | - [Docker Repository.](https://hub.docker.com/r/wcjiang/react-native) 83 | -------------------------------------------------------------------------------- /cdn/focus-visible.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define(t):t()}(0,function(){"use strict";function e(e){var t=!0,n=!1,o=null,d={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function i(e){return!!(e&&e!==document&&"HTML"!==e.nodeName&&"BODY"!==e.nodeName&&"classList"in e&&"contains"in e.classList)}function s(e){e.classList.contains("focus-visible")||(e.classList.add("focus-visible"),e.setAttribute("data-focus-visible-added",""))}function u(e){t=!1}function a(){document.addEventListener("mousemove",c),document.addEventListener("mousedown",c),document.addEventListener("mouseup",c),document.addEventListener("pointermove",c),document.addEventListener("pointerdown",c),document.addEventListener("pointerup",c),document.addEventListener("touchmove",c),document.addEventListener("touchstart",c),document.addEventListener("touchend",c)}function c(e){e.target.nodeName&&"html"===e.target.nodeName.toLowerCase()||(t=!1,document.removeEventListener("mousemove",c),document.removeEventListener("mousedown",c),document.removeEventListener("mouseup",c),document.removeEventListener("pointermove",c),document.removeEventListener("pointerdown",c),document.removeEventListener("pointerup",c),document.removeEventListener("touchmove",c),document.removeEventListener("touchstart",c),document.removeEventListener("touchend",c))}document.addEventListener("keydown",function(n){n.metaKey||n.altKey||n.ctrlKey||(i(e.activeElement)&&s(e.activeElement),t=!0)},!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",function(e){"hidden"==document.visibilityState&&(n&&(t=!0),a())},!0),a(),e.addEventListener("focus",function(e){var n,o,u;i(e.target)&&(t||(n=e.target,o=n.type,"INPUT"==(u=n.tagName)&&d[o]&&!n.readOnly||"TEXTAREA"==u&&!n.readOnly||n.isContentEditable))&&s(e.target)},!0),e.addEventListener("blur",function(e){var t;i(e.target)&&(e.target.classList.contains("focus-visible")||e.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(o),o=window.setTimeout(function(){n=!1,window.clearTimeout(o)},100),(t=e.target).hasAttribute("data-focus-visible-added")&&(t.classList.remove("focus-visible"),t.removeAttribute("data-focus-visible-added")))},!0),e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&e.host?e.host.setAttribute("data-js-focus-visible",""):e.nodeType===Node.DOCUMENT_NODE&&document.documentElement.classList.add("js-focus-visible")}if("undefined"!=typeof window&&"undefined"!=typeof document){var t;window.applyFocusVisiblePolyfill=e;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(e){(t=document.createEvent("CustomEvent")).initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}"undefined"!=typeof document&&e(document)}); 2 | //# sourceMappingURL=focus-visible.min.js.map -------------------------------------------------------------------------------- /cdn/scrollSpy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /* eslint-disable prefer-arrow-callback */ 9 | (function scrollSpy() { 10 | const OFFSET = 10; 11 | let timer; 12 | let headingsCache; 13 | const findHeadings = function findHeadings() { 14 | return headingsCache || document.querySelectorAll('.toc-headings > li > a'); 15 | }; 16 | const onScroll = function onScroll() { 17 | if (timer) { 18 | // throttle 19 | return; 20 | } 21 | timer = setTimeout(function() { 22 | timer = null; 23 | let activeNavFound = false; 24 | const headings = findHeadings(); // toc nav anchors 25 | /** 26 | * On every call, try to find header right after <-- next header 27 | * the one whose content is on the current screen <-- highlight this 28 | */ 29 | for (let i = 0; i < headings.length; i++) { 30 | // headings[i] is current element 31 | // if an element is already active, then current element is not active 32 | // if no element is already active, then current element is active 33 | let currNavActive = !activeNavFound; 34 | /** 35 | * Enter the following check up only when an active nav header is not yet found 36 | * Then, check the bounding rectangle of the next header 37 | * The headers that are scrolled passed will have negative bounding rect top 38 | * So the first one with positive bounding rect top will be the nearest next header 39 | */ 40 | if (currNavActive && i < headings.length - 1) { 41 | const heading = headings[i + 1]; 42 | const next = decodeURIComponent(heading.href.split('#')[1]); 43 | const nextHeader = document.getElementById(next); 44 | 45 | if (nextHeader) { 46 | const top = nextHeader.getBoundingClientRect().top; 47 | currNavActive = top > OFFSET; 48 | } else { 49 | console.error('Can not find header element', { 50 | id: next, 51 | heading, 52 | }); 53 | } 54 | } 55 | /** 56 | * Stop searching once a first such header is found, 57 | * this makes sure the highlighted header is the most current one 58 | */ 59 | if (currNavActive) { 60 | activeNavFound = true; 61 | headings[i].classList.add('active'); 62 | } else { 63 | headings[i].classList.remove('active'); 64 | } 65 | } 66 | }, 100); 67 | }; 68 | 69 | document.addEventListener('scroll', onScroll); 70 | document.addEventListener('resize', onScroll); 71 | document.addEventListener('DOMContentLoaded', function() { 72 | // Cache the headings once the page has fully loaded. 73 | headingsCache = findHeadings(); 74 | onScroll(); 75 | }); 76 | })(); 77 | -------------------------------------------------------------------------------- /cdn/sdk.js: -------------------------------------------------------------------------------- 1 | /*1565764635,,JIT Construction: v1001055920,en_US*/ 2 | 3 | /** 4 | * Copyright (c) 2017-present, Facebook, Inc. All rights reserved. 5 | * 6 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, 7 | * copy, modify, and distribute this software in source code or binary form for use 8 | * in connection with the web services and APIs provided by Facebook. 9 | * 10 | * As with any software that integrates with the Facebook platform, your use of 11 | * this software is subject to the Facebook Platform Policy 12 | * [http://developers.facebook.com/policy/]. This copyright notice shall be 13 | * included in all 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, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | (function _(a, b, c, d, e) { var f = window.console; f && Math.floor(new Date().getTime() / 1e3) - b > 7 * 24 * 60 * 60 && f.warn("The Facebook JSSDK is more than 7 days old."); if (window[c]) return; if (!window.JSON) return; var g = window[c] = { __buffer: { replay: function () { var a = this, b = function (d) { var b = window[c]; a.calls[d][0].split(".").forEach(function (a) { return b = b[a] }); b.apply(null, a.calls[d][1]) }; for (var d = 0; d < this.calls.length; d++)b(d); this.calls = [] }, calls: [], opts: null }, getUserID: function () { return "" }, getAuthResponse: function () { return null }, getAccessToken: function () { return null }, init: function (a) { g.__buffer.opts = a } }; for (var b = 0; b < d.length; b++) { f = d[b]; if (f in g) continue; var h = f.split("."), i = h.pop(), j = g; for (var k = 0; k < h.length; k++)j = j[h[k]] || (j[h[k]] = {}); j[i] = function (a) { if (a === "init") return; return function () { g.__buffer.calls.push([a, Array.prototype.slice.call(arguments)]) } }(f) } k = a; h = /Chrome\/(\d+)/.exec(navigator.userAgent); h && Number(h[1]) >= 55 && "assign" in Object && "findIndex" in [] && (k += "&ua=modern_es6"); j = document.createElement("script"); j.src = k; j.async = !0; e && (j.crossOrigin = "anonymous"); i = document.getElementsByTagName("script")[0]; i.parentNode && i.parentNode.insertBefore(j, i) })("https:\/\/connect.facebook.net\/en_US\/sdk.js?hash=0ccaaa40544e745bb71b2094bb7a8e02", 1565764635, "FB", ["AppEvents.EventNames", "AppEvents.ParameterNames", "AppEvents.activateApp", "AppEvents.clearAppVersion", "AppEvents.clearUserID", "AppEvents.getAppVersion", "AppEvents.getUserID", "AppEvents.logEvent", "AppEvents.logPageView", "AppEvents.logPurchase", "AppEvents.setAppVersion", "AppEvents.setUserID", "AppEvents.updateUserProperties", "Canvas.Plugin.showPluginElement", "Canvas.Plugin.hidePluginElement", "Canvas.Prefetcher.addStaticResource", "Canvas.Prefetcher.setCollectionMode", "Canvas.getPageInfo", "Canvas.scrollTo", "Canvas.setAutoGrow", "Canvas.setDoneLoading", "Canvas.setSize", "Canvas.setUrlHandler", "Canvas.startTimer", "Canvas.stopTimer", "Event.subscribe", "Event.unsubscribe", "XFBML.parse", "addFriend", "api", "getAccessToken", "getAuthResponse", "getLoginStatus", "getUserID", "init", "login", "logout", "publish", "share", "ui"], true); -------------------------------------------------------------------------------- /cdn/codeblocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /* eslint-disable module-strict */ 9 | 10 | (function() { 11 | 'use strict'; 12 | if (typeof document === 'undefined') { 13 | // Not on browser 14 | return; 15 | } 16 | 17 | document.addEventListener('DOMContentLoaded', init); 18 | 19 | function init() { 20 | var mobile = isMobile(); 21 | 22 | var webPlayerList = document.querySelectorAll('.web-player'); 23 | 24 | // Either show interactive or static code block, depending on desktop or mobile 25 | for (var i = 0; i < webPlayerList.length; ++i) { 26 | webPlayerList[i].classList.add(mobile ? 'mobile' : 'desktop'); 27 | 28 | if (!mobile) { 29 | // Determine location to look up required assets 30 | var assetRoot = encodeURIComponent( 31 | document.location.origin + '/react-native' 32 | ); 33 | 34 | // Set iframe src. Do this dynamically so the iframe never loads on mobile. 35 | var iframe = webPlayerList[i].querySelector('iframe'); 36 | iframe.src = 37 | iframe.getAttribute('data-src') + '&assetRoot=' + assetRoot; 38 | } 39 | } 40 | 41 | window.ExpoSnack && window.ExpoSnack.initialize(); 42 | 43 | var snackPlayerList = document.querySelectorAll('.snack-player'); 44 | 45 | // Either show interactive or static code block, depending on desktop or mobile 46 | for (var i = 0; i < snackPlayerList.length; ++i) { 47 | var snackPlayer = snackPlayerList[i]; 48 | var snackDesktopPlayer = snackPlayer.querySelectorAll( 49 | '.desktop-friendly-snack' 50 | )[0]; 51 | var plainCodeExample = snackPlayer.querySelectorAll( 52 | '.mobile-friendly-snack' 53 | )[0]; 54 | 55 | if (mobile) { 56 | snackDesktopPlayer.remove(); 57 | plainCodeExample.style.display = 'block'; 58 | } else { 59 | plainCodeExample.remove(); 60 | } 61 | } 62 | 63 | var backdrop = document.querySelector('.modal-backdrop'); 64 | if (!backdrop) { 65 | return; 66 | } 67 | 68 | var modalButtonOpenList = document.querySelectorAll('.modal-button-open'); 69 | var modalButtonClose = document.querySelector('.modal-button-close'); 70 | 71 | backdrop.addEventListener('click', hideModal); 72 | modalButtonClose.addEventListener('click', hideModal); 73 | 74 | // Bind event to NodeList items 75 | for (var i = 0; i < modalButtonOpenList.length; ++i) { 76 | modalButtonOpenList[i].addEventListener('click', showModal); 77 | } 78 | } 79 | 80 | function showModal(e) { 81 | var backdrop = document.querySelector('.modal-backdrop'); 82 | if (!backdrop) { 83 | return; 84 | } 85 | 86 | var modal = document.querySelector('.modal'); 87 | 88 | backdrop.classList.add('modal-open'); 89 | modal.classList.add('modal-open'); 90 | } 91 | 92 | function hideModal(e) { 93 | var backdrop = document.querySelector('.modal-backdrop'); 94 | if (!backdrop) { 95 | return; 96 | } 97 | 98 | var modal = document.querySelector('.modal'); 99 | 100 | backdrop.classList.remove('modal-open'); 101 | modal.classList.remove('modal-open'); 102 | } 103 | 104 | // Primitive mobile detection 105 | function isMobile() { 106 | return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 107 | navigator.userAgent 108 | ); 109 | } 110 | })(); 111 | -------------------------------------------------------------------------------- /cdn/embed.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | if (window.ExpoSnack) { 4 | return; 5 | } 6 | 7 | var ExpoSnack = { 8 | append: function(container, options) { 9 | options = options || {}; 10 | options.id = options.id || container.dataset.snackId || container.dataset.sketchId; 11 | options.platform = options.platform || container.dataset.snackPlatform || container.dataset.sketchPlatform; 12 | options.preview = options.preview || container.dataset.snackPreview || container.dataset.sketchPreview; 13 | options.sdkVersion = options.sdkVersion || container.dataset.snackSdkVersion; 14 | options.name = options.name || container.dataset.snackName; 15 | options.description = options.description || container.dataset.snackDescription; 16 | options.theme = options.theme || container.dataset.snackTheme; 17 | options.appetizePayerCode = options.appetizePayerCode || container.dataset.snackAppetizePayerCode; 18 | 19 | if (!options.code && container.dataset.snackCode) { 20 | options.code = decodeURIComponent(container.dataset.snackCode); 21 | } 22 | 23 | if (container.querySelector('iframe[data-snack-iframe]')) { 24 | return; 25 | } 26 | 27 | if (!options.id && !options.code) { 28 | return; 29 | } 30 | 31 | var iframeId = Math.random().toString(36).substr(2, 10); 32 | var iframe = document.createElement('iframe'); 33 | 34 | var iframeQueryParams = '?preview=' + options.preview 35 | + '&platform=' + options.platform 36 | + '&iframeId=' + iframeId; 37 | 38 | if (options.sdkVersion) { 39 | iframeQueryParams += '&sdkVersion=' + options.sdkVersion; 40 | } 41 | if (options.name) { 42 | iframeQueryParams += '&name=' + options.name; 43 | } 44 | if (options.description) { 45 | iframeQueryParams += '&description=' + options.description; 46 | } 47 | if (options.theme) { 48 | iframeQueryParams += '&theme=' + options.theme; 49 | } 50 | if (options.appetizePayerCode) { 51 | iframeQueryParams += '&appetizePayerCode=' + options.appetizePayerCode 52 | } 53 | 54 | if (options.id) { 55 | iframe.src = 'https://snack.expo.io/embedded/' + options.id + iframeQueryParams; 56 | } else { 57 | iframe.src = 'https://snack.expo.io/embedded' + iframeQueryParams + '&waitForData=true'; 58 | } 59 | iframe.style = 'display: block'; 60 | iframe.height = '100%'; 61 | iframe.width = '100%'; 62 | iframe.frameBorder = '0'; 63 | iframe.allowTransparency = true; 64 | iframe.dataset.snackIframe = true; 65 | 66 | container.appendChild(iframe); 67 | 68 | if (options.code) { 69 | window.addEventListener('message', function(event) { 70 | var eventName = event.data[0]; 71 | var data = event.data[1]; 72 | if (eventName === 'expoFrameLoaded' && data.iframeId === iframeId) { 73 | iframe.contentWindow.postMessage(['expoDataEvent', { 74 | iframeId: iframeId, 75 | code: options.code, 76 | }], '*') 77 | } 78 | }); 79 | } 80 | }, 81 | 82 | remove: function(container) { 83 | var iframe = container.querySelector('iframe[data-snack-iframe]'); 84 | 85 | if (iframe) { 86 | iframe.parentNode.removeChild(iframe); 87 | } 88 | }, 89 | 90 | initialize: function() { 91 | document.querySelectorAll('[data-sketch-id], [data-snack-id], [data-snack-code]').forEach(function(element) { 92 | ExpoSnack.append(element); 93 | }); 94 | } 95 | } 96 | 97 | if (document.readyState === "complete") { 98 | ExpoSnack.initialize(); 99 | } else { 100 | document.addEventListener('readystatechange', function() { 101 | if (document.readyState === "complete") { 102 | ExpoSnack.initialize(); 103 | } 104 | }); 105 | } 106 | 107 | window.ExpoSnack = ExpoSnack; 108 | window.ExpoSketch = ExpoSnack; 109 | })(); 110 | -------------------------------------------------------------------------------- /cdn/tabs.js: -------------------------------------------------------------------------------- 1 | const currentTabs = {}; 2 | 3 | function isActiveBlock(block) { 4 | for (let tab of Object.values(currentTabs)) { 5 | if (!block.classList.contains(tab)) { 6 | return false; 7 | } 8 | } 9 | return true; 10 | } 11 | 12 | function displayTab(type, value) { 13 | const list = document.getElementById(`toggle-${type}`); 14 | // short circuit if there is no toggle for this tab type 15 | if (!list) { 16 | return; 17 | } 18 | currentTabs[type] = value; 19 | [...list.children].forEach(child => { 20 | if (child.classList.contains(`button-${value}`)) { 21 | child.classList.add('active'); 22 | child.setAttribute('aria-selected', true); 23 | } else { 24 | child.classList.remove('active'); 25 | child.setAttribute('aria-selected', false); 26 | } 27 | }); 28 | 29 | var container = document.getElementsByTagName('block')[0].parentNode; 30 | [...container.children].forEach(child => { 31 | if (child.tagName !== 'BLOCK') { 32 | return; 33 | } 34 | if (isActiveBlock(child)) { 35 | child.classList.add('active'); 36 | } else { 37 | child.classList.remove('active'); 38 | } 39 | }); 40 | } 41 | function convertBlocks() { 42 | // Convert