├── .nvmrc ├── src ├── app │ ├── shared │ │ ├── pipes │ │ │ └── .gitkeep │ │ ├── directives │ │ │ └── .gitkeep │ │ ├── interceptors │ │ │ └── .gitkeep │ │ ├── shared.module.ts │ │ └── custom-material.module.ts │ ├── presentation │ │ ├── faq │ │ │ ├── faq.component.scss │ │ │ ├── faq.component.html │ │ │ ├── faq.component.ts │ │ │ └── faq.component.spec.ts │ │ ├── practise │ │ │ ├── practise.component.scss │ │ │ ├── practise.component.html │ │ │ ├── practise.component.ts │ │ │ └── practise.component.spec.ts │ │ ├── case-study │ │ │ ├── case-study.component.scss │ │ │ ├── case-study.component.html │ │ │ ├── case-study-routing.module.ts │ │ │ ├── cases.ts │ │ │ ├── case-study.module.ts │ │ │ ├── case-study.component.ts │ │ │ └── case-study.component.spec.ts │ │ ├── skill-tree │ │ │ ├── skill-tree.component.scss │ │ │ ├── skill-tree.component.html │ │ │ ├── skill-tree.module.ts │ │ │ ├── skill-tree.component.ts │ │ │ └── skill-tree.component.spec.ts │ │ ├── pattern │ │ │ ├── pattern.component.scss │ │ │ ├── pattern.component.html │ │ │ ├── pattern.component.ts │ │ │ └── pattern.component.spec.ts │ │ ├── maturity │ │ │ ├── maturity.component.html │ │ │ ├── maturity.component.scss │ │ │ ├── maturity.module.ts │ │ │ ├── maturity.component.spec.ts │ │ │ └── maturity.component.ts │ │ ├── home │ │ │ ├── contributiors.ts │ │ │ ├── home.component.html │ │ │ ├── home.module.ts │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ └── home.component.scss │ │ └── checklists │ │ │ ├── checklists.component.html │ │ │ ├── checklists.component.scss │ │ │ ├── checklists-routing.module.ts │ │ │ ├── checklists.module.ts │ │ │ ├── checklists.component.ts │ │ │ └── checklists.component.spec.ts │ ├── app.component.scss │ ├── globals.d.ts │ ├── webpack-translate-loader.ts │ ├── app.component.ts │ ├── app-routing.module.ts │ ├── app.module.ts │ ├── app.component.spec.ts │ └── app.component.html ├── assets │ ├── docs │ │ ├── skilltrees │ │ │ └── sample.md │ │ ├── CNAME │ │ ├── casestudies │ │ │ ├── baidu.md │ │ │ ├── huawei.md │ │ │ ├── alibaba.md │ │ │ ├── pingcap.md │ │ │ ├── thoughtworks.md │ │ │ ├── google.md │ │ │ ├── tencent.md │ │ │ └── microsoft.md │ │ ├── checklists │ │ │ ├── opensource-release.md │ │ │ ├── opensource-security.md │ │ │ ├── opensource-development.md │ │ │ └── opensource-deployment.md │ │ ├── README.md │ │ ├── maturities │ │ │ ├── evangelist.md │ │ │ └── project.md │ │ ├── home.md │ │ ├── faq.md │ │ ├── practise.md │ │ └── pattern.md │ ├── icons │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ ├── images │ │ ├── agile-fluency.png │ │ ├── mdb-fluency.png │ │ ├── arrow.svg │ │ ├── github-circle-white-transparent.svg │ │ ├── purelogo.svg │ │ ├── logo.svg │ │ ├── logo_pure.svg │ │ ├── mdb.svg │ │ ├── arch-level.svg │ │ └── esp.svg │ ├── resources │ │ └── images │ │ │ ├── arrow-right-thin.svg │ │ │ ├── avatar.svg │ │ │ ├── arrow.svg │ │ │ └── avatar-new.svg │ ├── scully-routes.json │ └── i18n │ │ ├── en.json │ │ └── zh-cn.json ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── environments │ ├── environment.ci.ts │ ├── environment.prod.ts │ └── environment.ts ├── README.md ├── styles │ ├── _color.scss │ ├── _behavior.scss │ ├── _mobile.scss │ ├── _dragula.scss │ ├── _periodic-variables.scss │ ├── _material-ui.scss │ ├── intro.scss │ ├── _markdown-render.scss │ └── _markdown.scss ├── main.ts ├── test.ts ├── index.html ├── styles.scss └── polyfills.ts ├── .adr.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── pr.yml │ ├── ci.yml │ └── deploy.yml ├── tsconfig.app.json ├── .editorconfig ├── tsconfig.spec.json ├── browserslist ├── README.md ├── .stylelintrc.json ├── ngsw-config.json ├── .gitignore ├── tsconfig.json ├── karma.conf.js ├── tslint.json ├── scully.ledge.config.js ├── package.json └── angular.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.16.1 2 | -------------------------------------------------------------------------------- /src/app/shared/pipes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/directives/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/interceptors/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/docs/skilltrees/sample.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/presentation/faq/faq.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/docs/CNAME: -------------------------------------------------------------------------------- 1 | opensource.phodal.com 2 | -------------------------------------------------------------------------------- /src/app/presentation/practise/practise.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/baidu.md: -------------------------------------------------------------------------------- 1 | # 百度(baidu) 2 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/presentation/skill-tree/skill-tree.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/huawei.md: -------------------------------------------------------------------------------- 1 | # 华为(huawei) 2 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/alibaba.md: -------------------------------------------------------------------------------- 1 | # 阿里巴巴 (Alibaba) 2 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/pingcap.md: -------------------------------------------------------------------------------- 1 | # PingCap (TBD) 2 | 3 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/thoughtworks.md: -------------------------------------------------------------------------------- 1 | # ThoughtWorks 2 | -------------------------------------------------------------------------------- /src/assets/docs/checklists/opensource-release.md: -------------------------------------------------------------------------------- 1 | # 开源软件发布检查清单 2 | -------------------------------------------------------------------------------- /src/assets/docs/checklists/opensource-security.md: -------------------------------------------------------------------------------- 1 | # 开源安全检查清单 2 | -------------------------------------------------------------------------------- /.adr.json: -------------------------------------------------------------------------------- 1 | {"language":"zh-cn","path":"docs/adr/","prefix":"","digits":4} -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/presentation/pattern/pattern.component.scss: -------------------------------------------------------------------------------- 1 | img { 2 | width: 400px; 3 | } 4 | -------------------------------------------------------------------------------- /src/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/favicon-16x16.png -------------------------------------------------------------------------------- /src/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/favicon-32x32.png -------------------------------------------------------------------------------- /src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/apple-touch-icon.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: phodal 4 | patreon: phodal 5 | -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/environments/environment.ci.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | ci: true, 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | ci: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/images/agile-fluency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/images/agile-fluency.png -------------------------------------------------------------------------------- /src/assets/images/mdb-fluency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/opensource/HEAD/src/assets/images/mdb-fluency.png -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .button-logo { 2 | margin-left: 104px; 3 | 4 | .logo { 5 | width: 148px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/docs/README.md: -------------------------------------------------------------------------------- 1 | # 内容修改指南 2 | 3 | 对应的内容: 4 | 5 | - [模式与原则](pattern.md) 6 | 7 | 案例学习详见:[casestudies](casestudies) 8 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/google.md: -------------------------------------------------------------------------------- 1 | # 谷歌(Google) 2 | 3 | 相关资源:[https://opensource.google/](https://opensource.google/) 4 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # DevOps Deploy Repository 2 | 3 | Code see in [https://github.com/phodal/ledge/](https://github.com/phodal/ledge/) 4 | -------------------------------------------------------------------------------- /src/app/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md'; 2 | 3 | declare module '*.json' { 4 | const value: any; 5 | export default value; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/presentation/maturity/maturity.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/presentation/skill-tree/skill-tree.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/presentation/faq/faq.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/presentation/home/contributiors.ts: -------------------------------------------------------------------------------- 1 | export interface Contributor { 2 | name: string; 3 | link: string; 4 | work: string; 5 | title: string; 6 | avatar: string; 7 | } 8 | 9 | export const contributors: Contributor[] = []; 10 | -------------------------------------------------------------------------------- /src/app/presentation/pattern/pattern.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/presentation/practise/practise.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/resources/images/arrow-right-thin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/scully-routes.json: -------------------------------------------------------------------------------- 1 | [{"route":"/pattern"},{"route":"/practise"},{"route":"/faq"},{"route":"/home"},{"route":"/skilltree/sample"},{"route":"/maturity/evangelist"},{"route":"/maturity/project"},{"route":"/case-study/sample"},{"route":"/checklists/opensource-release"},{"route":"/"}] -------------------------------------------------------------------------------- /src/assets/docs/maturities/evangelist.md: -------------------------------------------------------------------------------- 1 | # 开源布道师能力模型 2 | 3 | ![Developer Relations: A Five-Level Maturity Model](http://softwareas.com/developer-relations-a-five-level-maturity-model/) 4 | 5 | - 级别 0:没有开发人员关系 6 | - 级别 1:非正式 7 | - 级别 2:高触感 8 | - 级别 3:布道 9 | - 级别 4:倡导 10 | - 级别 5:量化 11 | -------------------------------------------------------------------------------- /src/assets/docs/checklists/opensource-development.md: -------------------------------------------------------------------------------- 1 | # 开源开发检查清单 2 | 3 | 4 | 来源:[开源软件合规实践](https://jimmysong.io/blog/open-source-compliance-practices/) 5 | 6 | 7 | - 批准将传入代码集成到产品的源代码存储库之前的核对表 8 | - 确保履行义务的清单 9 | - 开发人员的清单 10 | - 工程经理的清单 11 | - 合规人员清单 12 | - 开源法律人员的清单 13 | - 软件采购人员清单 14 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/styles/_color.scss: -------------------------------------------------------------------------------- 1 | $black: #111; 2 | $white: #fff; 3 | $purple: #7753df; 4 | $background-color: #f9f9f9; 5 | $green: #5b8e5b; 6 | $tooltip-bg: #7753df; 7 | 8 | $error-color: red; 9 | 10 | $lighter-gray: #e0e1e9; 11 | $border-color: $lighter-gray; 12 | $mid-gray: #ccc; 13 | $hover-grey: #f3f4f5; 14 | -------------------------------------------------------------------------------- /src/app/presentation/maturity/maturity.component.scss: -------------------------------------------------------------------------------- 1 | .maturity { 2 | width: 80%; 3 | max-width: 1200px; 4 | margin: 66px auto 0; 5 | height: calc(100vh - 66px); 6 | } 7 | 8 | .maturity-outline { 9 | font-size: 20px; 10 | text-indent: 50px; 11 | line-height: 30px; 12 | padding: 50px; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/webpack-translate-loader.ts: -------------------------------------------------------------------------------- 1 | import { TranslateLoader } from '@ngx-translate/core'; 2 | import { Observable, from } from 'rxjs'; 3 | 4 | export class WebpackTranslateLoader implements TranslateLoader { 5 | getTranslation(lang: string): Observable { 6 | return from(import(`../assets/i18n/${lang}.json`)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [], 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/images/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | # 使用单引号 11 | quote_type = single 12 | 13 | [*.md] 14 | max_line_length = off 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/color'; 2 | 3 | .checklists { 4 | width: 80%; 5 | max-width: 1200px; 6 | margin: 2em auto; 7 | } 8 | 9 | .checklist-block { 10 | background: #fff; 11 | padding: 2em; 12 | margin-bottom: 1em; 13 | border-radius: 2px; 14 | box-shadow: 0 1px 0 0 $lighter-gray; 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/_behavior.scss: -------------------------------------------------------------------------------- 1 | .noselect { 2 | -webkit-touch-callout: none; /* iOS Safari */ 3 | -webkit-user-select: none; /* Safari */ 4 | -khtml-user-select: none; /* Konqueror HTML */ 5 | -moz-user-select: none; /* Old versions of Firefox */ 6 | -ms-user-select: none; /* Internet Explorer/Edge */ 7 | user-select: none; 8 | /* Non-prefixed version, currently supported by Chrome, Opera and Firefox */ 9 | } 10 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/app/presentation/practise/practise.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import * as mdData from 'raw-loader!../../../assets/docs/practise.md'; 3 | 4 | @Component({ 5 | selector: 'app-practise', 6 | templateUrl: './practise.component.html', 7 | styleUrls: ['./practise.component.scss'], 8 | }) 9 | export class PractiseComponent implements OnInit { 10 | data = mdData.default; 11 | constructor() {} 12 | 13 | ngOnInit(): void {} 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/_mobile.scss: -------------------------------------------------------------------------------- 1 | .ledge-side-menu { 2 | button { 3 | padding: 0.5em; 4 | width: 8em; 5 | } 6 | } 7 | @media screen and (max-width: 1366px) { 8 | .button-logo { 9 | margin-left: 0 !important; 10 | } 11 | } 12 | 13 | @media screen and (max-width: 1024px) { 14 | .left-drawer { 15 | display: none !important; 16 | } 17 | 18 | body { 19 | min-width: auto!important; 20 | } 21 | 22 | .markdown-toc .right-content { 23 | height: 100%; 24 | width: 100%; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { CaseStudyComponent } from './case-study.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: ':case', 8 | component: CaseStudyComponent, 9 | }, 10 | { path: '', pathMatch: 'full', redirectTo: 'microsoft' }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class CaseStudyRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/app/presentation/faq/faq.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import * as mdData from 'raw-loader!../../../assets/docs/faq.md'; 3 | import { Title } from '@angular/platform-browser'; 4 | 5 | @Component({ 6 | selector: 'app-faq', 7 | templateUrl: './faq.component.html', 8 | styleUrls: ['./faq.component.scss'], 9 | }) 10 | export class FaqComponent implements OnInit { 11 | data = mdData.default; 12 | 13 | constructor(title: Title) { 14 | title.setTitle('开源常见问题 - 开源知识平台'); 15 | } 16 | 17 | ngOnInit(): void {} 18 | } 19 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { ChecklistsComponent } from './checklists.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: ':name', 8 | component: ChecklistsComponent, 9 | }, 10 | { path: '', pathMatch: 'full', redirectTo: 'opensource-release' }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class ChecklistsRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/app/presentation/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

- OpenSource -

4 |

5 | {{ 'opensource-introduction' | translate}} 6 |

7 |
8 |

9 | -> GitHub 10 |

11 |
12 | 13 |
14 | 16 | 17 | 18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /src/app/presentation/pattern/pattern.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | import * as mdData from 'raw-loader!../../../assets/docs/pattern.md'; 4 | 5 | @Component({ 6 | selector: 'app-pattern', 7 | templateUrl: './pattern.component.html', 8 | styleUrls: ['./pattern.component.scss'], 9 | }) 10 | export class PatternComponent implements OnInit { 11 | data = mdData.default; 12 | 13 | constructor(title: Title) { 14 | title.setTitle('原则与模式 - 开源知识平台'); 15 | } 16 | 17 | ngOnInit(): void {} 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/resources/images/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opensource —— 『开源知识平台』 2 | 3 | Homepage: [https://opensource.phodal.com/](https://opensource.phodal.com/) 4 | 5 | ![Badge](https://img.shields.io/badge/Poweredby-%40ledge--framework%2Fengine-brightgreen) 6 | 7 | > OpenSource 是一个基于 [Ledge](https://github.com/ledge-framework/engine/) 的开源『开源知识平台』 8 | 9 | 10 | 迎您在这个项目的 Issue 中留下您的宝贵意见,以帮助其他/她人更好地学习 DevOps 相关的知识。它可以是: 11 | 12 | - 修改手误的文本 13 | - 针对不合时宜内容的评论 14 | - 更好地开源实践 15 | - 缺失的内容引用 16 | - 相关的工具推荐 17 | - …… 18 | 19 | ## License 20 | 21 | @ 2020 A [Phodal Huang](https://www.phodal.com)'s [Idea](http://github.com/phodal/ideas). This code is distributed under the MPL license. See `LICENSE` in this directory. 22 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/cases.ts: -------------------------------------------------------------------------------- 1 | import { DocRoute } from '@ledge-framework/view/lib/ledge-multiple-docs/doc-route.model'; 2 | 3 | export type Cases = Array; 4 | 5 | // todo: 优先级根据内容的质量重新排序。现在的是后来的在后面 + 内容多的在前面,随机组合 6 | export const cases: Cases = [ 7 | { displayName: '微软', source: 'microsoft' }, 8 | { displayName: '阿里巴巴', source: 'alibaba' }, 9 | { displayName: 'Google', source: 'google' }, 10 | { displayName: '腾讯', source: 'tencent' }, 11 | { displayName: '百度', source: 'baidu' }, 12 | { displayName: '华为', source: 'huawei' }, 13 | { displayName: 'PingCap', source: 'pingcap' }, 14 | { displayName: 'ThoughtWorks', source: 'thoughtworks' }, 15 | ]; 16 | -------------------------------------------------------------------------------- /src/assets/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "case-study": "Cases", 4 | "tips": { 5 | "case-study": "", 6 | "patterns": "", 7 | "practises": "", 8 | "manual": "", 9 | "solution": "", 10 | "think-tank": "", 11 | "tool": "", 12 | "resources": "", 13 | "faq": "" 14 | }, 15 | "faq": "FAQ", 16 | "patterns": "Patterns", 17 | "practises": "Practises", 18 | "manual": "Manual", 19 | "solution": "Solution", 20 | "maturity": "Maturity", 21 | "tool": "Tools", 22 | "checklists": "Checklists", 23 | "job": "Job", 24 | "skilltree": "Skill Tree", 25 | "practise-page": { 26 | "frontend-devops-practise": "Frontend DevOps Practise" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "plugins": [ 4 | "stylelint-scss" 5 | ], 6 | "rules": { 7 | "no-empty-source": null, 8 | "selector-type-no-unknown": null, 9 | "at-rule-no-unknown": null, 10 | "scss/at-rule-no-unknown": true, 11 | "selector-pseudo-class-no-unknown": [ 12 | true, 13 | { 14 | "ignorePseudoClasses": [ 15 | "host" 16 | ] 17 | } 18 | ], 19 | "selector-pseudo-element-no-unknown": [ 20 | true, 21 | { 22 | "ignorePseudoElements": [ 23 | "ng-deep" 24 | ] 25 | } 26 | ] 27 | }, 28 | "ignore": [ 29 | "custom-elements", 30 | "default-namespace" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/app/presentation/faq/faq.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FaqComponent } from './faq.component'; 4 | 5 | describe('FaqComponent', () => { 6 | let component: FaqComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [FaqComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(FaqComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/favicon.ico", 11 | "/index.html", 12 | "/manifest.webmanifest", 13 | "/*.css", 14 | "/*.js" 15 | ] 16 | } 17 | }, { 18 | "name": "assets", 19 | "installMode": "lazy", 20 | "updateMode": "prefetch", 21 | "resources": { 22 | "files": [ 23 | "/assets/**", 24 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/resources/images/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | arrow 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { ChecklistsComponent } from './checklists.component'; 6 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 7 | import { ChecklistsRoutingModule } from './checklists-routing.module'; 8 | import { LedgeRenderModule } from '@ledge-framework/render'; 9 | 10 | @NgModule({ 11 | declarations: [ChecklistsComponent], 12 | imports: [ 13 | CommonModule, 14 | SharedModule, 15 | CustomMaterialModule, 16 | LedgeRenderModule, 17 | ChecklistsRoutingModule, 18 | ], 19 | }) 20 | export class ChecklistsModule {} 21 | -------------------------------------------------------------------------------- /src/assets/images/github-circle-white-transparent.svg: -------------------------------------------------------------------------------- 1 | github-circle-white-transparent -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | ci: false, 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/app/presentation/practise/practise.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PractiseComponent } from './practise.component'; 4 | 5 | describe('PractiseComponent', () => { 6 | let component: PractiseComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [PractiseComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PractiseComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { CaseStudyRoutingModule } from './case-study-routing.module'; 5 | import { CaseStudyComponent } from './case-study.component'; 6 | import { CustomMaterialModule } from 'src/app/shared/custom-material.module'; 7 | import { SharedModule } from 'src/app/shared/shared.module'; 8 | import { TranslateModule } from '@ngx-translate/core'; 9 | 10 | @NgModule({ 11 | declarations: [CaseStudyComponent], 12 | imports: [ 13 | CommonModule, 14 | CaseStudyRoutingModule, 15 | CustomMaterialModule, 16 | SharedModule, 17 | TranslateModule.forChild({ 18 | isolate: false, 19 | }), 20 | ], 21 | }) 22 | export class CaseStudyModule {} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | package-lock.json 48 | .idea/ 49 | plugins/.idea 50 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting, 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context( 12 | path: string, 13 | deep?: boolean, 14 | filter?: RegExp 15 | ): { 16 | keys(): string[]; 17 | (id: string): T; 18 | }; 19 | }; 20 | 21 | // First, initialize the Angular testing environment. 22 | getTestBed().initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting() 25 | ); 26 | // Then we find all the tests. 27 | const context = require.context('./', true, /\.spec\.ts$/); 28 | // And load the modules. 29 | context.keys().map(context); 30 | -------------------------------------------------------------------------------- /src/app/presentation/skill-tree/skill-tree.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule, Routes } from '@angular/router'; 5 | import { LedgeRenderModule } from '@ledge-framework/render'; 6 | 7 | import { SkillTreeComponent } from './skill-tree.component'; 8 | import { SharedModule } from '../../shared/shared.module'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: ':skilltree', 13 | component: SkillTreeComponent, 14 | }, 15 | { path: '', pathMatch: 'full', redirectTo: 'sample' }, 16 | ]; 17 | 18 | @NgModule({ 19 | declarations: [SkillTreeComponent], 20 | imports: [ 21 | CommonModule, 22 | FormsModule, 23 | SharedModule, 24 | LedgeRenderModule, 25 | RouterModule.forChild(routes), 26 | ], 27 | }) 28 | export class SkillTreeModule {} 29 | -------------------------------------------------------------------------------- /src/styles/_dragula.scss: -------------------------------------------------------------------------------- 1 | /* in-flight clone */ 2 | .gu-mirror { 3 | position: fixed !important; 4 | margin: 0 !important; 5 | z-index: 9999 !important; 6 | opacity: 0.8; 7 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; 8 | filter: alpha(opacity=80); 9 | pointer-events: none; 10 | } 11 | 12 | /* high-performance display:none; helper */ 13 | .gu-hide { 14 | left: -9999px !important; 15 | } 16 | 17 | /* added to mirrorContainer (default = body) while dragging */ 18 | .gu-unselectable { 19 | -webkit-user-select: none !important; 20 | -moz-user-select: none !important; 21 | -ms-user-select: none !important; 22 | user-select: none !important; 23 | } 24 | 25 | /* added to the source element while its mirror is dragged */ 26 | .gu-transit { 27 | opacity: 0.2; 28 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)"; 29 | filter: alpha(opacity=20); 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/resources/images/avatar-new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/presentation/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HomeComponent } from './home.component'; 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { RouterModule } from '@angular/router'; 6 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 7 | import { HttpClientModule } from '@angular/common/http'; 8 | import { InViewportModule } from '@ngx-starter-kit/ngx-utils'; 9 | import { TranslateModule } from '@ngx-translate/core'; 10 | 11 | @NgModule({ 12 | declarations: [HomeComponent], 13 | imports: [ 14 | CommonModule, 15 | SharedModule, 16 | HttpClientModule, 17 | InViewportModule, 18 | CustomMaterialModule, 19 | RouterModule.forChild([{ path: '', component: HomeComponent }]), 20 | TranslateModule.forChild({ 21 | isolate: false, 22 | }), 23 | ], 24 | }) 25 | export class HomeModule {} 26 | -------------------------------------------------------------------------------- /src/app/presentation/maturity/maturity.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | 6 | import { MaturityComponent } from './maturity.component'; 7 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 8 | import { SharedModule } from '../../shared/shared.module'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: ':name', 13 | component: MaturityComponent, 14 | }, 15 | { path: '', pathMatch: 'full', redirectTo: 'project' }, 16 | ]; 17 | 18 | @NgModule({ 19 | declarations: [MaturityComponent], 20 | imports: [ 21 | CommonModule, 22 | CustomMaterialModule, 23 | SharedModule, 24 | RouterModule.forChild(routes), 25 | TranslateModule.forChild({ 26 | isolate: false, 27 | }), 28 | ], 29 | }) 30 | export class MaturityModule {} 31 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/tencent.md: -------------------------------------------------------------------------------- 1 | # 腾讯(Tencent) 2 | 3 | 来源:[首次!腾讯全面公开整体开源路线图](https://www.oschina.net/news/107763/tencent-announce-open-source-roadmap) 4 | 5 | 研发模式:**开放、共享、合力开发** 6 | 7 | ## 开放路线图 8 | 9 | - 第一步是内部开源协同。首先拉通内部项目和组织,通过部门小团队作战或跨部门大团队作战的方式协同推进,以优化资源配置的方式集中优势寻求技术突破。随后,腾讯建立起筛选机制将代码开放出来。 10 | - 第二步是外部代码开放。优化设计与代码结构,不断拓展落地场景,有效利用外部贡献者资源实现资源整合,构建技术影响力。 11 | - 第三步是社区开放治理,在这一阶段,注重大规模技术推广与应用、开发者生态体系构建、社区领袖与领导力培养、全社会研发资源的优化配置四个方面。 12 | 13 | ```process-card 14 | | 内部开源协同 | 外部代码开放 | 社区开放治理 | 15 | |--|--|--| 16 | | 拉通组件,推动协作 | 优化设计与代码结构 | 大规模技术推广与应用 | 17 | | 优化内部资源配置 | 拓展落地场景应用 | 构建开发者生态体系 | 18 | | 集中优势,寻求技术突破 | 利用外部贡献者资源 | 社区领袖与领导力培养 | 19 | | | 构建技术影响力 | 全社会研发资源的优化配置 | 20 | 21 | config: {"colors": [{"bg":"#e55852","font":"#b71a09"},{"bg":"#e98832","font":"#c85113"},{"bg":"#f0d668","font":"#b88d0f"},{"bg":"#a4c9cf","font":"#598893"},{"bg":"#47c0af","font":"#175a54"},{"bg":"#387fd5","font":"#9ac9f5"},{"bg":"#7753df","font":"#cbb5f8"}]} 22 | ``` 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "allowSyntheticDefaultImports": true, 15 | "resolveJsonModule": true, 16 | "typeRoots": [ 17 | "node_modules/@types" 18 | ], 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ], 23 | "paths": { 24 | "ledge-render": [ 25 | "dist/ledge-render/ledge-render", 26 | "dist/ledge-render" 27 | ], 28 | "ledge-view": [ 29 | "dist/ledge-view/ledge-view", 30 | "dist/ledge-view" 31 | ] 32 | } 33 | }, 34 | "angularCompilerOptions": { 35 | "fullTemplateTypeCheck": true, 36 | "strictInjectionParameters": true 37 | } 38 | } -------------------------------------------------------------------------------- /src/assets/i18n/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "首页", 3 | "opensource-introduction": "开源软件又称开放源代码软件,是一种源代码可以任意获取的计算机软件,这种软件的版权持有人在软件协议的规定之下保留一部分权利并允许用户学习、修改以及以任何目的向任何人分发该软件。\n", 4 | "tips": { 5 | "case-study": "从社区的知识库中,我们将各种公司的案例浓缩到易于使用的内容和材料中。", 6 | "patterns": "基于我们的实践,我们提炼了位于它背后的模式与原则,帮助个人和组织更好地了解 DevOps 文化。", 7 | "practises": "我们从海量的 DevOps 内容中,提炼出了一系列的最佳实践,以更好地帮助企业进行 DevOps 实践。", 8 | "manual": "只凭实践与原则,无法让中小型 IT 团队进行 DevOps 转型,所以我们准备了详实的实施手册,以帮助您一步步前进。", 9 | "tool": "提供设计、实施 DevOps 过程中的工具,如设计 DevOps 等等", 10 | "resources": "DevOps 相关的资源,如年度报告、书籍等", 11 | "faq": "FAQ" 12 | }, 13 | "faq": "常见问题", 14 | "case-study": "案例学习", 15 | "patterns": "模式与原则", 16 | "practises": "最佳实践", 17 | "manual": "实施手册", 18 | "solution": "解决方案", 19 | "think-tank": "智库", 20 | "mobile": "移动端", 21 | "maturity": "成熟度模型", 22 | "report": "年度报告", 23 | "resource": "资源", 24 | "tool": "工具", 25 | "checklists": "检查清单", 26 | "job": "招聘", 27 | "skilltree": "技能树", 28 | "practise-page": { 29 | "frontend-devops-practise": "前端 DevOps 实践" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/assets/docs/checklists/opensource-deployment.md: -------------------------------------------------------------------------------- 1 | # 开源部署检查清单 2 | 3 | 来源:[开源软件合规实践](https://jimmysong.io/blog/open-source-compliance-practices/) 4 | 5 | - [ ] 验证引入开源软件包的修改是否已记录,并作为更改日志的一部分包含在开源发行说明中。 6 | - [ ] 确保每个修改后的源代码文件都包含版权声明,免责声明和通用“更改日志”(Changelog)条目的附加条目。 7 | - [ ] 确认源代码包的所有内容均已由工程团队审核并由 OSRB 确认。 8 | - [ ] 确保在非公司标准 Linux 计算机上编译开源软件包。此步骤的目标是确保您要发布的开源软件包在通用最终用户系统上进行编译。 9 | - [ ] 将产品手册更新为: 10 | - [ ] 提及该产品包含开源软件。 11 | - [ ] 包括与产品中包含的不同开源软件相对应的所有许可证的列表。 12 | - [ ] 提供适当的版权和归属通知。 13 | - [ ] 通过网页下载或通过产品手册中提供的指定地址通过电子邮件或邮寄方式与贵公司联系,说明如何访问开源软件包的代码(书面提供)。 14 | - [ ] 执行语言检查(linguistic review)以确保源代码中没有任何不适当的注释。 15 | - [ ] 注意:有些公司忘记进行语言检查,当代码发布时,他们会因源代码中留下的不当注释而尴尬。执行语言检查的另一个重要原因是确保源代码和注释不涉及未来的产品代码名称或功能。 16 | - [ ] 确保现有许可、版权和归属通知不受干扰。 17 | - [ ] 验证要分发的源代码是否与产品一起使用的二进制文件对应,源代码构建到与产品一起分发的同一个库中,并且源代码分发中包含适当的指令(除时间/日期戳外派生的二进制文件通常是相同的)。 18 | - [ ] 验证包是否遵循开源策略中定义的链接关系和交互。 19 | - [ ] 确保在开源软件包的根文件夹中的 LICENSE 文件中包含许可证文本的副本(如果尚未存在)。 20 | - [ ] 如果源代码包需要特殊的构建工具或环境设置,则将详细信息包含在 README 文件或类似文件中。 这些清单,特别是在实现自动化并与业务和开发流程集成时,可以提醒您必须完成的所有事项,以确保合规性并减少发生错误的几率。 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🙋 Feature Request 3 | about: Want us to add something to Ledge? 4 | --- 5 | 6 | 11 | 12 | 21 | 22 | ## Feature Request 23 | 24 | 35 | 36 | ## What does the proposed API examples? 37 | 38 | 43 | 44 | ## Other reference [Optional] 45 | 46 | 51 | -------------------------------------------------------------------------------- /src/app/presentation/pattern/pattern.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PatternComponent } from './pattern.component'; 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 6 | import { LedgeRenderModule } from '@ledge-framework/render'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | 9 | describe('PatternComponent', () => { 10 | let component: PatternComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | SharedModule, 17 | CustomMaterialModule, 18 | LedgeRenderModule, 19 | RouterTestingModule, 20 | ], 21 | declarations: [PatternComponent], 22 | }).compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(PatternComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | component.ngOnInit(); 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/assets/docs/home.md: -------------------------------------------------------------------------------- 1 | ## 企业开源五大支柱 2 | 3 | ```process-card 4 | | 组织支持 | 工程能力 | 开源文化 | 产品思维 | 社区与生态 | 5 | |-|-|-|-|-| 6 | | 发布开源指南 | 充分测试 | 建立内部开源文化 | 和产品策略一致 | 对生态进行战略投资 | 7 | | 设立开源协议指南 | 整洁代码 | 寻找开源倡导者 | 设立项目里程碑 | 培养关键意见领袖(KOL) | 8 | | 制定开源投资战略 | DevOps 流程一体化 | 开放式地管理 | 制定营销策略 | 招募技术布道者 | 9 | | 建立开源项目部门 | 文档体验设计 | 鼓励参与、协作 | 项目管理 | 构建开发者生态 | 10 | | 建设度量指标 | 采用社区标准的技术实践 | 公开透明开源软件流程 | 管理请求意见稿(RFC) | 反馈驱动开发 | 11 | 12 | config: {"colors": [{"bg":"#e55852","font":"#b71a09"},{"bg":"#e98832","font":"#c85113"},{"bg":"#f0d668","font":"#b88d0f"},{"bg":"#a4c9cf","font":"#598893"}]} 13 | ``` 14 | 15 | ## 企业开源能力全景图 16 | 17 | ```process-step 18 | - 使用开源 19 | - [1] 制定开源使用指南 20 | - [1] 开源合规审查 21 | - 参与开源 22 | - [2] 引入开源技术实践 23 | - [1] 制定开源参与指南 24 | - [1] 开源治理策略 25 | - [3] 建议团队回馈开源 26 | - [5] 参与社区建设 27 | - 走向开源 28 | - [3] 内部项目开源共享 29 | - [5] 社区推广策略 30 | - [2] 敏捷测试实践 31 | - [4] 和产品策略一致 32 | - [1] 制定贡献指南 33 | - [1] 设计成熟度模型 34 | - [1] 开源项目管理部门 35 | - [4] 制定开源项目营销策略 36 | - 构建生态 37 | - [5] 技术布道 38 | - [5] 创建开发者社区 39 | - [2] 开发者体验 40 | - [3] 去中心化的社区管理 41 | - [3] 透明决策和讨论 42 | - [5] 社区参与多样性 43 | - [1] 生态战略投资 44 | 45 | config: {"heads": ["组织支持", "工程能力", "开源文化", "产品思维", "社区与生态"]} 46 | ``` 47 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: PULL REQUEST 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | pull_request: 9 | branches: [master] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Use Node.js 12.x 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: "12.x" 28 | 29 | - name: Install 30 | run: | 31 | yarn install 32 | 33 | - name: Lint 34 | run: | 35 | yarn lint 36 | 37 | - name: Testing 38 | run: yarn test:ci 39 | 40 | - name: Build 41 | run: yarn build:ci 42 | -------------------------------------------------------------------------------- /src/assets/docs/maturities/project.md: -------------------------------------------------------------------------------- 1 | # 开源项目成熟度 2 | 3 | ```maturity 4 | - 基础级成熟度 5 | - 产品文档 6 | - 标准化 7 | - 许可证 8 | - 技术环境 9 | - 提交数和 bug 数 10 | - 可维护性和稳定性 11 | - 配置管理 12 | - 项目计划 13 | - 需求管理 14 | - 产品路线图 15 | ``` 16 | 17 | ```maturity 18 | - 中级成熟度 19 | - 路线图的可用性和使用 20 | - 利益相关者管理 21 | - 项目计划 22 | - 项目监控 23 | - 测试 24 | - 设计 25 | - 过程和产品质量保证 26 | ``` 27 | 28 | 基于:[Open Source Maturity Model](https://en.wikipedia.org/wiki/OpenSource_Maturity_Model) 29 | 30 | 英文原材料: 31 | 32 | - PDOC: Product Documentation 33 | - STD: Use of Established and Widespread Standards 34 | - QTP: Quality of Test Plan 35 | - LCS: Licenses 36 | - ENV: Technical Environment 37 | - DFCT: Number of Commits and Bug Reports 38 | - MST: Maintainability and Stability 39 | - CM: Configuration Management 40 | - PP1: Project Planning Part 1 41 | - REQM: Requirements Management 42 | - RDMP1: Availability and Use of a (product) roadmap 43 | 44 | Intermediate level: 45 | 46 | - RDMP2: Availability and Use of a (product) roadmap 47 | - STK: Relationship between Stakeholders 48 | - PP2: Project Planning Part 2 49 | - PMC: Project Monitoring and Control 50 | - TST1: Test Part 1 51 | - DSN1: Design Part 1 52 | - PPQA: Process and Product Quality Assurance 53 | -------------------------------------------------------------------------------- /src/assets/docs/casestudies/microsoft.md: -------------------------------------------------------------------------------- 1 | # 微软(Microsoft) 2 | 3 | - 云服务与开源社区结合布局 4 | - 1976 比尔·盖茨写了《致爱好者的公开信》,对电脑爱好者没有付费就使用微软的 Altair Basic 软件表示失望 5 | - 1998 微软的机密文件“万圣节文件”泄露,内容是微软总部用来对付开源软件(特别是 Linux)的预备方案。显示出微软私下认为开源软件是劲敌 6 | - 2000 微软第二任 CEO 斯蒂文·鲍尔默公开责难称“Linux 就是个把它碰到的一切都变成专利的癌症” 7 | - 单一软件授权模式 视开源为死敌 8 | - 2004 基于 OSS 许可协议发布的 WiX 工具集成为了微软发布的第一个开源项目 9 | - 2006 微软开源社区的首次尝试:微软推出开源托管网站 CodePlex,在此网站所发布的所有程序都可以下载源码使用 10 | - 逐渐成为开源软件的最大贡献者 11 | - 2012 微软开源了开发大型应用的工具语言 TypeScript;成立“微软开放技术有限公司”;基于 Apache2.0 许可证开源了部分 Web 产品 12 | - 2014 微软开源.NET 核心;新任 CEO 萨提亚·纳德拉提出“微软爱 Linux”;加入 Open Compute Project;成立 .NET 基金会,全力支持 .NET 平台上的开源研发与合作 13 | - 2015 微软开源 Visual Studio Code;将 RedHat 企业 Linux 带到 Azure 云上 14 | - 2016 微软已将所有领先的 Linux 发行版带到了 Azure 云上;加入 Linux 基金会成为董事;发布跨平台部署平台.NET(windows, Linx, Mac);跃居 GitHub 上开源贡献人数最多的组织 15 | - 2018 微软 75 亿美元收购 GitHub 16 | - 2020 Github 收购 NPM 17 | 18 | ## 步骤 19 | 20 | ```process-step 21 | - 赋能 22 | - 在云上放置开源实例 23 | - 整合 24 | - 云平台使用开源技术 25 | - 发布 26 | - 开发开源的技术 27 | - 贡献 28 | - 致力于开源生态 29 | ``` 30 | 31 | ## 分类 32 | 33 | - 新生代。刷新开发人员对于微软形象的认知 34 | - 中生代。开源 .NET 生态相关的项目为其注入新的希望 35 | - 中生代。开源有意思的老旧系统为其带来新的活力,丰富生态 36 | 37 | ## 案例 38 | 39 | ### Visual Studio Code 40 | 41 | - 微软开源的 Visual Studio Code 在过去的 5 年间,击败了一个一个的收费、免费编辑器。甚至在某些领域(如前端)能和专业的 IDE (WebStorm)相媲美。 42 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { TranslateService } from '@ngx-translate/core'; 4 | import { StorageMap } from '@ngx-pwa/local-storage'; 5 | 6 | @Component({ 7 | selector: 'app-root', 8 | templateUrl: './app.component.html', 9 | styleUrls: ['./app.component.scss'], 10 | }) 11 | export class AppComponent implements OnInit { 12 | title = 'opensource'; 13 | 14 | constructor( 15 | private route: Router, 16 | public translate: TranslateService, 17 | private storage: StorageMap 18 | ) { 19 | translate.use('zh-cn'); 20 | } 21 | 22 | // todo: refactor 23 | isHome() { 24 | return ( 25 | this.route.url === '/home' || 26 | this.route.url === '/maturity' || 27 | this.route.url === '/report' || 28 | this.route.url === '/design' 29 | ); 30 | } 31 | 32 | setLanguage(lang: string) { 33 | this.translate.use(lang); 34 | this.storage.set('language', lang).subscribe(() => {}); 35 | } 36 | 37 | ngOnInit(): void { 38 | this.storage.get('language').subscribe((value: string) => { 39 | if (!!value) { 40 | this.translate.use(value); 41 | } 42 | }); 43 | } 44 | 45 | openLink(link: string) { 46 | window.open(link, '_blank'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/images/purelogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo_pure 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/presentation/maturity/maturity.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { LedgeRenderModule } from '@ledge-framework/render'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | 6 | import { MaturityComponent } from './maturity.component'; 7 | import { SharedModule } from '../../shared/shared.module'; 8 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 9 | 10 | describe('MaturityComponent', () => { 11 | let component: MaturityComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | BrowserAnimationsModule, 18 | SharedModule, 19 | CustomMaterialModule, 20 | LedgeRenderModule, 21 | RouterTestingModule, 22 | ], 23 | declarations: [MaturityComponent], 24 | }).compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(MaturityComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/styles/_periodic-variables.scss: -------------------------------------------------------------------------------- 1 | $solid-background-color: #171717; 2 | $liquid-background-color: #2D51E1; 3 | $gas-background-color: #923A49; 4 | $unknown-background-color: #898989; 5 | $gas-color: #990000; 6 | $liquid-color: #0000dd; 7 | $solid-color: #000; 8 | $unknown-color: #667766; 9 | $border-color: #CCC; 10 | $thick-border-color: #9a9a9a; 11 | $gray: #D3D3D3; 12 | 13 | $periodic-square: 64px; 14 | 15 | .packageManage {background: #666666} 16 | .scm { background:#7f195e; } /* SCM */ 17 | .deployment { background:#09c95e; } /* DEPLOYMENT */ 18 | .analytics { background:#008988; } /* ANALYTICS */ 19 | .database { background:#f37c20; } /* DATABASE */ 20 | .containers { background:#3e4853; } /* CONTAINERS */ 21 | .monitoring { background:#6a7b8d; } /* MONITORING */ 22 | .ci { background:#46bbff; } /* CI */ 23 | .releaseOrchestration { background:#06803c; } /* RELEASE ORCHESTRATION */ 24 | .security { background:#008ddf; } /* SECURITY */ 25 | .testing { background:#dc5332; } /* TESTING */ 26 | .openCloud { background:#e9bd1e; } /* CLOUD */ 27 | .publicCloud { background: #762ce9; } /* CLOUD */ 28 | .collaboration { background:#aa217e; } /* COLLAB */ 29 | .config { background:#005d92; } /* CONFIG */ 30 | .aiops { background:#bc580a; } /* AI OPS */ 31 | .operation { background: #4178bc; } /* AI OPS */ 32 | .atom-placeholder {background: #dda750;} 33 | .platform {background: #dd002c;} 34 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | basePath: '', 8 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 9 | plugins: [ 10 | require('karma-jasmine'), 11 | require('karma-chrome-launcher'), 12 | require('karma-jasmine-html-reporter'), 13 | require('karma-coverage-istanbul-reporter'), 14 | require('@angular-devkit/build-angular/plugins/karma'), 15 | ], 16 | client: { 17 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageIstanbulReporter: { 20 | dir: require('path').join(__dirname, './coverage/ledge'), 21 | reports: ['html', 'lcovonly', 'text-summary'], 22 | fixWebpackSourcePaths: true, 23 | }, 24 | reporters: ['progress', 'kjhtml'], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ['Chrome'], 30 | customLaunchers: { 31 | ChromeHeadlessCI: { 32 | base: 'ChromeHeadless', 33 | flags: ['--no-sandbox', '--disable-gpu'], 34 | }, 35 | }, 36 | singleRun: false, 37 | restartOnFileChange: true, 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OpenSource - 开源知识平台 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/app/presentation/skill-tree/skill-tree.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | 4 | import { MatDrawerContent } from '@angular/material/sidenav'; 5 | import { ActivatedRoute } from '@angular/router'; 6 | import { Cases } from '../case-study/cases'; 7 | import { DocRoute } from '@ledge-framework/view/lib/ledge-multiple-docs/doc-route.model'; 8 | 9 | export const trees: Cases = [{ displayName: '示例', source: 'sample' }]; 10 | 11 | @Component({ 12 | selector: 'app-skill-tree', 13 | templateUrl: './skill-tree.component.html', 14 | styleUrls: ['./skill-tree.component.scss'], 15 | }) 16 | export class SkillTreeComponent implements OnInit { 17 | @ViewChild('drawerContent', { static: false }) 18 | drawerContent: MatDrawerContent; 19 | 20 | currentSource: string; 21 | src: string; 22 | content: string; 23 | 24 | items: DocRoute[] = trees; 25 | currentUrl = '/skilltree'; 26 | urlPrefix = `skilltrees`; 27 | 28 | constructor(private title: Title, private activatedRoute: ActivatedRoute) {} 29 | 30 | ngOnInit(): void { 31 | this.activatedRoute.paramMap.subscribe((p) => { 32 | const param = p.get('skilltree'); 33 | const currentItem = this.items.find((item) => item.source === param); 34 | this.title.setTitle( 35 | `开源 ${currentItem.displayName} 技能图谱 - 开源知识平台` 36 | ); 37 | this.currentSource = param; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/presentation/maturity/maturity.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | import { MatDrawerContent } from '@angular/material/sidenav'; 4 | import { ActivatedRoute } from '@angular/router'; 5 | import { DocRoute } from '@ledge-framework/view/lib/ledge-multiple-docs/doc-route.model'; 6 | 7 | export const lists: DocRoute[] = [ 8 | { displayName: '布道师能力模型', source: 'evangelist' }, 9 | { displayName: '开源项目成熟度', source: 'project' }, 10 | ]; 11 | 12 | @Component({ 13 | selector: 'app-maturity', 14 | templateUrl: './maturity.component.html', 15 | styleUrls: ['./maturity.component.scss'], 16 | }) 17 | export class MaturityComponent implements OnInit { 18 | @ViewChild('drawerContent', { static: false }) 19 | drawerContent: MatDrawerContent; 20 | 21 | currentSource: string; 22 | src: string; 23 | content: string; 24 | 25 | items: DocRoute[] = lists; 26 | currentUrl = '/maturity'; 27 | urlPrefix = `maturities`; 28 | 29 | constructor(private title: Title, private activatedRoute: ActivatedRoute) {} 30 | 31 | ngOnInit(): void { 32 | this.activatedRoute.paramMap.subscribe((p) => { 33 | const param = p.get('name'); 34 | const currentItem = this.items.find((item) => item.source === param); 35 | this.title.setTitle( 36 | `开源 ${currentItem.displayName} 检查清单 - 开源知识平台` 37 | ); 38 | this.currentSource = param; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { MatDrawerContent } from '@angular/material/sidenav'; 5 | import { TranslateService } from '@ngx-translate/core'; 6 | 7 | import { cases } from './cases'; 8 | import { DocRoute } from '@ledge-framework/view/lib/ledge-multiple-docs/doc-route.model'; 9 | 10 | @Component({ 11 | selector: 'app-case-study', 12 | templateUrl: './case-study.component.html', 13 | styleUrls: ['./case-study.component.scss'], 14 | }) 15 | export class CaseStudyComponent implements OnInit { 16 | @ViewChild('drawerContent', { static: false }) 17 | drawerContent: MatDrawerContent; 18 | 19 | currentSource: string; 20 | src: string; 21 | content: string; 22 | 23 | items: DocRoute[] = cases; 24 | currentUrl = '/case-study'; 25 | urlPrefix = `casestudies`; 26 | 27 | constructor( 28 | private title: Title, 29 | private activatedRoute: ActivatedRoute, 30 | private translate: TranslateService 31 | ) {} 32 | 33 | ngOnInit(): void { 34 | this.activatedRoute.paramMap.subscribe((p) => { 35 | const param = p.get('case'); 36 | const currentItem = this.items.find((item) => item.source === param); 37 | this.title.setTitle( 38 | `${currentItem.displayName} 开源案例学习(互联网公司/传统公司) - 开源知识平台` 39 | ); 40 | this.currentSource = param; 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { HttpClient, HttpClientModule } from '@angular/common/http'; 3 | import { NgModule, SecurityContext } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterModule } from '@angular/router'; 6 | import { TranslateModule } from '@ngx-translate/core'; 7 | import { MarkdownModule, MarkedOptions } from 'ngx-markdown'; 8 | import { LedgeRenderModule } from '@ledge-framework/render'; 9 | import { LedgeViewModule } from '@ledge-framework/view'; 10 | 11 | import { CustomMaterialModule } from './custom-material.module'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | RouterModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | HttpClientModule, 20 | CustomMaterialModule, 21 | LedgeRenderModule, 22 | LedgeViewModule, 23 | TranslateModule, 24 | MarkdownModule.forRoot({ 25 | sanitize: SecurityContext.NONE, 26 | loader: HttpClient, 27 | markedOptions: { 28 | provide: MarkedOptions, 29 | useValue: { 30 | gfm: true, 31 | breaks: false, 32 | pedantic: false, 33 | smartLists: true, 34 | smartypants: false, 35 | langPrefix: 'language-', 36 | headerPrefix: '', 37 | headerIds: true, 38 | }, 39 | }, 40 | }), 41 | ], 42 | declarations: [], 43 | providers: [], 44 | exports: [LedgeViewModule], 45 | entryComponents: [], 46 | }) 47 | export class SharedModule {} 48 | -------------------------------------------------------------------------------- /src/styles/_material-ui.scss: -------------------------------------------------------------------------------- 1 | @import "./color"; 2 | 3 | // Navigation Bar 4 | .app-header { 5 | position: fixed; 6 | top: 0; 7 | z-index: 999; 8 | width: 100%; 9 | } 10 | 11 | .mat-tooltip-panel { 12 | .mat-tooltip { 13 | background-color: $tooltip-bg; 14 | font-size: 14px; 15 | } 16 | } 17 | 18 | .mat-toolbar { 19 | box-shadow: 0 1px 0 0 $lighter-gray; 20 | 21 | &.mat-accent { 22 | color: $purple; 23 | background: $white; 24 | box-shadow: none; 25 | border-bottom: solid 1px $border-color; 26 | } 27 | 28 | .mat-toolbar-row { 29 | align-items: stretch; 30 | } 31 | 32 | .mat-button { 33 | font-size: 16px; 34 | font-weight: normal; 35 | height: 100%; 36 | border-radius: 0; 37 | 38 | &.active { 39 | color: $white; 40 | background-color: $purple; 41 | } 42 | } 43 | } 44 | 45 | .cdk-overlay-container { 46 | .cdk-overlay-backdrop.nav-menu-wrapper { 47 | & + .cdk-overlay-connected-position-bounding-box { 48 | .mat-menu-panel { 49 | margin-top: 16px; 50 | border-radius: 0; 51 | 52 | .mat-menu-content:not(:empty) { 53 | padding-top: 0; 54 | padding-bottom: 0; 55 | } 56 | 57 | .mat-menu-content { 58 | .mat-menu-item { 59 | line-height: 60px; 60 | height: 60px; 61 | box-shadow: 0 1px 0 0 $lighter-gray; 62 | 63 | &:hover { 64 | color: $purple; 65 | background-color: rgba($purple, .2); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Did something not work as expected? 4 | --- 5 | 6 | 11 | 12 | 21 | 22 | ## Bug Report 23 | 24 | 29 | 30 | ## Current Behavior? 31 | 32 | 37 | 38 | 43 | 44 | ## Expected Behavior? 45 | 46 | 51 | 52 | ## Steps to reproduce [Optional] 53 | 54 | 59 | 60 | ## Your Environment [Optional] 61 | 62 | 67 | 68 | 73 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Title } from '@angular/platform-browser'; 4 | import { MatDrawerContent } from '@angular/material/sidenav'; 5 | import { DocRoute } from '@ledge-framework/view/lib/ledge-multiple-docs/doc-route.model'; 6 | 7 | export const lists: DocRoute[] = [ 8 | { displayName: '开源软件发布检查清单', source: 'opensource-release' }, 9 | { displayName: '开源安全检查清单', source: 'opensource-security' }, 10 | { displayName: '开源开发检查清单', source: 'opensource-development' }, 11 | { displayName: '开源部署检查清单', source: 'opensource-deployment' }, 12 | ]; 13 | 14 | @Component({ 15 | selector: 'app-checklists', 16 | templateUrl: './checklists.component.html', 17 | styleUrls: ['./checklists.component.scss'], 18 | }) 19 | export class ChecklistsComponent implements OnInit { 20 | @ViewChild('drawerContent', { static: false }) 21 | drawerContent: MatDrawerContent; 22 | 23 | currentSource: string; 24 | src: string; 25 | content: string; 26 | 27 | items: DocRoute[] = lists; 28 | currentUrl = '/checklists'; 29 | urlPrefix = `checklists`; 30 | 31 | constructor(private title: Title, private activatedRoute: ActivatedRoute) {} 32 | 33 | ngOnInit(): void { 34 | this.activatedRoute.paramMap.subscribe((p) => { 35 | const param = p.get('name'); 36 | const currentItem = this.items.find((item) => item.source === param); 37 | this.title.setTitle( 38 | `开源软件 ${currentItem.displayName} 检查清单 - 开源知识平台` 39 | ); 40 | this.currentSource = param; 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/presentation/skill-tree/skill-tree.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { LedgeRenderModule } from '@ledge-framework/render'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { ActivatedRoute, convertToParamMap } from '@angular/router'; 6 | import { of } from 'rxjs'; 7 | 8 | import { SkillTreeComponent } from './skill-tree.component'; 9 | import { SharedModule } from '../../shared/shared.module'; 10 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 11 | 12 | describe('SkillTreeComponent', () => { 13 | let component: SkillTreeComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | imports: [ 19 | BrowserAnimationsModule, 20 | SharedModule, 21 | CustomMaterialModule, 22 | LedgeRenderModule, 23 | RouterTestingModule, 24 | ], 25 | providers: [ 26 | { 27 | provide: ActivatedRoute, 28 | useValue: { 29 | fragment: of({}), 30 | paramMap: of(convertToParamMap({ skilltree: 'sample' })), 31 | }, 32 | }, 33 | ], 34 | declarations: [SkillTreeComponent], 35 | }).compileComponents(); 36 | })); 37 | 38 | beforeEach(() => { 39 | fixture = TestBed.createComponent(SkillTreeComponent); 40 | component = fixture.componentInstance; 41 | fixture.detectChanges(); 42 | }); 43 | 44 | it('should create', () => { 45 | component.ngOnInit(); 46 | expect(component).toBeTruthy(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PatternComponent } from './presentation/pattern/pattern.component'; 4 | import { FaqComponent } from './presentation/faq/faq.component'; 5 | import { PractiseComponent } from './presentation/practise/practise.component'; 6 | 7 | const routes: Routes = [ 8 | { path: '', pathMatch: 'full', redirectTo: '/home' }, 9 | { 10 | path: 'pattern', 11 | component: PatternComponent, 12 | }, 13 | { 14 | path: 'practise', 15 | component: PractiseComponent, 16 | }, 17 | { 18 | path: 'faq', 19 | component: FaqComponent, 20 | }, 21 | { 22 | path: 'home', 23 | loadChildren: () => 24 | import('./presentation/home/home.module').then((m) => m.HomeModule), 25 | }, 26 | { 27 | path: 'skilltree', 28 | loadChildren: () => 29 | import('./presentation/skill-tree/skill-tree.module').then( 30 | (m) => m.SkillTreeModule 31 | ), 32 | }, 33 | { 34 | path: 'maturity', 35 | loadChildren: () => 36 | import('./presentation/maturity/maturity.module').then( 37 | (m) => m.MaturityModule 38 | ), 39 | }, 40 | { 41 | path: 'case-study', 42 | loadChildren: () => 43 | import('./presentation/case-study/case-study.module').then( 44 | (m) => m.CaseStudyModule 45 | ), 46 | }, 47 | { 48 | path: 'checklists', 49 | loadChildren: () => 50 | import('./presentation/checklists/checklists.module').then( 51 | (m) => m.ChecklistsModule 52 | ), 53 | }, 54 | ]; 55 | 56 | @NgModule({ 57 | imports: [RouterModule.forRoot(routes)], 58 | exports: [RouterModule], 59 | }) 60 | export class AppRoutingModule {} 61 | -------------------------------------------------------------------------------- /src/app/shared/custom-material.module.ts: -------------------------------------------------------------------------------- 1 | import { ScrollingModule } from '@angular/cdk/scrolling'; 2 | import { NgModule } from '@angular/core'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { MatCardModule } from '@angular/material/card'; 5 | import { 6 | MatDialogModule, 7 | MAT_DIALOG_DEFAULT_OPTIONS, 8 | } from '@angular/material/dialog'; 9 | import { MatIconModule } from '@angular/material/icon'; 10 | import { MatInputModule } from '@angular/material/input'; 11 | import { MatMenuModule } from '@angular/material/menu'; 12 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 13 | import { MatSelectModule } from '@angular/material/select'; 14 | import { MatSidenavModule } from '@angular/material/sidenav'; 15 | import { MatSliderModule } from '@angular/material/slider'; 16 | import { MatTabsModule } from '@angular/material/tabs'; 17 | import { MatToolbarModule } from '@angular/material/toolbar'; 18 | import { MatTooltipModule } from '@angular/material/tooltip'; 19 | import { MatFormFieldModule } from '@angular/material/form-field'; 20 | 21 | const modules = [ 22 | MatToolbarModule, 23 | MatButtonModule, 24 | MatMenuModule, 25 | MatProgressSpinnerModule, 26 | MatSliderModule, 27 | MatSidenavModule, 28 | MatIconModule, 29 | MatDialogModule, 30 | MatCardModule, 31 | MatInputModule, 32 | MatTooltipModule, 33 | MatTabsModule, 34 | MatSelectModule, 35 | MatFormFieldModule, 36 | 37 | ScrollingModule, 38 | ]; 39 | 40 | @NgModule({ 41 | imports: modules, 42 | declarations: [], 43 | providers: [ 44 | { provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true } }, 45 | ], 46 | exports: modules, 47 | entryComponents: [], 48 | }) 49 | export class CustomMaterialModule {} 50 | -------------------------------------------------------------------------------- /src/app/presentation/checklists/checklists.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ChecklistsComponent } from './checklists.component'; 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { LedgeRenderModule } from '@ledge-framework/render'; 6 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 8 | import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; 9 | import { of } from 'rxjs'; 10 | import { RouterTestingModule } from '@angular/router/testing'; 11 | 12 | describe('ChecklistsComponent', () => { 13 | let component: ChecklistsComponent; 14 | let fixture: ComponentFixture; 15 | const mockRouter = { 16 | navigate: jasmine.createSpy('navigate'), 17 | }; 18 | 19 | beforeEach(async(() => { 20 | TestBed.configureTestingModule({ 21 | imports: [ 22 | SharedModule, 23 | CustomMaterialModule, 24 | LedgeRenderModule, 25 | BrowserAnimationsModule, 26 | RouterTestingModule, 27 | ], 28 | declarations: [ChecklistsComponent], 29 | providers: [ 30 | { provide: Router, useValue: mockRouter }, 31 | { 32 | provide: ActivatedRoute, 33 | useValue: { 34 | fragment: of({}), 35 | paramMap: of(convertToParamMap({ name: 'opensource-release' })), 36 | }, 37 | }, 38 | ], 39 | }).compileComponents(); 40 | })); 41 | 42 | beforeEach(() => { 43 | fixture = TestBed.createComponent(ChecklistsComponent); 44 | component = fixture.componentInstance; 45 | fixture.detectChanges(); 46 | }); 47 | 48 | it('should create', () => { 49 | component.ngOnInit(); 50 | expect(component).toBeTruthy(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Use Node.js 12.x 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: "12.x" 28 | 29 | - name: Get yarn cache directory path 30 | id: yarn-cache-dir-path 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | 33 | - uses: actions/cache@v1 34 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 35 | with: 36 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-yarn- 40 | 41 | - name: Install 42 | run: | 43 | yarn install 44 | 45 | - name: Lint 46 | run: | 47 | yarn lint 48 | 49 | - name: Build 50 | run: | 51 | yarn build:ci 52 | 53 | - name: Testing 54 | run: yarn test:ci 55 | 56 | - name: Upload coverage to Codecov 57 | uses: codecov/codecov-action@v1 58 | with: 59 | file: ./coverage/ledge/lcov.info 60 | flags: unittests 61 | name: codecov-umbrella 62 | fail_ci_if_error: false 63 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Created with Sketch. 10 | 11 | 12 | 13 | 16 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/assets/images/logo_pure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Created with Sketch. 10 | 11 | 12 | 13 | 16 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/app/presentation/case-study/case-study.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CaseStudyComponent } from './case-study.component'; 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { 7 | TranslateFakeLoader, 8 | TranslateLoader, 9 | TranslateModule, 10 | } from '@ngx-translate/core'; 11 | import { LedgeRenderModule } from '@ledge-framework/render'; 12 | import { CustomMaterialModule } from '../../shared/custom-material.module'; 13 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 14 | import { BrowserTestingModule } from '@angular/platform-browser/testing'; 15 | import { ActivatedRoute, convertToParamMap } from '@angular/router'; 16 | import { of } from 'rxjs'; 17 | 18 | describe('CaseStudyComponent', () => { 19 | let component: CaseStudyComponent; 20 | let fixture: ComponentFixture; 21 | 22 | beforeEach(async(() => { 23 | TestBed.configureTestingModule({ 24 | imports: [ 25 | SharedModule, 26 | RouterTestingModule, 27 | LedgeRenderModule, 28 | CustomMaterialModule, 29 | BrowserAnimationsModule, 30 | BrowserTestingModule, 31 | TranslateModule.forRoot({ 32 | loader: { 33 | provide: TranslateLoader, 34 | useClass: TranslateFakeLoader, 35 | }, 36 | }), 37 | ], 38 | declarations: [CaseStudyComponent], 39 | providers: [ 40 | { 41 | provide: ActivatedRoute, 42 | useValue: { 43 | fragment: of({}), 44 | paramMap: of(convertToParamMap({ case: 'sample' })), 45 | }, 46 | }, 47 | ], 48 | }).compileComponents(); 49 | })); 50 | 51 | beforeEach(() => { 52 | fixture = TestBed.createComponent(CaseStudyComponent); 53 | component = fixture.componentInstance; 54 | fixture.detectChanges(); 55 | }); 56 | 57 | it('should create', () => { 58 | expect(component).toBeTruthy(); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { BrowserModule, Title } from '@angular/platform-browser'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | import { LedgeRenderModule } from '@ledge-framework/render'; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | import { AppComponent } from './app.component'; 9 | import { PatternComponent } from './presentation/pattern/pattern.component'; 10 | import { CustomMaterialModule } from './shared/custom-material.module'; 11 | import { SharedModule } from './shared/shared.module'; 12 | import { ScullyLibModule } from '@scullyio/ng-lib'; 13 | import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; 14 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 15 | import { FlexLayoutModule } from '@angular/flex-layout'; 16 | import { WebpackTranslateLoader } from './webpack-translate-loader'; 17 | import { FaqComponent } from './presentation/faq/faq.component'; 18 | import { PractiseComponent } from './presentation/practise/practise.component'; 19 | 20 | export function createTranslateLoader(http: HttpClient) { 21 | return new TranslateHttpLoader(http, './assets/i18n/', '.json'); 22 | } 23 | 24 | @NgModule({ 25 | declarations: [ 26 | AppComponent, 27 | PatternComponent, 28 | FaqComponent, 29 | PractiseComponent, 30 | ], 31 | imports: [ 32 | BrowserModule, 33 | FormsModule, 34 | ReactiveFormsModule, 35 | SharedModule, 36 | AppRoutingModule, 37 | HttpClientModule, 38 | BrowserAnimationsModule, 39 | CustomMaterialModule, 40 | LedgeRenderModule, 41 | ScullyLibModule, 42 | FlexLayoutModule, 43 | TranslateModule.forRoot({ 44 | defaultLanguage: 'zh-cn', 45 | loader: { 46 | provide: TranslateLoader, 47 | useClass: WebpackTranslateLoader, 48 | }, 49 | }), 50 | ], 51 | providers: [Title], 52 | bootstrap: [AppComponent], 53 | }) 54 | export class AppModule {} 55 | -------------------------------------------------------------------------------- /src/app/presentation/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | async, 3 | ComponentFixture, 4 | inject, 5 | TestBed, 6 | } from '@angular/core/testing'; 7 | import { HomeComponent } from './home.component'; 8 | import { RouterTestingModule } from '@angular/router/testing'; 9 | import { 10 | TranslateFakeLoader, 11 | TranslateLoader, 12 | TranslateModule, 13 | } from '@ngx-translate/core'; 14 | import { 15 | HttpClientTestingModule, 16 | HttpTestingController, 17 | } from '@angular/common/http/testing'; 18 | 19 | import { SharedModule } from '../../shared/shared.module'; 20 | 21 | describe('HomeComponent', () => { 22 | let component: HomeComponent; 23 | let fixture: ComponentFixture; 24 | 25 | beforeEach(async(() => { 26 | TestBed.configureTestingModule({ 27 | imports: [ 28 | SharedModule, 29 | RouterTestingModule, 30 | HttpClientTestingModule, 31 | TranslateModule.forRoot({ 32 | loader: { 33 | provide: TranslateLoader, 34 | useClass: TranslateFakeLoader, 35 | }, 36 | }), 37 | ], 38 | declarations: [HomeComponent], 39 | }).compileComponents(); 40 | })); 41 | 42 | beforeEach(() => { 43 | fixture = TestBed.createComponent(HomeComponent); 44 | component = fixture.componentInstance; 45 | fixture.detectChanges(); 46 | }); 47 | 48 | it('should create', () => { 49 | expect(component).toBeTruthy(); 50 | }); 51 | 52 | it('should update category', () => { 53 | const category = 'sourceMgr'; 54 | 55 | component.setCurrentAtomCategory(category); 56 | 57 | expect(component.category).toBe(category); 58 | }); 59 | 60 | it('should get data', () => { 61 | inject([HttpTestingController], (httpMock: HttpTestingController) => { 62 | component.ngAfterViewInit(); 63 | 64 | const fisrt = httpMock.expectOne('./assets/periodic-table.json'); 65 | const req = httpMock.expectOne( 66 | 'https://api.github.com/repos/phodal/ledge/contributors' 67 | ); 68 | expect(req.request.method).toEqual('GET'); 69 | req.flush([]); 70 | 71 | httpMock.verify(); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Deploy 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Use Node.js 12.x 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: "12.x" 28 | 29 | - name: Get yarn cache directory path 30 | id: yarn-cache-dir-path 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | 33 | - uses: actions/cache@v1 34 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 35 | with: 36 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-yarn- 40 | 41 | - name: Install 42 | run: | 43 | yarn install 44 | 45 | - name: Build 46 | run: | 47 | yarn build:ci 48 | 49 | - name: Build Scully 50 | run: | 51 | npm run scully -- --host='0.0.0.0' --scanRoutes --serverTimeout=60000 52 | 53 | - name: Deploy 🚀 54 | uses: JamesIves/github-pages-deploy-action@releases/v3 55 | with: 56 | ACCESS_TOKEN: ${{ secrets.SuperSecret }} 57 | BRANCH: gh-pages 58 | REPOSITORY_NAME: "phodal/oss-archive" 59 | FOLDER: dist/static 60 | CLEAN: true 61 | CLEAN_EXCLUDE: '["CNAME"]' 62 | continue-on-error: true 63 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MatMenuModule } from '@angular/material/menu'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { 5 | TranslateFakeLoader, 6 | TranslateLoader, 7 | TranslateModule, 8 | TranslateService, 9 | TranslateStore, 10 | } from '@ngx-translate/core'; 11 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 12 | 13 | import { CustomMaterialModule } from './shared/custom-material.module'; 14 | import { SharedModule } from './shared/shared.module'; 15 | import { AppComponent } from './app.component'; 16 | 17 | describe('AppComponent', () => { 18 | let component: AppComponent; 19 | let fixture: ComponentFixture; 20 | 21 | beforeEach(async(() => { 22 | TestBed.configureTestingModule({ 23 | imports: [ 24 | RouterTestingModule, 25 | MatMenuModule, 26 | CustomMaterialModule, 27 | BrowserAnimationsModule, 28 | SharedModule, 29 | TranslateModule.forRoot({ 30 | loader: { 31 | provide: TranslateLoader, 32 | useClass: TranslateFakeLoader, 33 | }, 34 | }), 35 | ], 36 | declarations: [AppComponent], 37 | providers: [TranslateService, TranslateStore], 38 | }).compileComponents(); 39 | })); 40 | 41 | beforeEach(() => { 42 | fixture = TestBed.createComponent(AppComponent); 43 | component = fixture.componentInstance; 44 | fixture.detectChanges(); 45 | }); 46 | 47 | it('should create the app', () => { 48 | const app = fixture.debugElement.componentInstance; 49 | expect(app).toBeTruthy(); 50 | }); 51 | 52 | it(`should have as title 'clean-angular'`, () => { 53 | const app = fixture.debugElement.componentInstance; 54 | expect(app.title).toEqual('opensource'); 55 | }); 56 | 57 | it('should call window.open when click link', () => { 58 | spyOn(window, 'open'); 59 | 60 | component.openLink('https://devops.phodal.com'); 61 | 62 | expect(window.open).toHaveBeenCalled(); 63 | }); 64 | 65 | it('should enable set language', () => { 66 | component.setLanguage('zh-cn'); 67 | 68 | expect(component.translate.currentLang).toEqual('zh-cn'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case", 23 | "component" 24 | ], 25 | "import-blacklist": [ 26 | true, 27 | "rxjs/Rx" 28 | ], 29 | "interface-name": false, 30 | "max-classes-per-file": false, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-access": false, 36 | "member-ordering": [ 37 | true, 38 | { 39 | "order": [ 40 | "static-field", 41 | "instance-field", 42 | "static-method", 43 | "instance-method" 44 | ] 45 | } 46 | ], 47 | "no-consecutive-blank-lines": false, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-empty": false, 57 | "no-inferrable-types": [ 58 | true, 59 | "ignore-params" 60 | ], 61 | "no-non-null-assertion": true, 62 | "no-redundant-jsdoc": true, 63 | "no-switch-case-fall-through": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/assets/docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## 什么是开源? 4 | 5 | 事实上,我们经常混淆了两个概念,那就是开源软件和开源这个行为。 6 | 7 | > 开源软件是源代码可以任意获取的计算机软件,任何人都能查看、修改和分发他们认为合适的代码。开源软件依托同行评审和社区生产,皆以分散、协作的方式开发。 —— 红帽官网 8 | 9 | 换句话来说,你选择一个协议,将你写的代码公开发布,这叫开源一个软件。但是,它并不叫你搞开源。开源源于开源软件,但是它现在已经成为超越软件生产的运行和工作方式。 10 | 11 | > 开源源于开源软件生产的运行和工作方式,它是一种基于去中心化、自组织式的软件开发模式运作的工作方式。它以社区作为根基,通过开放、透明、协作几项原则开展的活动。 12 | 13 | ## 开源只是公开代码? 14 | 15 | 在那本开源的《GitHub 漫游指南》里,我一直在讲述如何在 GitHub 上开发一个 “成功” 的开源项目。因为开源不仅仅只是说源代码的开源,还包含了设计文档、产品的内容等等,还要以开源的方式来运作。以 opensource.com 对于开源方式的解释来说,需要这么五个维度: 16 | 17 | 1. 透明度。 18 | 2. 协作。 19 | 3. 尽早发布、持续发布。快速建立原型,发布第一个版本,并且不断地快速地迭代。 20 | 4. 精英制度。根据提出的最佳方案做决定的方式 21 | 5. 社区。形成社区,提升社区参与度,转化为社区目标。 22 | 23 | 也因此,如果只是公开源码的话,那是走到开源的第一步,刚来到开源的起跑线上而已,还没参与到这个游戏中去。 24 | 25 | ## 开源项目是什么? 26 | 27 | 作为一个资深的开源运动参与者,我有一个这样的体会:运营一个开源项目,就好像创业一样。我们需要采用《黑客与画家》作者 Paul Graham 所说的创业公式: 28 | 29 | 1. 搭建原型 30 | 2. 上线运营 31 | 3. 收集反馈 32 | 4. 调整产品 33 | 5. 成长壮大 34 | 35 | 所以,开源就像是一场小型创业,需要进行竞品分析,需要制定合理的策略。当然了,如果你的东西绝无仅有,那就无需如此。而除了分析市场,针对于开发人员,还要考虑: 36 | 37 | 1. 作为投资人(技术投资),他们能获得什么?提升技术?找个好工作? 38 | 2. 作为潜在的用户,从哪里知道这个项目? 39 | 3. 作为贡献者,如何提供不同级别的贡献计划? 40 | 4. …… 41 | 42 | 你并不一定非得去考虑这些问题,只要在持续完善的过程中,这些问题的答案就会浮现出来。只是呢,在你开始之前想好,可能会事半功倍。 43 | 44 | ## 开源项目的重点? 45 | 46 | 对于个人来说,开源的目的可能是找个好工作、为以后找个好工作……;对于一家组织来说,他们考虑开源可能有多种多样的目的: 47 | 48 | 1. 降低开发、维护成本。由社区来帮助寻找 bug,提出一些观点。 49 | 2. 技术影响力招聘 50 | 3. 建立技术壁垒。 51 | 4. 营造生态。 52 | 5. …… 53 | 54 | 一个好的开源作品,需要连接到上下游,即影响开发者,又影响使用者。慢慢地,它个作品就会成为一个影响行业的存在。尽管会不断有其它的项目冒出来,但是由于稳固的生态建设,将巩固组织在该领域的影响力。 55 | 56 | ## 组织需要制定开源策略吗? 57 | 58 | 从开头的大部分四大难题:一次性开源、按揭开源、KPI 驱动式开源、社区是什么?。我们就会发现:国内大公司的开源策略都是错的。 59 | 60 | 他们可能,今年发布 Phodal UI,明后发布 Phodal Compiler,后年发布 Phodal OS。然后,中间靠各种公关稿,完成在社区的宣传。 61 | 62 | 应该是这样的,今年发布 Phodal UI 1.0,年中发布 Phodal UI 2.0,明年发布 Phodal UI 3.0 和 Phodal Compiler 1.0,明年年中 Phodal UI 4.0 + Phodal Compiler 2.0。过程中,需要依赖于布道师来进行闭环: 63 | 64 | 1. 维护开发者关系 65 | 2. 在社区进行宣传 66 | 3. 对社区进行支持、收集社区反馈 67 | 4. 建立连接内部的通道 68 | 5. 促进内部进行改进。 69 | 70 | 这些组织需要建立一个具备可持续性的开源策略: 71 | 72 | 1. 明确其带来的业务价值(如人才引进 、生态等) 73 | 2. 专职的开发人员进行开源支持 74 | 3. 开放式的开源团队组织结构 75 | 4. 合理、适当地长期 KPI 考核机制 76 | 5. 政策和流程支持。如专项鼓励奖金 77 | 6. 明确地专利和知识产权机制 78 | 79 | ## 开源到开放式组织 80 | 81 | 领导力变化,当我们在组织中开发一个软件应用时是以职权影响力为核心构建的;而开源方式,则是以非职权影响力构建的。 82 | 83 | 社区的每个人都可以提出自己的意见,你可以 say No,但是每个人都可以提出意见。就这一点来说,对于大部分的国内公司来说是一种挑战,大部分的领导希望听到统一的声音 —— 论组织内多样性的重要。 84 | 85 | 简单来说,大家想来就可以来,想走就可以走。所以,开源的一个难点就在于:如何吸引到人来参与开发。 86 | 87 | 尽管大部分项目都是围绕个人、团队的中心化开放式组织,如 linus 之于 Linux。但是,开源还可能变成一个中心化的组织,如 Node.js 的 IO.js 出走事件。根据开源协议,人们可以很容易派生出一个新的项目。 88 | 89 | -------------------------------------------------------------------------------- /src/app/presentation/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, OnInit } from '@angular/core'; 2 | import { Title } from '@angular/platform-browser'; 3 | import { NavigationEnd, Router } from '@angular/router'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import { Observable } from 'rxjs'; 6 | import { TranslateService } from '@ngx-translate/core'; 7 | 8 | import * as mdData from 'raw-loader!../../../assets/docs/home.md'; 9 | import { ShepherdService } from 'angular-shepherd'; 10 | import { StorageMap } from '@ngx-pwa/local-storage'; 11 | import { isScullyRunning } from '@scullyio/ng-lib'; 12 | 13 | @Component({ 14 | selector: 'app-home', 15 | templateUrl: './home.component.html', 16 | styleUrls: ['./home.component.scss'], 17 | }) 18 | export class HomeComponent implements OnInit, AfterViewInit { 19 | data = mdData.default; 20 | 21 | constructor( 22 | title: Title, 23 | private router: Router, 24 | private http: HttpClient, 25 | private storage: StorageMap, 26 | private shepherdService: ShepherdService, 27 | public translate: TranslateService 28 | ) { 29 | title.setTitle('开源软件 - 开源知识平台'); 30 | } 31 | 32 | category: string; 33 | 34 | homemd = mdData.default; 35 | allContributors$: Observable; 36 | inViewport = false; 37 | steps = []; 38 | 39 | setCurrentAtomCategory(category: string) { 40 | this.category = category; 41 | } 42 | 43 | ngOnInit(): void { 44 | this.router.events.subscribe((evt) => { 45 | if (!(evt instanceof NavigationEnd)) { 46 | return; 47 | } 48 | window.scrollTo(0, 0); 49 | }); 50 | } 51 | 52 | ngAfterViewInit(): void { 53 | this.allContributors$ = this.http 54 | .get('https://api.github.com/repos/phodal/ledge/contributors') 55 | .pipe(); 56 | 57 | this.shepherdService.defaultStepOptions = { 58 | cancelIcon: { 59 | enabled: true, 60 | }, 61 | }; 62 | this.shepherdService.modal = true; 63 | this.shepherdService.confirmCancel = false; 64 | this.shepherdService.addSteps(this.steps); 65 | 66 | if (!isScullyRunning()) { 67 | const doneKey = 'intro.hadDone'; 68 | this.storage.get(doneKey).subscribe((value: boolean) => { 69 | if (!value) { 70 | this.shepherdService.start(); 71 | this.storage.set(doneKey, true).subscribe(() => {}); 72 | } 73 | }); 74 | } 75 | } 76 | 77 | show(event: Partial) { 78 | if (event.intersectionRatio >= 0.5) { 79 | this.inViewport = true; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 7 | 8 |
9 | 10 |
11 | 12 |
13 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 39 | 43 | 47 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /scully.ledge.config.js: -------------------------------------------------------------------------------- 1 | const { Sitemap } = require('@gammastream/scully-plugin-sitemap'); 2 | const { registerPlugin } = require('@scullyio/scully'); 3 | 4 | const defaultPostRenderers = [Sitemap]; 5 | 6 | const sitemapOptions = { 7 | urlPrefix: 'https://opensource.phodal.com/', 8 | sitemapFilename: 'sitemap.xml', 9 | changeFreq: 'hourly', 10 | priority: [ 11 | '1.0', 12 | '0.9', 13 | '0.8', 14 | '0.7', 15 | '0.6', 16 | '0.5', 17 | '0.4', 18 | '0.3', 19 | '0.2', 20 | '0.1', 21 | '0.0', 22 | ], 23 | ignoredRoutes: ['/404'], 24 | }; 25 | 26 | function casePlugin(route, config) { 27 | return Promise.resolve([ 28 | { route: '/case-study/alibaba' }, 29 | { route: '/case-study/baidu' }, 30 | { route: '/case-study/tencent' }, 31 | { route: '/case-study/huawei' }, 32 | { route: '/case-study/microsoft' }, 33 | { route: '/case-study/google' }, 34 | { route: '/case-study/thoughtworks' }, 35 | ]); 36 | } 37 | 38 | function checklistsPlugin(route, config) { 39 | return Promise.resolve([ 40 | { route: '/checklists/opensource-release' }, 41 | { route: '/checklists/opensource-security' }, 42 | { route: '/checklists/opensource-development' }, 43 | { route: '/checklists/opensource-deployment' }, 44 | ]); 45 | } 46 | 47 | function practisePlugin(route, config) { 48 | return Promise.resolve([{ route: '/practise' }]); 49 | } 50 | 51 | function maturiyPlugin(route, config) { 52 | return Promise.resolve([ 53 | { route: '/maturity/evangelist' }, 54 | { route: '/maturity/project' }, 55 | ]); 56 | s; 57 | } 58 | 59 | function skilltreePlugin(route, config) { 60 | return Promise.resolve([{ route: '/skilltree/sample' }]); 61 | } 62 | 63 | const validator = async (conf) => []; 64 | registerPlugin('router', 'case', casePlugin, validator); 65 | registerPlugin('router', 'checklists', checklistsPlugin, validator); 66 | registerPlugin('router', 'practise', practisePlugin, validator); 67 | registerPlugin('router', 'maturity', maturiyPlugin, validator); 68 | registerPlugin('router', 'skilltree', skilltreePlugin, validator); 69 | 70 | exports.config = { 71 | projectRoot: './src', 72 | projectName: 'ledge', 73 | outDir: './dist/static', 74 | puppeteerLaunchOptions: { 75 | args: ['--no-sandbox', '--disable-setuid-sandbox'], 76 | defaultViewport: { 77 | width: 1440, 78 | height: 1080, 79 | }, 80 | }, 81 | sitemapOptions, 82 | defaultPostRenderers, 83 | routes: { 84 | '/case-study/:case': { 85 | type: 'case', 86 | }, 87 | '/checklists/:name': { 88 | type: 'checklists', 89 | }, 90 | '/practise/:practise': { 91 | type: 'practise', 92 | }, 93 | '/maturity/:name': { 94 | type: 'maturity', 95 | }, 96 | '/skilltree/:skilltree': { 97 | type: 'skilltree', 98 | }, 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '~material-design-icons/iconfont/material-icons.css'; 3 | @import "~@ledge-framework/render/prebuild-theme/index.css"; 4 | 5 | @import "styles/periodic-variables"; 6 | 7 | @import "styles/markdown"; 8 | @import "styles/markdown-render"; 9 | @import "styles/dragula"; 10 | @import "styles/behavior"; 11 | @import "styles/color"; 12 | @import "styles/material-ui"; 13 | @import "styles/mobile"; 14 | @import "styles/intro"; 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | * { 21 | box-sizing: border-box; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | 26 | body { 27 | min-width: 1200px; 28 | width: auto; 29 | font-family: 'Chinese Quote', 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji'; 30 | font-size: 16px; 31 | margin: 0; 32 | background-color: $background-color; 33 | } 34 | 35 | @media screen and (min-width: 2501px) and (max-width: 4000px) { 36 | body { 37 | font-size: 20px; 38 | } 39 | } 40 | 41 | .spacer { 42 | flex: 1 1 auto; 43 | } 44 | 45 | .center { 46 | text-align: center; 47 | } 48 | 49 | .scroll-to-top { 50 | .mat-fab.mat-accent { 51 | background-color: $purple; 52 | } 53 | } 54 | 55 | .markdown { 56 | .ledge-checklist { 57 | label { 58 | font-size: 1em !important; 59 | } 60 | } 61 | 62 | .home-container { 63 | margin: 0 auto; 64 | max-width: 112.0rem; 65 | padding: 0 2.0rem; 66 | position: relative; 67 | width: 100% 68 | } 69 | } 70 | 71 | .home-markdown { 72 | .markdown { 73 | p { 74 | padding: 2em; 75 | text-align: center; 76 | } 77 | } 78 | 79 | .card { 80 | p { 81 | padding: 0; 82 | text-align: left; 83 | } 84 | } 85 | } 86 | 87 | 88 | .header { 89 | $themeColours: ( 90 | 0: #F37C20, 91 | 1: #E9BD1E, 92 | 2: #00A2A1, 93 | 3: #0062CE, 94 | 4: #666666, 95 | 5: #DC5332, 96 | 6: #079948, 97 | 7: #999999, 98 | 8: #800080, 99 | 9: #003366, 100 | 10: #AA217E, 101 | 11: #55acee, 102 | 12: #444444, 103 | 13: #61bb46, 104 | 14: #007C78 105 | ); 106 | 107 | @each $i, $color in $themeColours { 108 | &.type_#{$i} { 109 | background: $color; 110 | } 111 | &.type_#{$i}:after { 112 | border-left: 22px solid $color; 113 | } 114 | } 115 | } 116 | 117 | .ledge-render { 118 | .table-step { 119 | .card-body { 120 | p { 121 | margin-bottom: 0 !important; 122 | } 123 | } 124 | } 125 | 126 | .flex-table { 127 | width: 100%; 128 | margin: 1em 0; 129 | display: flex; 130 | justify-content: center; 131 | } 132 | } 133 | 134 | .ledge-skilltree { 135 | .graphviz svg { 136 | height: 2048px; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/assets/images/mdb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 启动前 15 | 16 | 17 | 转变 18 | 组织文化 19 | 20 | 21 | 转变 22 | 组织架构 23 | 24 | 25 | 转变 26 | 组织职能 27 | 28 | 29 | 转变 30 | 组织节奏 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 专注 39 | 40 | 41 | 执行 42 | 43 | 44 | 优化 45 | 46 | 47 | 强化 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. 3 | */ 4 | import '@angular/localize/init'; 5 | /** 6 | * This file includes polyfills needed by Angular and is loaded before the app. 7 | * You can add your own extra polyfills to this file. 8 | * 9 | * This file is divided into 2 sections: 10 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 11 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 12 | * file. 13 | * 14 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 15 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 16 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 17 | * 18 | * Learn more in https://angular.io/guide/browser-support 19 | */ 20 | 21 | /*************************************************************************************************** 22 | * BROWSER POLYFILLS 23 | */ 24 | 25 | import 'core-js/es/object'; 26 | import 'core-js/es/function'; 27 | import 'core-js/es/parse-int'; 28 | import 'core-js/es/parse-float'; 29 | import 'core-js/es/number'; 30 | import 'core-js/es/math'; 31 | import 'core-js/es/string'; 32 | import 'core-js/es/date'; 33 | import 'core-js/es/array'; 34 | import 'core-js/es/regexp'; 35 | import 'core-js/es/map'; 36 | import 'core-js/es/weak-map'; 37 | import 'core-js/es/set'; 38 | 39 | import 'core-js/es/reflect'; 40 | 41 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 42 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 43 | 44 | /** 45 | * Web Animations `@angular/platform-browser/animations` 46 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 47 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 48 | */ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | /** 52 | * By default, zone.js will patch all possible macroTask and DomEvents 53 | * user can disable parts of macroTask/DomEvents patch by setting following flags 54 | * because those flags need to be set before `zone.js` being loaded, and webpack 55 | * will put import in the top of bundle, so user need to create a separate file 56 | * in this directory (for example: zone-flags.ts), and put the following flags 57 | * into that file, and then add the following code before importing zone.js. 58 | * import './zone-flags'; 59 | * 60 | * The flags allowed in zone-flags.ts are listed here. 61 | * 62 | * The following flags will work for all browsers. 63 | * 64 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 65 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 66 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 67 | * 68 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 69 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 70 | * 71 | * (window as any).__Zone_enable_cross_context_check = true; 72 | * 73 | */ 74 | 75 | /*************************************************************************************************** 76 | * Zone JS is required by default for Angular itself. 77 | */ 78 | import 'zone.js/dist/zone'; // Included with Angular CLI. 79 | 80 | (window as any).__Zone_enable_cross_context_check = true; // patch for IE 81 | /*************************************************************************************************** 82 | * APPLICATION IMPORTS 83 | */ 84 | (window as any).global = window; 85 | 86 | (window as any).global = window; // gets rid of the first error 87 | // @ts-ignore 88 | global.Buffer = global.Buffer || require('buffer').Buffer; // which leads to buffer being required 89 | (window as any).process = { 90 | // which leads to window.process.version needing to be parsed 91 | env: { DEBUG: undefined }, // https://github.com/nodejs/readable-stream/issues/313 92 | version: 'v0.9.', 93 | }; 94 | (window as any).setImmediate = window.setTimeout; // which leads to setImmediate being required 95 | 96 | /*************************************************************************************************** 97 | * SCULLY IMPORTS 98 | */ 99 | // tslint:disable-next-line: align 100 | import 'zone.js/dist/task-tracking'; 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ledge", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "./node_modules/.bin/ng", 6 | "start": "yarn ng serve", 7 | "build": "node --max_old_space_size=4096 ./node_modules/.bin/ng build", 8 | "build:ci": "node --max_old_space_size=4096 ./node_modules/.bin/ng build --configuration ci", 9 | "build:stats": "node --max_old_space_size=4096 ./node_modules/.bin/ng build --stats-json", 10 | "analyze": "webpack-bundle-analyzer dist/ledge/stats-es2015.json", 11 | "test": "yarn ng test", 12 | "test:ci": "yarn ng test --watch=false --progress=false --browsers=ChromeHeadlessCI --codeCoverage", 13 | "lint": "yarn ng lint", 14 | "commit": "git-cz", 15 | "package": "yarn build:ci && rm -rf dist/static && yarn scully", 16 | "deploy": "yarn package && npx angular-cli-ghpages --repo=https://github.com/phodal/oss-archive.git --dir=dist/static --cname=opensource.phodal.com", 17 | "scully": "scully --scanRoutes", 18 | "scully:serve": "scully serve", 19 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" 20 | }, 21 | "private": false, 22 | "dependencies": { 23 | "@angular/animations": "~9.0.3", 24 | "@angular/cdk": "^9.1.1", 25 | "@angular/common": "~9.0.3", 26 | "@angular/compiler": "~9.0.3", 27 | "@angular/core": "~9.0.3", 28 | "@angular/flex-layout": "^9.0.0-beta.29", 29 | "@angular/forms": "~9.0.3", 30 | "@angular/localize": "^9.1.0", 31 | "@angular/material": "^9.1.1", 32 | "@angular/platform-browser": "~9.0.3", 33 | "@angular/platform-browser-dynamic": "^9.1.0", 34 | "@angular/router": "~9.0.3", 35 | "@angular/service-worker": "~9.0.3", 36 | "@ledge-framework/render": "^1.0.2", 37 | "@ledge-framework/view": "^1.0.2", 38 | "@ngx-pwa/local-storage": "^9.0.2", 39 | "@ngx-starter-kit/ngx-utils": "^0.0.8", 40 | "@ngx-translate/core": "^12.1.2", 41 | "@ngx-translate/http-loader": "^4.0.0", 42 | "@scullyio/init": "^0.0.25", 43 | "@scullyio/ng-lib": "latest", 44 | "@scullyio/scully": "latest", 45 | "angular-shepherd": "^0.6.0", 46 | "angular-split": "^3.0.3", 47 | "core-js": "^3.6.4", 48 | "d3": "^5.15.0", 49 | "d3-graphviz": "^3.0.4", 50 | "dagre-d3": "^0.6.4", 51 | "date-fns": "^2.12.0", 52 | "echarts": "^4.6.0", 53 | "graphlib-dot": "^0.6.4", 54 | "marked": "^0.8.0", 55 | "material-design-icons": "^3.0.1", 56 | "mermaid": "^8.5.0", 57 | "ng2-dragula": "^2.1.1", 58 | "ngx-markdown": "^9.0.0", 59 | "ngx-virtual-scroller": "^4.0.3", 60 | "prismjs": "^1.20.0", 61 | "rxjs": "~6.5.4", 62 | "shortid": "^2.2.15", 63 | "tslib": "^1.10.0", 64 | "zone.js": "~0.10.2" 65 | }, 66 | "devDependencies": { 67 | "@angular-devkit/build-angular": "~0.900.4", 68 | "@angular-devkit/build-ng-packagr": "~0.900.5", 69 | "@angular/cli": "~9.0.4", 70 | "@angular/compiler-cli": "~9.0.3", 71 | "@angular/language-service": "~9.0.3", 72 | "@commitlint/cli": "^8.3.5", 73 | "@commitlint/config-conventional": "^8.3.4", 74 | "@gammastream/scully-plugin-sitemap": "^0.0.5", 75 | "@types/d3": "^5.7.2", 76 | "@types/echarts": "^4.4.3", 77 | "@types/jasmine": "~3.5.0", 78 | "@types/jasminewd2": "~2.0.3", 79 | "@types/node": "^12.11.1", 80 | "angular-cli-ghpages": "^0.6.2", 81 | "codelyzer": "^5.1.2", 82 | "cz-conventional-changelog": "3.1.0", 83 | "husky": "^4.2.3", 84 | "jasmine-core": "~3.5.0", 85 | "jasmine-spec-reporter": "~4.2.1", 86 | "karma": "~4.3.0", 87 | "karma-chrome-launcher": "^3.1.0", 88 | "karma-coverage-istanbul-reporter": "~2.1.0", 89 | "karma-jasmine": "~2.0.1", 90 | "karma-jasmine-html-reporter": "^1.4.2", 91 | "lint-staged": ">=10", 92 | "ng-packagr": "^9.0.0", 93 | "prettier": "^2.0.2", 94 | "puppeteer": "^2.1.1", 95 | "stylelint": "^13.3.0", 96 | "stylelint-config-standard": "^20.0.0", 97 | "stylelint-scss": "^3.16.0", 98 | "ts-node": "~8.3.0", 99 | "tslint": "~5.18.0", 100 | "typescript": "~3.7.5", 101 | "webpack-bundle-analyzer": "^3.6.1", 102 | "xmlbuilder": "^15.1.0" 103 | }, 104 | "browser": { 105 | "crypto": false 106 | }, 107 | "husky": { 108 | "hooks": { 109 | "pre-commit": "lint-staged", 110 | "pre-push": "yarn run test:ci" 111 | } 112 | }, 113 | "lint-staged": { 114 | "src/app/**/*.{css,scss}": [ 115 | "stylelint --syntax=scss", 116 | "prettier --parser --write" 117 | ], 118 | "{src,test}/**/*.ts": [ 119 | "prettier --write --single-quote" 120 | ], 121 | "*.{js,css,md}": "prettier --write" 122 | }, 123 | "config": { 124 | "commitizen": { 125 | "path": "./node_modules/cz-conventional-changelog" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/assets/images/arch-level.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 应用级 18 | 19 | 20 | 系统级 21 | 22 | 23 | 模块级 24 | 25 | 26 | 代码级 27 | 28 | 29 | 30 | 31 | 分层架构、微内核架构等 32 | 33 | 34 | 35 | 36 | 基础设施层 37 | 38 | 39 | 40 | 规范、原则、质量等 41 | 42 | 43 | 基础适应度函数 44 | 45 | 46 | 组件化、模块化、插件化等 47 | 48 | 49 | 模式库、设计系统、云原生、DevOps 等 50 | 51 | 52 | 服务导向架构、聚合导向架构等 53 | 54 | 55 | 56 | 57 | 文档 58 | 59 | 60 | 构建系统 61 | 62 | 63 | 工作流 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ledge": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/ledge", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets", 29 | "src/README.md", 30 | "src/manifest.webmanifest" 31 | ], 32 | "styles": [ 33 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 34 | "src/styles.scss", 35 | "node_modules/prismjs/themes/prism-okaidia.css" 36 | ], 37 | "scripts": [ 38 | "node_modules/marked/lib/marked.js", 39 | "node_modules/prismjs/prism.js", 40 | "node_modules/prismjs/components/prism-javascript.min.js", 41 | "node_modules/prismjs/components/prism-java.min.js", 42 | "node_modules/prismjs/components/prism-python.min.js", 43 | "node_modules/prismjs/components/prism-gherkin.min.js", 44 | "node_modules/prismjs/components/prism-groovy.min.js", 45 | "node_modules/prismjs/components/prism-json.min.js", 46 | "node_modules/prismjs/components/prism-makefile.min.js" 47 | ] 48 | }, 49 | "configurations": { 50 | "ci": { 51 | "fileReplacements": [ 52 | { 53 | "replace": "src/environments/environment.ts", 54 | "with": "src/environments/environment.ci.ts" 55 | } 56 | ], 57 | "optimization": true, 58 | "outputHashing": "all", 59 | "sourceMap": false, 60 | "extractCss": true, 61 | "namedChunks": false, 62 | "extractLicenses": true, 63 | "vendorChunk": false, 64 | "buildOptimizer": true, 65 | "budgets": [ 66 | { 67 | "type": "initial", 68 | "maximumWarning": "2mb", 69 | "maximumError": "5mb" 70 | }, 71 | { 72 | "type": "anyComponentStyle", 73 | "maximumWarning": "6kb", 74 | "maximumError": "10kb" 75 | } 76 | ], 77 | "serviceWorker": false 78 | }, 79 | "production": { 80 | "fileReplacements": [ 81 | { 82 | "replace": "src/environments/environment.ts", 83 | "with": "src/environments/environment.prod.ts" 84 | } 85 | ], 86 | "optimization": true, 87 | "outputHashing": "all", 88 | "sourceMap": false, 89 | "extractCss": true, 90 | "namedChunks": false, 91 | "extractLicenses": true, 92 | "vendorChunk": false, 93 | "buildOptimizer": true, 94 | "budgets": [ 95 | { 96 | "type": "initial", 97 | "maximumWarning": "2mb", 98 | "maximumError": "5mb" 99 | }, 100 | { 101 | "type": "anyComponentStyle", 102 | "maximumWarning": "6kb", 103 | "maximumError": "10kb" 104 | } 105 | ], 106 | "serviceWorker": false, 107 | "ngswConfigPath": "ngsw-config.json" 108 | } 109 | } 110 | }, 111 | "serve": { 112 | "builder": "@angular-devkit/build-angular:dev-server", 113 | "options": { 114 | "browserTarget": "ledge:build" 115 | }, 116 | "configurations": { 117 | "production": { 118 | "browserTarget": "ledge:build:production" 119 | }, 120 | "ci": { 121 | "browserTarget": "ledge:build:production" 122 | } 123 | } 124 | }, 125 | "extract-i18n": { 126 | "builder": "@angular-devkit/build-angular:extract-i18n", 127 | "options": { 128 | "browserTarget": "ledge:build" 129 | } 130 | }, 131 | "test": { 132 | "builder": "@angular-devkit/build-angular:karma", 133 | "options": { 134 | "main": "src/test.ts", 135 | "polyfills": "src/polyfills.ts", 136 | "tsConfig": "tsconfig.spec.json", 137 | "karmaConfig": "karma.conf.js", 138 | "assets": [ 139 | "src/favicon.ico", 140 | "src/assets", 141 | "src/manifest.webmanifest" 142 | ], 143 | "styles": [ 144 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 145 | "src/styles.scss" 146 | ], 147 | "scripts": [] 148 | } 149 | }, 150 | "lint": { 151 | "builder": "@angular-devkit/build-angular:tslint", 152 | "options": { 153 | "tsConfig": [ 154 | "tsconfig.app.json", 155 | "tsconfig.spec.json" 156 | ], 157 | "exclude": [ 158 | "**/node_modules/**" 159 | ] 160 | } 161 | }, 162 | "deploy": { 163 | "builder": "angular-cli-ghpages:deploy", 164 | "options": {} 165 | } 166 | } 167 | } 168 | }, 169 | "defaultProject": "ledge", 170 | "cli": { 171 | "analytics": false 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/app/presentation/home/home.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/color'; 2 | 3 | .home-container { 4 | position: relative; 5 | z-index: 100; 6 | top: 64px; 7 | 8 | a { 9 | text-decoration: none; 10 | color: $purple; 11 | 12 | &:hover { 13 | color: $purple; 14 | } 15 | } 16 | } 17 | 18 | .intro { 19 | width: 80%; 20 | max-width: 1400px; 21 | margin: 1em auto; 22 | position: relative; 23 | text-align: center; 24 | } 25 | 26 | .title-desc { 27 | width: 100%; 28 | background: $black; 29 | color: $white; 30 | text-align: center; 31 | padding: 50px 0; 32 | 33 | h1 { 34 | font-size: 48px; 35 | padding: 32px; 36 | font-weight: bold; 37 | } 38 | 39 | p { 40 | font-size: 18px; 41 | width: 800px; 42 | margin: 0 auto; 43 | line-height: 1.5; 44 | } 45 | 46 | .home-link { 47 | text-decoration: none; 48 | color: #fff; 49 | } 50 | } 51 | 52 | .cards { 53 | width: 80%; 54 | margin: 3em auto; 55 | display: flex; 56 | flex-direction: row; 57 | justify-content: space-between; 58 | 59 | .card { 60 | color: $white; 61 | background: #424242; 62 | width: 300px; 63 | } 64 | } 65 | 66 | .period-title { 67 | margin: 0; 68 | 69 | h2, 70 | h4 { 71 | text-align: center; 72 | margin: 1em auto 0; 73 | } 74 | } 75 | 76 | .process-bar { 77 | padding: 1em 0; 78 | margin: 0 1em; 79 | } 80 | 81 | .second-header { 82 | margin-top: 4em; 83 | margin-bottom: 2em; 84 | } 85 | 86 | .esp-insight { 87 | padding-top: 4em; 88 | margin: 2em auto 4em; 89 | width: 1080px; 90 | 91 | img { 92 | text-align: center; 93 | margin-top: 2em; 94 | } 95 | } 96 | 97 | .inspired { 98 | margin-top: 8em; 99 | text-align: center; 100 | } 101 | 102 | .home-section { 103 | width: 1200px; 104 | margin: 2em auto 0; 105 | background-color: $white; 106 | 107 | &.period-section { 108 | width: auto; 109 | padding: 2em; 110 | } 111 | 112 | .section-title { 113 | font-size: 40px; 114 | letter-spacing: 2.5px; 115 | padding: 36px 0; 116 | text-align: center; 117 | font-weight: 600; 118 | } 119 | 120 | .section-sub-title { 121 | text-align: center; 122 | font-size: 20px; 123 | font-weight: 600; 124 | letter-spacing: 1.25px; 125 | } 126 | 127 | .sample-title { 128 | font-size: 16px; 129 | line-height: 1.5; 130 | letter-spacing: 1px; 131 | text-align: center; 132 | margin-top: 16px; 133 | } 134 | 135 | .section-content { 136 | padding-bottom: 60px; 137 | margin-top: 36px; 138 | 139 | img { 140 | display: block; 141 | margin: 0 auto; 142 | } 143 | } 144 | 145 | .process-title { 146 | font-size: 16px; 147 | line-height: 1.5; 148 | letter-spacing: 1px; 149 | text-align: center; 150 | margin: 16px auto 0; 151 | width: 800px; 152 | } 153 | 154 | .process-content { 155 | margin: 40px auto; 156 | width: 1080px; 157 | height: 310px; 158 | background-color: #f3f4f5; 159 | padding: 61px 0; 160 | } 161 | 162 | .devops-content { 163 | margin: 40px auto; 164 | width: 1084px; 165 | 166 | p { 167 | text-align: center; 168 | } 169 | } 170 | 171 | .section-more { 172 | p { 173 | font-size: 1.2em; 174 | line-height: 1.5; 175 | margin-bottom: 2em; 176 | } 177 | 178 | padding: 2em; 179 | text-align: center; 180 | } 181 | } 182 | 183 | .relate { 184 | background-color: transparent; 185 | 186 | .relate-title { 187 | font-size: 24px; 188 | text-align: center; 189 | line-height: 1.5; 190 | padding: 1em 0; 191 | } 192 | 193 | .relate-resources { 194 | display: flex; 195 | flex-direction: row; 196 | 197 | a:hover { 198 | color: $purple; 199 | } 200 | 201 | a::after { 202 | content: ''; 203 | display: block; 204 | background: url('/assets/resources/images/arrow.svg') no-repeat; 205 | background-size: 10px 20px; 206 | width: 10px; 207 | height: 20px; 208 | float: right; 209 | right: 12px; 210 | position: relative; 211 | top: 52px; 212 | } 213 | 214 | .resource-item { 215 | flex: 1 1 auto; 216 | height: 120px; 217 | margin-right: 2em; 218 | width: 25%; 219 | line-height: 120px; 220 | background-color: $white; 221 | text-decoration: none; 222 | color: $black; 223 | text-align: center; 224 | font-size: 20px; 225 | 226 | &:last-child { 227 | margin-right: 0; 228 | } 229 | 230 | &:hover { 231 | box-shadow: 0 8px 20px 0 rgba(0, 0, 0, 0.1); 232 | } 233 | } 234 | } 235 | } 236 | 237 | :host ::ng-deep { 238 | .devops-content { 239 | .no-toc-markdown { 240 | width: 100%; 241 | } 242 | 243 | .render-item { 244 | padding: 0; 245 | } 246 | } 247 | } 248 | 249 | .contribution { 250 | border-radius: 8px; 251 | margin-top: 4em; 252 | box-shadow: 0 8px 20px 0 rgba(0, 0, 0, 0.1); 253 | 254 | .contributors { 255 | display: flex; 256 | height: auto; 257 | flex-wrap: wrap; 258 | 259 | .contributor { 260 | width: 200px; 261 | height: 240px; 262 | min-height: 180px; 263 | text-align: center; 264 | margin: auto; 265 | 266 | a { 267 | text-decoration: none; 268 | color: #000; 269 | 270 | &:visited { 271 | color: black; 272 | } 273 | } 274 | 275 | .username { 276 | padding: 6px; 277 | } 278 | 279 | .name { 280 | font-size: 1.4em; 281 | color: black; 282 | padding: 6px; 283 | } 284 | 285 | .title { 286 | font-size: 12px; 287 | color: $black; 288 | } 289 | 290 | .work { 291 | font-size: 1em; 292 | padding: 6px; 293 | color: $black; 294 | line-height: 1.5; 295 | } 296 | 297 | img { 298 | height: 120px; 299 | width: 120px; 300 | border-radius: 120px; 301 | } 302 | 303 | .small-avatar { 304 | img { 305 | width: 60px; 306 | height: 60px; 307 | } 308 | } 309 | } 310 | 311 | &.all-contributors { 312 | .contributor { 313 | width: 100px; 314 | height: 120px; 315 | min-height: 120px; 316 | } 317 | } 318 | } 319 | } 320 | 321 | .footer { 322 | background: $black; 323 | display: flex; 324 | color: $white; 325 | margin-top: 2em; 326 | height: 40em; 327 | } 328 | 329 | .join-us-content { 330 | font-size: 1.4em; 331 | text-align: center; 332 | } 333 | -------------------------------------------------------------------------------- /src/styles/intro.scss: -------------------------------------------------------------------------------- 1 | .shepherd-element{background:#fff;border-radius:5px;box-shadow:0 1px 4px rgba(0,0,0,.2);max-width:600px;opacity:0;outline:none;transition:opacity .3s;width:100%;z-index:9999}.shepherd-enabled.shepherd-element{opacity:1}.shepherd-element[data-popper-reference-hidden]:not(.shepherd-centered){opacity:0}.shepherd-element,.shepherd-element *,.shepherd-element :after,.shepherd-element :before{box-sizing:border-box}.shepherd-arrow,.shepherd-arrow:before{position:absolute;width:16px;height:16px;z-index:-1}.shepherd-arrow:before{content:"";transform:rotate(45deg);background:#fff}.shepherd-element[data-popper-placement^=top]>.shepherd-arrow{bottom:-8px}.shepherd-element[data-popper-placement^=bottom]>.shepherd-arrow{top:-8px}.shepherd-element[data-popper-placement^=left]>.shepherd-arrow{right:-8px}.shepherd-element[data-popper-placement^=right]>.shepherd-arrow{left:-8px}.shepherd-element.shepherd-centered>.shepherd-arrow{opacity:0}.shepherd-element.shepherd-has-title[data-popper-placement^=bottom]>.shepherd-arrow:before{background-color:#e6e6e6}.shepherd-target-click-disabled.shepherd-enabled.shepherd-target,.shepherd-target-click-disabled.shepherd-enabled.shepherd-target *{pointer-events:none} 2 | .shepherd-modal-overlay-container{-ms-filter:progid:dximagetransform.microsoft.gradient.alpha(Opacity=50);filter:alpha(opacity=50);height:0;left:0;opacity:0;overflow:hidden;pointer-events:none;position:fixed;top:0;transition:all .3s ease-out,height 0ms .3s,opacity .3s 0ms;width:100vw;z-index:9997}.shepherd-modal-overlay-container.shepherd-modal-is-visible{height:100vh;opacity:.5;transition:all .3s ease-out,height 0s 0s,opacity .3s 0s}.shepherd-modal-overlay-container.shepherd-modal-is-visible path{pointer-events:all} 3 | .shepherd-content{border-radius:5px;outline:none;padding:0} 4 | .shepherd-footer{border-bottom-left-radius:5px;border-bottom-right-radius:5px;display:flex;justify-content:flex-end;padding:0 .75rem .75rem}.shepherd-footer .shepherd-button:last-child{margin-right:0} 5 | .shepherd-header{align-items:center;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;justify-content:flex-end;line-height:2em;padding:.75rem .75rem 0}.shepherd-has-title .shepherd-content .shepherd-header{background:#e6e6e6;padding:1em} 6 | .shepherd-text{color:rgba(0,0,0,.75);font-size:1rem;line-height:1.3em;max-height:30vh;overflow:scroll;padding:.75em}.shepherd-text p{margin-top:0}.shepherd-text p:last-child{margin-bottom:0} 7 | .shepherd-button{background:#3288e6;border:0;border-radius:3px;color:hsla(0,0%,100%,.75);cursor:pointer;margin-right:.5rem;padding:.5rem 1.5rem;transition:all .5s ease}.shepherd-button:not(:disabled):hover{background:#196fcc;color:hsla(0,0%,100%,.75)}.shepherd-button.shepherd-button-secondary{background:#f1f2f3;color:rgba(0,0,0,.75)}.shepherd-button.shepherd-button-secondary:not(:disabled):hover{background:#d6d9db;color:rgba(0,0,0,.75)}.shepherd-button:disabled{cursor:not-allowed} 8 | .shepherd-cancel-icon{background:transparent;border:none;color:hsla(0,0%,50.2%,.75);font-size:2em;cursor:pointer;font-weight:400;margin:0;padding:0;transition:color .5s ease}.shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon{color:hsla(0,0%,50.2%,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)} 9 | .shepherd-title{color:rgba(0,0,0,.75);display:flex;font-size:1rem;font-weight:400;flex:1 0 auto;margin:0;padding:0} 10 | 11 | .shepherd-button { 12 | background: #ffffff; 13 | border-top: solid 4px #16202D; 14 | border-radius: 0; 15 | color: #16202D; 16 | display: flex; 17 | flex-grow: 1; 18 | font-family: "GT Pressura", sans-serif; 19 | font-size: 1rem; 20 | justify-content: center; 21 | margin: 0; 22 | padding: 1rem; 23 | text-align: center; 24 | text-transform: uppercase; 25 | } 26 | 27 | .shepherd-button:hover { 28 | background: #16202D; 29 | color: #ffffff; 30 | } 31 | 32 | .shepherd-button.shepherd-button-secondary { 33 | background: #CAD5D5; 34 | } 35 | 36 | .shepherd-button.shepherd-button-secondary:hover { 37 | color: #CAD5D5; 38 | background: #16202D; 39 | } 40 | 41 | .shepherd-cancel-icon { 42 | font-family: "GT Pressura", sans-serif; 43 | } 44 | 45 | .shepherd-element { 46 | border: solid 4px #16202D; 47 | } 48 | 49 | .shepherd-element, 50 | .shepherd-header, 51 | .shepherd-footer { 52 | border-radius: 0; 53 | } 54 | 55 | .shepherd-element .shepherd-arrow { 56 | border-width: 0; 57 | height: auto; 58 | width: auto; 59 | } 60 | 61 | .shepherd-arrow::before { 62 | display: none; 63 | } 64 | 65 | .shepherd-element .shepherd-arrow:after { 66 | content: url('/assets/images/arrow.svg'); 67 | display: inline-block; 68 | } 69 | 70 | .shepherd-element[data-popper-placement^='top'] .shepherd-arrow, 71 | .shepherd-element.shepherd-pinned-top .shepherd-arrow { 72 | bottom: -35px; 73 | } 74 | 75 | .shepherd-element[data-popper-placement^='top'] .shepherd-arrow:after, 76 | .shepherd-element.shepherd-pinned-top .shepherd-arrow:after { 77 | transform: rotate(270deg); 78 | } 79 | 80 | .shepherd-element[data-popper-placement^='bottom'] .shepherd-arrow { 81 | top: -35px; 82 | } 83 | 84 | .shepherd-element[data-popper-placement^='bottom'] .shepherd-arrow:after { 85 | transform: rotate(90deg); 86 | } 87 | 88 | .shepherd-element[data-popper-placement^='left'] .shepherd-arrow, 89 | .shepherd-element.shepherd-pinned-left .shepherd-arrow { 90 | right: -35px; 91 | } 92 | 93 | .shepherd-element[data-popper-placement^='left'] .shepherd-arrow:after, 94 | .shepherd-element.shepherd-pinned-left .shepherd-arrow:after { 95 | transform: rotate(180deg); 96 | } 97 | 98 | .shepherd-element[data-popper-placement^='right'] .shepherd-arrow, 99 | .shepherd-element.shepherd-pinned-right .shepherd-arrow { 100 | left: -35px; 101 | } 102 | 103 | .shepherd-footer { 104 | padding: 0; 105 | } 106 | 107 | .shepherd-footer button:not(:last-of-type) { 108 | border-right: solid 4px #16202D; 109 | } 110 | 111 | .shepherd-has-title .shepherd-content .shepherd-cancel-icon { 112 | margin-top: -7px; 113 | } 114 | 115 | .shepherd-has-title .shepherd-content .shepherd-header { 116 | background: transparent; 117 | font-family: "GT Pressura", sans-serif; 118 | padding-bottom: 0; 119 | padding-left: 2rem; 120 | } 121 | 122 | .shepherd-has-title .shepherd-content .shepherd-header .shepherd-title { 123 | font-weight: bold; 124 | font-size: 1.2rem; 125 | text-transform: uppercase; 126 | } 127 | 128 | .shepherd-text { 129 | font-size: 1.2rem; 130 | padding: 2rem; 131 | } 132 | 133 | .shepherd-text a, .shepherd-text a:visited, 134 | .shepherd-text a:active { 135 | border-bottom: 1px dotted; 136 | border-bottom-color: rgba(0, 0, 0, 0.75); 137 | color: rgba(0, 0, 0, 0.75); 138 | text-decoration: none; 139 | } 140 | 141 | .shepherd-text a:hover, .shepherd-text a:visited:hover, 142 | .shepherd-text a:active:hover { 143 | border-bottom-style: solid; 144 | } 145 | -------------------------------------------------------------------------------- /src/styles/_markdown-render.scss: -------------------------------------------------------------------------------- 1 | @import "./color"; 2 | 3 | .virtual-scroll-render { 4 | .ledge-render { 5 | top: 66px; 6 | height: 100%; 7 | 8 | virtual-scroller, 9 | ledge-render { 10 | height: 100%; 11 | } 12 | } 13 | } 14 | 15 | .markdown-toc { 16 | display: flex; 17 | flex-direction: row; 18 | width: 100%; 19 | overflow-y: hidden; 20 | 21 | .right-content { 22 | width: 85%; 23 | height: calc(100vh - 66px); 24 | overflow-y: scroll; 25 | 26 | .ledge-render { 27 | width: calc(100% - 2em); 28 | border: solid 1px $border-color; 29 | background: $white; 30 | margin: 2em 1em; 31 | } 32 | } 33 | 34 | .left-drawer { 35 | width: 20%; 36 | min-width: 240px; 37 | font-size: 16px; 38 | overflow-y: scroll; 39 | height: calc(100vh - 66px); 40 | padding-right: 8px; 41 | overflow-x: hidden; 42 | background: $white; 43 | border-right: solid 1px $border-color; 44 | } 45 | } 46 | 47 | .reporter-page, 48 | .multiple-docs { 49 | .no-toc-markdown { 50 | height: calc(100vh - 66px); 51 | overflow-y: scroll; 52 | width: 100% !important; 53 | margin: 0 !important; 54 | 55 | .ledge-render { 56 | width: calc(100% - 2em); 57 | border: solid 1px $border-color; 58 | background: $white; 59 | margin: 2em 1em; 60 | } 61 | } 62 | 63 | .active { 64 | background-color: $purple; 65 | color: #fff; 66 | border-radius: 24px; 67 | 68 | &:hover { 69 | color: #fff !important; 70 | background-color: $purple !important; 71 | } 72 | } 73 | 74 | .left-drawer { 75 | ul { 76 | margin: 0; 77 | padding: 0 12px; 78 | 79 | li { 80 | min-height: 44px; 81 | line-height: 44px; 82 | font-size: 1.1rem; 83 | text-align: center; 84 | display: block; 85 | margin: 8px 0; 86 | 87 | &:active, 88 | &:focus { 89 | outline: 0; 90 | border: none; 91 | -moz-outline-style: none; 92 | border-radius: 24px; 93 | } 94 | 95 | @extend .noselect; 96 | 97 | &:hover { 98 | cursor: pointer; 99 | border-radius: 24px; 100 | background-color: $hover-grey; 101 | color: $black; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | .left-drawer { 109 | display: flex; 110 | flex: 1; 111 | 112 | .progress { 113 | $gap: 10px; 114 | $line-height: 24px; 115 | $bullet-radius: 5px; 116 | $line-thick: 1px; 117 | $next-color: #666; 118 | $current-color: #333; 119 | $prev-color: #333; 120 | 121 | display: inline-flex; 122 | height: 100%; 123 | 124 | > div { 125 | display: flex; 126 | flex-direction: column; 127 | color: $purple; 128 | 129 | &.left { 130 | padding-right: $gap; 131 | padding-left: $gap; 132 | text-align: right; 133 | 134 | // Line 135 | div { 136 | &:last-of-type:after { 137 | display: none; 138 | } 139 | 140 | &:after { 141 | content: ""; 142 | background: fade_out($purple, .9); //rgba(0, 0, 0, 0.6); 143 | border-radius: 2px; 144 | position: absolute; 145 | right: -$gap; 146 | top: $line-height/2; 147 | height: 101%; 148 | width: 1px; 149 | transform: translateX(50%); 150 | } 151 | } 152 | } 153 | 154 | &.right { 155 | padding-left: $gap; 156 | 157 | .level_1, 158 | .level_2, 159 | .level_3, 160 | .level_4, 161 | .level_5 { 162 | margin: 8px 0; 163 | 164 | a { 165 | text-decoration: none; 166 | color: #000; 167 | 168 | &:hover { 169 | color: $purple; 170 | } 171 | 172 | &.active { 173 | color: $purple; 174 | } 175 | } 176 | } 177 | 178 | .level_1 { 179 | font-weight: 400; 180 | } 181 | 182 | .level_2 { 183 | font-weight: 300; 184 | } 185 | 186 | .level_3, 187 | .level_4, 188 | .level_5 { 189 | font-weight: 100; 190 | } 191 | 192 | div { 193 | &.prev { 194 | &:after { 195 | transition: none; 196 | } 197 | } 198 | 199 | &.active { 200 | color: $current-color; 201 | font-weight: bold; 202 | 203 | &:before { 204 | background: $current-color; 205 | padding: $bullet-radius * 2; 206 | transition: all 0.2s .15s cubic-bezier(0.175, 0.885, 0.32, 2); 207 | } 208 | 209 | &:after { 210 | height: 0%; 211 | transition: height .2s ease-out; 212 | } 213 | 214 | ~ div { 215 | color: $next-color; 216 | 217 | &:before { 218 | background: $next-color; 219 | padding: $bullet-radius * 0.5; 220 | } 221 | 222 | &:after { 223 | height: 0%; 224 | transition: none; 225 | } 226 | } 227 | } 228 | 229 | &.level_1 { 230 | font-size: 16px; 231 | padding-left: 12px; 232 | // Dot 233 | &:before { 234 | content: ""; 235 | border-radius: 50%; 236 | position: absolute; 237 | top: $line-height/2; 238 | left: -$gap; 239 | height: 12px; 240 | width: 12px; 241 | border-radius: $line-height/$line-height; 242 | border: solid 1px $purple; 243 | background: #fff; 244 | transform: translateX(-50%) translateY(-50%); 245 | transition: padding 0.2s ease; 246 | } 247 | 248 | // Line 249 | &:after { 250 | content: ""; 251 | background: $purple; 252 | border-radius: 2px; 253 | position: absolute; 254 | left: -$gap; 255 | top: 18px; 256 | height: 101%; 257 | width: $line-thick; 258 | transform: translateX(-50%); 259 | transition: height 0.2s ease; 260 | } 261 | } 262 | 263 | .level_2, 264 | .level_3, 265 | .level_4, 266 | .level_5 { 267 | padding-left: 8px; 268 | } 269 | 270 | .level_2 { 271 | font-size: 14px; 272 | 273 | &:before { 274 | position: relative; 275 | left: -6px; 276 | content: ''; 277 | display: inline-block; 278 | width: 8px; 279 | height: 8px; 280 | border-radius: 4px; 281 | background-color: $purple; 282 | } 283 | } 284 | 285 | .level_3, .level_4, .level_5 { 286 | font-size: 12px; 287 | 288 | &:before { 289 | height: 0; 290 | position: relative; 291 | content: ""; 292 | top: -1px; 293 | display: inline-block; 294 | vertical-align: middle; 295 | border: 1px solid $purple; 296 | right: 4px; 297 | width: 4px; 298 | } 299 | } 300 | } 301 | } 302 | 303 | div { 304 | flex: 1; 305 | position: relative; 306 | cursor: default; 307 | 308 | &:last-of-type { 309 | flex: 0; 310 | } 311 | } 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/assets/docs/practise.md: -------------------------------------------------------------------------------- 1 | # 开源最佳实践 2 | 3 | # 工程能力 4 | 5 | ## 高度自动化 6 | 7 | ## 文档体验设计 8 | 9 | ## 充分测试 10 | 11 | # 开源文化 12 | 13 | ## 公开透明 14 | 15 | ## 开放式地管理 16 | 17 | # 产品思维 18 | 19 | ## 和产品策略一致 20 | 21 | ## 制定营销策略 22 | 23 | ## 项目管理 24 | 25 | ## 请求意见稿 26 | 27 | # 社区与生态 28 | 29 | ## 培养关键意见领袖 30 | 31 | ## 反馈驱动开发 32 | 33 | ## 技术布道 34 | 35 | ## 开发者生态 36 | 37 | # 组织支持 38 | 39 | ## 内部开源 40 | 41 | ## 开源投资战略 42 | 43 | ## 开源项目管理 44 | 45 | ### 流程 46 | 47 | #### 使用流程 48 | 49 | #### 发布流程 50 | 51 | ### 度量 52 | 53 | ## 开源指南 54 | 55 | ### 参与开源 56 | 57 | 源自:[How to become an open source enterprise](https://resources.github.com/whitepapers/How-to-Become-an-Open-Source-Enterprise/) 58 | 59 | - 阅读项目的 `README` 和 `CONTRIBUTING` 文件,以获取有关如何有效参与的指南。 60 | - 订阅通知,阅读和评论问题,并提出解决请求问题的解决方案。 61 | - 使用您喜欢的编程语言探索开源项目。寻找既使您感兴趣,又与组织战略保持一致的项目。 62 | - 尽可能地标准化开源库的使用,以简化维护并减少暴露。 63 | - 在上游向开源项目贡献您的本地改进,减轻维护人员的负担,并向社区提供有价值的反馈。 64 | - 查看您企业的开源投资,并选择最关键的项目进行战略重点关注。 这可能包括增加时间,精力甚至财务援助。 65 | - 回馈给开源项目时,请使用 `-on-behalf-commit` 标签,以表示贵组织的支持。 66 | 67 | ### 发布开源指南 68 | 69 | ### 开源协议指南 70 | 71 | # 个人开源流程 72 | 73 | ![开源软件开发流程](http://articles.phodal.com/os-builder/os-builder.png) 74 | 75 | 即下面的 11 个步骤: 76 | 77 | 0. 问题。 78 | 1. 目标。制定出一个 SMART 的目标 79 | 2. 想法。确认出需要发布的功能 80 | 3. 名字。是的,你需要一个吊炸天的名字 81 | 4. 开始构建。寻找模板创建你的 Hello, World 82 | 5. 编写功能。 83 | 6. 发布。尽早地发现 0.1.0 版本 84 | 7. 迭代。有计划的发布功能,直到它完成你想要的功能 -> 1.0.0 85 | 8. 自动化。测试,持续集成,持续发布。 86 | 9. 接受反馈。评估用户的反馈,决定是否添加为功能 87 | 10. marketing。编写文档、文章、博客,在社交媒体进行宣传 88 | 89 | 等等,真实的情况并没有这么复杂,在这里只是为了方便叙述。 90 | 91 | Then,让我们一点点地脱下开源的面纱。 92 | 93 | ## 0.问题:为什么这个软件存在着 94 | 95 | 有用的软件都是为了解决某些特定需求、问题而存在的。在开源软件世界里,从小至左填充一个字符串的 ``left-pad``,大到改变世界的 Linux,他们都解决了一些特定的问题。尽管有一些可能是学习而创建的,但是它解决了开发者的学习问题。 96 | 97 | 总而言之,就是软件存在的意义。 98 | 99 | 如在先前的文章《[2017 年节点——我写的那些开源软件](https://www.phodal.com/blog/summary-2017-the-opensource-software/)》总结的那样,我们想造一个轮子的来源可以是: 100 | 101 | - 日常工作中遇到的一些问题,提出对应的解决方案。 102 | - 使用某个开源软件的过程中,发现它不能满足我们的需求 103 | - 提取出工作上的一些好的技术实践 104 | - 我想开发一个工具,来帮助其它人 105 | - 我有一个想法,我要用它来改变世界 106 | - 无聊,我就是想造个轮子 107 | 108 | 接下来,让我举几个例子。 109 | 110 | ### 现在软件不能满足需求 111 | 112 | 当我在自己的项目上实施**架构决策记录**的时候,我找到了一个相关的库:adr-tools,但是发现这个库有一些小的缺点: 113 | 114 | - 使用 shell 编写,不易读懂、只支持类 Unix; 115 | - 模板里使用的是英语,不支持中文及其他语言 116 | 117 | 这种时候,一般是在项目上提个 issue,但是发现这个项目使用 shell 扩展起来不容易。因此,自己来写一个类似的软件是一种更好的选择,于是我使用 TypeScript 写了 Node.js 版的 ADR。 118 | 119 | ### 日常工作的结晶 120 | 121 | 在我们的日常、业余的开发工作中,我们往往能实施一些好的技术实践。只是受限于劳动合同的约束,我们不能直接使用这些代码来开源。但是可以独立地重新开始来造一个轮子。在一些有开源文化的公司里,便可以走开源的流程开源出来。 122 | 123 | 如我创建的 Dore,就是一个总结工作结晶的实践。它总结了我在项目上使用 React Native 实现的 WebView 容器的经验。不过,这依赖于我们对代码的抽象能力。即使,我们在项目上用了好的思想,但是并不一定能提取出来。 124 | 125 | ### 我需要一个新的工具 126 | 127 | 上面的两个例子,都需要一定的运行和抽象能力。除了自己乐意造轮子,我们还可以随意地创建一个工具,作为自己制作的工具。 128 | 129 | 如我最近写的 Solla,就是为了解决我写作的时候找图封面图的问题。 130 | 131 | ## 1.下定你的决心 132 | 133 | 我的意思是,你要下定决心去做这一件事情。因为,你很有可能半途而废,比如说我可能在上一步里,习惯了使用英语来写架构决策记录。 134 | 135 | 这是一件很难的事情,特别是当你和我一样,在 GitHub 上挖了两百多个坑位之后,你可能不会想去做这样的事件。 136 | 137 | 权衡这方面的利益不是一件容易的事情,所以不妨直接进入下一步。 138 | 139 | ## 2.完善关于这个软件的想法、需求 140 | 141 | 当我们决定创建这个开源软件的时候,我们就需要细致地想想它到底需要什么功能。它的核心功能又是什么? 142 | 143 | 在拥有对比软件的情况下,核心功能与其它软件都是差不多的。以文本编辑器为例,如果你不能提供插入图片的功能,那么有需求的用户可能就跑了。而为了吸引不同的用户,就需要一些额外的吸引人的特性。同样是文本编辑器,如果你能提供 Markdown 支持,那么你就能吸引这些用户。因此,如果是对比其它框架,那么就要完成相同的核心功能,并提供一些额外的功能。 144 | 145 | 所以,在计划的初期就要思考能提供怎样的特性。 146 | 147 | 再以架构决策记录框架 ADR 为例,那便是: 148 | 149 | - 采用一种通用的语言环境,如 Node.js,以支持主流的操作系统 150 | - 多语言支持,我的意思是它至少可以支持 English 和 中文 151 | - 支持状态日志查询,即我应该可以看到一个决策在生命周期里的变化 152 | - 一个更好的列表展示,我应该可以查看到某条决策,以及对应的最后状态、修改时间等等 153 | - 使用 markdown 展示,以便在 GitHub 上显示 154 | - 拥有一个 ToC 页面,方便用户查看 155 | 156 | 补充一点,上面有几个功能都是在实现的过程中想的,有一两个是在使用的过程中想到的。这些功能能在设计初期的时候考虑到的话,那么后期架构就不会需要需求的添加而带来开发风险。 157 | 158 | ## 3.取一个合适的名字 159 | 160 | 好了,在需求确认得差不多之后,就可以开始动手了。然后,我们就会面临一个很严峻的问题,取一个合适的名字 161 | 162 | ### 取个名字 163 | 164 | 取名,对于多数人(包含程序员)来说,是一件痛苦的事。先设想一下,我们写一个 Web 框架,我们可能需要一个能朗朗上口的名字,以 Vue、React、Angular 来看,最好是单音节、双音节、三音节。 165 | 166 | 并且,它没有被包管理工具注册过的: 167 | 168 | - Ruby 就查看有没有对应的 gem 名可以用。 169 | - Node.js 则是 npm 170 | - Python 便是 pip 171 | 172 | 想想,还是 Java 程序员好,这个依赖的包名是 com.phodal.xxoo,代表了这是 phodal.com 的 xxoo,而不是 hug8217.com 的 xxoo。那么,其对应的域名有可能就是 xxoo.phodal.com。 173 | 174 | 如果我们想到的名字已经被注册过了,那么这就很尴尬了。在这个时候,可以采取自己的命名规则: 175 | 176 | 我之前的命名规则:moImages、moLogs、moForms、moLe,都是以 mo 开头加一个对应用途的单词。 177 | 178 | 比如我最近的系列:dore、mifa、solla、sido,则是以唱名结合来取名字的。 179 | 180 | ### 选择合适的开源协议 181 | 182 | 取完名字之后,就需要选择一个合适的开源协议。不同的许可(协议)会赋予用户不同的权利,如 GPL 协议强制要求开源修改过源码的代码,而宽松一点的 MIT 则不会有这种要求。 183 | 184 | 更细致的协议选型,可以见我之前画的选型图: 185 | 186 | ![Licenses](http://articles.phodal.com/os-builder/licenses.jpg) 187 | 188 | 按我的习惯,都是以 MIT 协议来发布开源软件,CC-NC 协议来发布电子书。 189 | 190 | ### 发布 0.0.1 191 | 192 | 是的,当我们取好名字,选好了协议,我们就要发布 0.0.1 版本了。 193 | 194 | 我的意思是,我们要先抢下这个名字,即占坑。在占坑前,请深思,你真的会写好这个工具吗? 195 | 196 | 比如说,我们使用 Node.js 来写一个 MQTT 的包,那么直接用 MQTT 显然更容易被搜索到:Node.js MQTT。 197 | 198 | 不行的话,只能用 Mosquitto、Mosca 这种名字了。又或者 moMQTT、iMQTT 这种加上前缀的名字,对应于 Java 平台,就可以是 jMQTT。 199 | 200 | ## 4.开始构建 201 | 202 | 如我在《全栈应用开发:精益实践》一书中所说,在我们编写软件之前,我们要先做技术选型与搭建开发环境。这里的技术选型就没有那么复杂,就只需要选择一个合适的语言。 203 | 204 | ### 选择合适的语言 205 | 206 | 选择合适的语言,只针对于某些有各种子集的语言,或者小版本间差异比较大的语言,如 Ruby。 207 | 208 | 不过,还有一个是语言版本的问题。在面向服务器的操作系统,都安装有 Python 环境,但是可能版本不一样。有的是 2.7 的,有的版本可能是 3.4。所以,对于使用 Python 语言的用户来说,还存在选择版本的问题。 209 | 210 | 如果是一个非 JavaScript 的软件,可能就不存在需要选语言了。如果是一个 JavaScript CLI(命令行)工具,也会有这样的问题,到底是使用编写 CoffeScript、TypeScript 还是 ES6 编写都是一个问题。 211 | 212 | 在编写 CLI 工具时,就需要了解是要针对某些用户而开发。如对于 Java 程序员来说,他们的电脑上可能没有 Node.js 环境,如果他们使用的是 macOS 电话,那么应该都是有 Ruby、Python环境。同样的,对于前端程序员来说,如果是 Windows 系统,那么也没有 Java、Python 和 Ruby、Go 环境,直接使用 JavaScript 语言就更容易了。 213 | 214 | ### 搭建构建 || 寻找模板 215 | 216 | 自己搭建一个完整的项目架子,是一个相当浪费时间的事情——**特别是,我们第一次造轮子的时候**。对于一些框架的插件、CLI 工具来说,可能官方直接提供了一个 Hello, World 模板。但是,有一些时候往往没有这么简单。要么找一个差不多的模板,再有针对于的修改;要么复制现有的架子,以我们的名称替换其中的名字。 217 | 218 | 当我造轮子的时候,我习惯在网上搜索相应的模板,比如说:``javascript lib boilerplate``、``typescript lib boilerplate`` 或者 ``typescript cli starter``。在没有合适的情况下,就是找一些使用 TypeScript 写的库,在那之上进行修改。 219 | 220 | ## 5.编写原型:核心功能 221 | 222 | 核心功能,意味着,不做过多的错误处理——假设用户是按我们的预期行为进行的。依微软公司(Steve Ballmer)的经验,20% 的代码是在核心的逻辑上,而有超过 80% 的代码是在处理错误逻辑上。 223 | 224 | 所以,在那之前,请先完成核心的功能。它可以让你更快地发布早期的预览版,以早点接受市场的反馈。 225 | 226 | 构建开源与构建产品是类似的,能越早推出早期版本,就越有机会赢得市场;能不断地继续接受反馈并进行改进,就越能吸引忠实用户;能做好一些市场工作,也就越吸引更多用户。 227 | 228 | ## 6.发布 229 | 230 | 如果这是我们第一次开发开源软件,那么我们应该先发布几个版本,来测试预期的过程是不是正常的。这个时候,就应该发布另外一个早期版本 0.1.0。从之前占坑的 0.0.1 到 0.1.0 之间,我们会做一些简单的开发,比如,验证一些基本的核心功能是不是好的、编写一个容易读懂的 README和一句话文案。 231 | 232 | ### 0.1.0 版本 233 | 234 | 0.1.0 意味着,**我们拥有了一些基本的功能,并且框架本身是可以工作的。** 235 | 236 | 比如说,你要做一个 Ruby 的 Gem 包,那么你的第一个版本是让包可以被调用。哪怕只是一个 Hello, World,它都能验证我们的模式是可以工作的。 237 | 238 | 如果你要实现的是一个 CLI 工具,那么你需要的就是安装之后,可以直接使用。比如说,最近我在写 Solla 的时候,在引入了 async 之后,就报错了: 239 | 240 | ``` 241 | ERROR in [default] 242 | Cannot find module 'tslib'. 243 | ``` 244 | 245 | 这只是其中遇到的一个问题,如果问题过多的话,那么到时候调试起来就有些麻烦。 246 | 247 | ### 简化安装、使用 248 | 249 | 在设计发布流程的时候,我们也要注意框架本身的易用性。如我们开发一个库,那么用户只添加一个依赖就可以使用: 250 | 251 | ``` 252 | dependencies { 253 | compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.+' 254 | } 255 | ``` 256 | 257 | 或者: 258 | 259 | ``` 260 | npm install -g adr 261 | ``` 262 | 263 | 早先,为了支持不同的语言,还需要 ``adr init``,后来把这个步骤变成了可选,即配置了一些默认的配置。于是,用户可以直接使用 ``adr new`` 创建。 264 | 265 | 麻烦一些的情况下,可能要多一两步: 266 | 267 | ``` 268 | yarn add react-native-check-app-install 269 | react-native link 270 | ``` 271 | 272 | 如果整个安装的过程很复杂,那么使用者在使用的过程中就会放弃。 273 | 274 | ## 7.迭代 275 | 276 | 好了,我们的早期几个版本都可以工作了,我们正常地走向迭代开发的过程: 277 | 278 | ![Agile 迭代](http://articles.phodal.com/os-builder/agile-iteration.png) 279 | 280 | ### 更新文档 281 | 282 | 首先在 README 上,写好项目的简介,并根据项目 API 的变化,不断地更新 README。如果项目的使用者需要依赖而文档,那么请及时更新文档。 283 | 284 | 不过,指望程序员及时更新文档可能是一种不可靠的事情。这时候,应该使用 jsDOC、tsDoc、JavaDoc 这一类的工具,以从代码中生成对应的文档。 285 | 286 | ### 编写测试 287 | 288 | 测试,是一个项目的质量保证。在可能地时候,应该花时间去写测试,它可以让项目看上去很可靠。然后,在醒目地地方告诉用户,“你看这个软件的测试覆盖率有 92%,质量有相当高的保证。” 289 | 290 | ![测试覆盖率](http://articles.phodal.com/os-builder/codecov.png) 291 | 292 | 特别是用户使用的是你的库,它能表明这个库相当地可靠。 293 | 294 | ### 1.0.0 版本 295 | 296 | 在完成了我们计划的主要功能之后,就可以发布我们的 1.0.0 版本。与版本号 0.5.5 相比来说,版本号 1.0.0 会给人更可靠的感觉。如果你喜欢的话,可以像 Oracle 一样直接发布 2.0 的版本,它与 1.0.0 版本相比,看上去更加可靠。 297 | 298 | ## 8.自动化 299 | 300 | 接下来,就是对上面做的内容进行一系列的自动化。如自动化文档、自动化 CHANGELOG、持续集成 等等。 301 | 302 | 303 | ### 自动化 CHANGELOG 304 | 305 | 可以按 GitHub 上的 [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog) 来编写自己的提交信息: 306 | 307 | ``` 308 | [optional scope]: 309 | 310 | [optional body] 311 | 312 | [optional footer] 313 | ``` 314 | 315 | 就可以优雅地生成 CHANGELOG 了: 316 | 317 | ![提交信息示例](commit-message-example.png) 318 | 319 | ### 自动化 RELEASE 320 | 321 | 当我们添加了一个新功能,或者修复一个 BUG 的时候。考虑到用户的需要,我们就会发布这个新版本,一般来说,我们可能要这么做: 322 | 323 | - 使用 ``git tag`` 提交一个新的版本 324 | - 修改 CHANGELOG 来增加更新的功能 325 | - 更新文档来通知用户 326 | - 等等 327 | 328 | 这些都应该可以自动化,如我使用的 TypeScript 模板,就可以执行 release 来发布下一个版本。 329 | 330 | ``` 331 | "release": "yarn reset && yarn test && yarn docs:publish && yarn changelog", 332 | ``` 333 | 334 | ## 9.接受反馈 335 | 336 | 开源是一种社区行为,当用户看到我们的源码写得不好、出现 BUG、新增需求等等。就会在 GitHub 提交一些反馈,又或者是 PULL Request。 337 | 338 | 作为我们的用户,我们应该对他们做出即时地**响应**。但是不是一味着同意用户的需求,应该有一个清晰地 Roadmap,根据自己的时间来安排是否要开发。 339 | 340 | 如果用户提了一个可怕的需求,那么 341 | 342 | - 要做,这个需求在考虑中 343 | - 做,这个需求在待办列表中 344 | - 不做,这个需求还需要验证 345 | - blablabla 346 | 347 | 不管结果如何,在看到反馈的时候,尽可能早地去回复用户。 348 | 349 | ## 10.marketing 350 | 351 | 开源需要一些营销的技巧,这些技巧可以帮你吸引关注。举个简单的例子,司徒正美的 avalon 框架出身得很早,也 MV* 方面也做得很不错,但是在 marketing 上就……。以至于国内的很多前端,都不了解这个框架,要不今天在国内可能就是 AVRR 四大框架了。 352 | 353 | 在那篇《如何运营一个开源项目并取得较大影响力?》中,我详细介绍了如何进行开源推广。 354 | 355 | - 编写一个好的 README 356 | - 写好项目的一句话文案 357 | - README 里写明解决了什么问题 358 | - README 里写明下项目的特性 359 | 360 | 同时,在国内的一些技术资讯类网站上,也可以发布一些简单的文章来介绍框架。这些平台有: 361 | 362 | - 极客头条 363 | - 掘金 364 | - 开发者头条 365 | - v2ex 366 | - 知乎 367 | - 不成器的微博 368 | - 等等 369 | 370 | 一般来说,如果已经有了对比的框架,会写一个对比的报告。考虑到,每个框架都会各自有优势,只是优势的大和小,决定了用户量。如框架 A 容易上手,框架 B 设计思想好,那么显然框架 A 的优势更大。这样的对比可以放在 README 中,但是不提倡放在文档中。 371 | -------------------------------------------------------------------------------- /src/assets/docs/pattern.md: -------------------------------------------------------------------------------- 1 | # 开源模式 2 | 3 | # 企业开源模式 4 | 5 | 开源的四个时代: 6 | 7 | - 弱版权时代。哪来的闭源。Unix 在 AT&T 诞生,它以低廉甚至免费的许可分发源码。 8 | - 自由软件时代。开源就是自由。理查德·斯托曼(RMS)发起了 GNU 计划,发起了『自由软件运动』。 9 | - 开源运动时代。开源为了对抗。Mozilla 开创的开源运动重塑了整个技术界。 10 | - 云原生开源时代。开源可以赚更多的钱。云厂商为了扩大市场份额,开始运用开源战略。 11 | 12 | 对于企业来说,企业使用开源可以: 13 | 14 | - 提高软件质量 15 | - 降低总拥有成本 16 | - 改善安全防护 17 | - 为云技术云原生设计 18 | - 能安全利用开源技术 19 | - 获得最前沿创新技术 20 | 21 | ## 开源盈利模式 22 | 23 | ### 直接盈利 24 | 25 | - 付费版本。如开源的 Nginx,以及更多功能的 Nginx Plus。双重许可、商业使用收费、部分组件收费等。 26 | - 咨询/服务。如 JBoss、Spring 通过提供技术支持、培训、二次开发支持等技术服务而获得收入。 27 | - 托管模式。通过免费开源的 MongoDB 吸引用户。推出 MongoDB 数据库托管服务等,增加创收渠道。 28 | 29 | ### 间接获利 30 | 31 | - 建立内部协作。增强组织内技术氛围,共享团队间的技术资源。 32 | - 打造雇主品牌。在技术社区建立品牌形象,和杰出贡献者保持良好的关系,有助公司技术招聘。 33 | - 构建合作生态。与上下游供应链建立合作,建立在该领域的权威定位。 34 | - 参与标准制定。借助于社区力量,一起促进行业建立标准。 35 | - 推广技术理念。将新的技术理念融入开源项目中,促进整个行业往前发展。 36 | 37 | ## 四种模式 38 | 39 | | | 使用开源 | 参与开源 | 走向开源 | 构建生态 | 40 | | -------- | ---------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | 41 | | 准入条件 | 协议审核 | 采用“上游优先”策略 | 企业开源指南
开源政策指导
法务支持
良好的工程实践 | 社区的设计与运营
技术支持服务
核心运行能力
扩展与混合能力 | 42 | | 收益 | 提升开发效率
降低开源使用门槛 | 了解开源世界的游戏规则
熟悉开源社区文化
引入先进的技术实践 | 拥抱开放式创新
建立在开源社区的声誉
对人才更具吸引力
突显自身的影响力 | 建立行业标准
借助开源进行市场扩张 | 43 | | 盈利方式 | (N/A) | (N/A) | 付费版本、托管模式、咨询/服务 | 技术支持服务、云服务 | 44 | 45 | ## 大公司采用策略示例 46 | 47 | | | 参与开源 | 走向开源 | 构建生态 | 转折事件 | 48 | | -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------- | 49 | | 微软 | GitHub 上开源参与人数最多的企业 | 开源 .NET 平台、VS Code、TypeScript 等 | 收购运营 GitHub、DevOps 开源项目管理与支持平台、云能力 | 2014 年新任 CEO 提出 “微软爱 Linux” 全面支持开源 | 50 | | 谷歌 | GitHub 上开源参与人数前 5 位的企业 | 开源 Android、Chromium、GO、TensorFlow、Angular 等 | 投资 GitLab、云能力 | 2007 年开源 Android 源代码,加速了 Android 的普及 | 51 | | 苹果 | GitHub 上开源参与人数第 25 位的企业 | 开源项目相比其他大厂不多,比较知名的有 Webkit、LLVM | | 1996 年 OS X 操作系统是基于开源的 Darwin BSD Unix | 52 | | IBM | GitHub 上开源参与人数前 5 位的企业 | 开源 QisKit、Kabanaro、Eclipse、POWER 指令集、HyperLedger fabric 等 | 收购 RedHat 提供开源技术咨询服务、建立开源基金会 Linux, Eclipse, Apache, CNCF 等 | | 90 年代末,为支持 Linux 缴纳 10 亿美元专利费,并成立基金会 | 53 | | 阿里巴巴 | GitHub 上开源参与人数第 12 位企业,中国地区第 1 位。在内部有开源布道师、开源办公室等组织 | 在芯片、物联网、云计算、硬件、微服务框架、数据库、AI 等方面有众多开源项目 | | 2010 年发布第一个开源项目 Dubbo | 54 | 55 | # 企业开源五大支柱 56 | 57 | - 组织支持。开源是一种开放创新型的组织模式,需要自上而下的支持。 58 | - 工程能力。开源项目的工程能力是对外展示工程能力的渠道。 59 | - 开源文化。开源世界有独特的文化基因:开放、协作、透明等。 60 | - 产品思维。每个开源项目都是一个产品,都需要像产品一样对待。 61 | - 社区与生态。活跃社区,构建生态,是一个开源项目强大的必由之路。 62 | 63 | ```process-card 64 | | 组织支持 | 工程能力 | 开源文化 | 产品思维 | 社区与生态 | 65 | |-|-|-|-|-| 66 | | 发布开源指南 | 充分测试 | 建立内部开源文化 | 和产品策略一致 | 对生态进行战略投资 | 67 | | 设立开源协议指南 | 整洁代码 | 寻找开源倡导者 | 设立项目里程碑 | 培养关键意见领袖(KOL) | 68 | | 制定开源投资战略 | DevOps 流程一体化 | 开放式地管理 | 制定营销策略 | 招募技术布道者 | 69 | | 建立开源项目部门 | 文档体验设计 | 鼓励参与、协作 | 项目管理 | 构建开发者生态 | 70 | | 建设度量指标 | 采用社区标准的技术实践 | 公开透明开源软件流程 | 管理请求意见稿(RFC) | 反馈驱动开发 | 71 | 72 | config: {"colors": [{"bg":"#e55852","font":"#b71a09"},{"bg":"#e98832","font":"#c85113"},{"bg":"#f0d668","font":"#b88d0f"},{"bg":"#a4c9cf","font":"#598893"}]} 73 | ``` 74 | 75 | # 企业开源能力全景图 76 | 77 | | 组织支持 | 工程能力 | 开源文化 | 产品思维 | 社区与生态 | 78 | 79 | ```process-step 80 | - 使用开源 81 | - [1] 制定开源使用指南 82 | - [1] 开源合规审查 83 | - 参与开源 84 | - [2] 引入开源技术实践 85 | - [1] 制定开源参与指南 86 | - [1] 开源治理策略 87 | - [3] 建议团队回馈开源 88 | - [5] 参与社区建设 89 | - 走向开源 90 | - [3] 内部项目开源共享 91 | - [5] 社区推广策略 92 | - [2] 敏捷测试实践 93 | - [4] 和产品策略一致 94 | - [1] 制定贡献指南 95 | - [1] 设计成熟度模型 96 | - [1] 开源项目管理部门 97 | - [4] 制定开源项目营销策略 98 | - 构建生态 99 | - [5] 技术布道 100 | - [5] 创建开发者社区 101 | - [2] 开发者体验 102 | - [3] 去中心化的社区管理 103 | - [3] 透明决策和讨论 104 | - [5] 社区参与多样性 105 | - [1] 生态战略投资 106 | 107 | config: {"heads": ["组织支持", "工程能力", "开源文化", "产品思维", "社区与生态"]} 108 | ``` 109 | 110 | # 使用开源 111 | 112 | ## 开放技术构架 113 | 114 | 优点: 115 | 116 | - 更容易招到适合的人才 117 | - 开放技术降低研发成本 118 | - 社区己有拥有丰富的资料 119 | - 技术已被验证过 120 | - 易于寻找迁移方案 121 | - 关注于高优先级的部分 122 | - 无需担心『供应商-锁定』 123 | 124 | ### 运营挑战及风险 125 | 126 | ## 阅读开源软件源码 127 | 128 | 最近的一段时间里,我在研究 Android 配套工具和 Android Studio 相关的实现,以及它们如何配合完成一个 APK 的构建。因为整个系统各个模块之间的关系过于复杂,除此,不同模块之间也包含了大量的代码 —— 无论是从行数上,还是从函数长度上来说。(PS:顺便吐槽一句, `@google.com` 的一部分人写的代码也是又臭又长) 129 | 130 | 从总体的思路上来说,在进入代码阅读之前,我们需要: 131 | 132 | 1. 理解代码背后的业务流程 133 | 2. 理解架构设计的思想 134 | 135 | 从而我们才能理解主流程(脉络)。针对于此,我们会发现一些不同的模式: 136 | 137 | 1. 借鉴他人。从他人的学习笔记中,理解整体的思路和过程。如 Android APK 的构建,Android 资源如何优化,从中理清代码阅读的思路。 138 | 2. 源码学习。 139 | 3. 借助测试调试。 140 | 4. fork 主流程。 141 | 142 | 它们并不是互相独立的,往往是结合一起使用的。 143 | 144 | ### 借鉴他人 145 | 146 | 这种模式,可以实现**快速地学习**。它存在的一些明显的缺点是: 147 | 148 | - 学到的东西是二手加工过的。 149 | - 部分的代码可能与真实的情形脱节。 150 | 151 | 所以,它适用于你想快速了解某一部分的功能,从而了解全貌,随后我们就可以深入某一部分进行了解。 152 | 153 | 在这种模式之下,我推荐:通过**购买、阅读书籍**的方式来学习。如果能买到书便是一件幸运的事,因为它已经经过了系统性的加工。唯一的问题可能是上面的代码有些老旧。但是,它更加的系统化、完整,方便我们理解,并减少搜索资料的成本。 154 | 155 | **为什么不是网上找资料?**: 156 | 157 | 1. 找资料需要投入时间成本 158 | 2. 资料不一定详细 159 | 160 | 如果你能直接找到详细的资料,毕竟花费的时间足够短的话,那么也是没问题的。 161 | 162 | ### 源码学习 163 | 164 | 源码学习是一个需要花费大量时间和精力的事情,除非万不得已,否则我也不想用这种方式。因为,我们会缺少大量的上下文,这些上下文可能导致我们理解出现一些误差。 165 | 166 | 前期准备: 167 | 168 | 1. 合适的工具。最好是~~收费的(🐶)~~能用上的。 169 | 2. 合适的存储空间。像 Android 这样的系统,clone 下来就要 120G,编译的话,估计得达到 200G 吧;而像 Android Studio 的源码,clone 下来也要 60G。 170 | 3. 寻找阅读的模式。 171 | 4. 尝试去构建应用。它不一定可行,但是如果可以的话,会节省你大量的时间。 172 | 173 | 源码学习是一个非常重的学习模式。我们要花费大量的时间: 174 | 175 | 1. 在代码间跳转 176 | 2. 梳理业务逻辑 177 | 178 | 所以,还有一些不错的犯懒的姿势: 179 | 180 | 1. 通过书籍来加强。我一直觉得对于学习来说,阅读书籍是最理想的方式。因为寻找资料需要成本,而多数的书都会起到一个索引的目的。 181 | 2. 寻找相似的轮子。一个有意思的技术,必然有很多公司、很多人都研究过。他们都会尝试去创造类似的轮子。唯一的问题是,我们要学习到什么程度,如果只是理解的话,那么看看别人重复的轮子也是可以的;如果是为了深入的话,那么还得回过头去看看源码 182 | 183 | ### 借助测试调试 184 | 185 | 对于调试来说,我们还会面临的一个挑战是:诸如我这样的入门级 MBP 配置,对于大型系统来说编译根本不够用。应对这种问题的一个比较良好的姿势是:**通过 IDE 调试测试来完成对部分代码的调试**。(PS:这种方式也适用于业务代码的开发) 186 | 187 | 如果我们可以在应用的入口中创建某一模块对应的测试,那么我们就可以快速调试整个应用了。 188 | 189 | ### fork 主流程 190 | 191 | 对于我来说,我觉得只阅读源码是一种只为了解决一时问题的方式。同时,像我这样的凡人,对于某些知识和内容,只要不使用,我可能隔个十天半个月,我就忘光了(虽然我一直觉得这是一件好事)。 192 | 193 | 边阅读代码,边 fork 项目,我们还会有一些挑战: 194 | 195 | 1. 语言的熟练度和模式。对于熟悉的语言来说,比如日常编写业务代码的时候,我们并不需要理解于诸如类加载器、元编程、字节码这一类的复杂模式。 196 | 2. 新的框架、工具或语言的学习成本。比如,我在过程中就遇到需要理解和学习 Gradle 插件的一些构建机制。 197 | 3. 代码中大量的、无用的异常处理的代码。 198 | 4. 别人的代码都是 💩。(PS:一个月后自己的代码也是屎) 199 | 200 | 所以,还有一些模式: 201 | 202 | 0. 划分模块边界。寻找架构图,通过架构图来划分模块。 203 | 1. 切片化运行。一个模块,一个模块来理解整个系统。如 IDEA 插件的编写、IDEA 插件与 Gradle 如何交互,Gradle 插件的原理与编写,Gradle 如何调用其它命令行工具,命令行工具的原理与编写。 204 | 2. 通过测试运行。如针对于 ApkAnalyser 这样的工具,我们可以通过单元测试而非构建一个 CLI 的方式来运行。 205 | 3. 选择另外一门语言。因为别人用 Java、Groovy、Kotlin 编写的应用,如果你用 Rust、Go 再写一遍的话,那么你就能一次学到两个东西了:一个是新的编程语言,一个是这个开源项目的代码。 206 | 207 | #### README 输出 208 | 209 | 为了方便我们查阅和其他/她人使用,我往往会把相关的内容记录到项目的 README 上。 210 | 211 | - 相关的文档资料 212 | - 相似的开源项目 213 | - 过程中的内容产出 214 | - 代码简要说明 215 | - …… 216 | 217 | 这样一来,其他/她人在学习的过程中还能 GET 到相似的思路。 218 | 219 | ### 结论 220 | 221 | 最后,简单做一些成本对比: 222 | 223 | | 模式 | 成本 | 性价比 | 主要场景 | 224 | |---------|-----|--------|------------| 225 | | 借鉴他人 | 低 | 高 | 学习 | 226 | | 阅读源码学习 | 高 | 低 | 理解思想| 227 | | fork 主流程 | 高 | 低 | 理解、模仿 | 228 | | 借助测试调试 | 较高 | 中 | 理解、模仿 | 229 | 230 | 一些结合模式: 231 | 232 | 1. 阅读二手资料,根据二手资料理解主脉络 233 | 2. 编写主流程调用链,理解架构设计理想 234 | 3. 借助开源软件的测试调试,理解参数及流程 235 | 4. …… 236 | 237 | 你呢,你有什么好用的模式? 238 | 239 | # 开发者体验 240 | 241 | 关注开发者体验之前,应该确保核心功能:完善 + 稳定。即你需要提供可用、稳定的特性,再去提升总体的用户体验。除非,对于你的系统来说,你在一开始就不缺用户。 242 | 243 | ## 开发者体验六要素 244 | 245 | | 错误呈现 | 文档体验 | 易用性 | 交互式 | 触点 | 支持 | 246 | |-----|-----|-----|-----|-----|-----| 247 | | 错误描述 | 开发者门户 | 一键式安装 | 低配置/零配置 | 文章 | 问题反馈渠道 | 248 | | 报错即文档 | 发布日志 | 自动化版本迁移工具 | 声明式使用 | 演讲/分享 | 问题响应时间 | 249 | | 报错即修改建议 | 代码生成文档 | 自助式搭建 | 可交互文档 | Hackathon | 开发者即服务 | 250 | | | 版本迁移指南 | | 沙盒及产品环境 | | 开发者社区 | 251 | 252 | ## 开发者门户成熟度模型 253 | 254 | 在编写这篇文章的过程中,刚好看到了一篇对于门户的度量模型,《[How Mature Are You? A Developer Experience API Maturity Model](http://jennywanger.com/speaking/dx-maturity-model/)》简单地翻译了一些**国内适用**的部分(详细见原文): 255 | 256 | | Level 1 | Level 2 | Level 3 | Level 4 | 257 | |-------|-------|-------|-------| 258 | | 封闭的系统 | 门户是自服务的,但是不连贯的 | 完全自服务 | 可个性化的统一门户 | 259 | | 文档缺乏成功调用 API 的信息 | 1 天内可以调用 API | 10 分钟内可以调用 API | 分钟级 API 调用 | 260 | | API 和功能没有正确对应 | 快速开始指南、修改日志和教程 | 提供沙盒和生产环境 | 认证流程 | 261 | | 响应问题需要一周的时间 | 交互式文档 | 代码示例和库 | | 262 | | | 问题在 2 ~ 3 天内被回答 | 24 小时回答问题 | | 263 | 264 | -------------------------------------------------------------------------------- /src/styles/_markdown.scss: -------------------------------------------------------------------------------- 1 | @import './behavior'; 2 | @import './color'; 3 | 4 | $markdown-list-color: #262626; 5 | $markdown-primary: #384452; 6 | $markdown-grey: #d1d8df; 7 | $markdown-green: #1abc9c; 8 | $markdown-dark-grey: #5e6772; 9 | $markdown-blue: #23B7F3; 10 | $markdown-code-grey: #eef1f5; 11 | $markdown-color-red: #f53d3d; 12 | $markdown-color-yellow: #ffff3a; 13 | $markdown-light-grey: #fdfdfd; 14 | $markdown-mid-grey: #666; 15 | $line-height: 1.5em; 16 | 17 | .markdown { 18 | .row { 19 | display: flex; 20 | flex-direction: column; 21 | padding: 0; 22 | width: 100% 23 | } 24 | 25 | .row.row-no-padding { 26 | padding: 0 27 | } 28 | 29 | .row.row-no-padding > .column { 30 | padding: 0 31 | } 32 | 33 | .row.row-wrap { 34 | flex-wrap: wrap 35 | } 36 | 37 | .row.row-top { 38 | align-items: flex-start 39 | } 40 | 41 | .row.row-bottom { 42 | align-items: flex-end 43 | } 44 | 45 | .row.row-center { 46 | align-items: center 47 | } 48 | 49 | .row.row-stretch { 50 | align-items: stretch 51 | } 52 | 53 | .row.row-baseline { 54 | align-items: baseline 55 | } 56 | 57 | .row .column { 58 | display: block; 59 | flex: 1 1 auto; 60 | margin-left: 0; 61 | max-width: 100%; 62 | width: 100% 63 | } 64 | 65 | .row .column.column-offset-10 { 66 | margin-left: 10% 67 | } 68 | 69 | .row .column.column-offset-20 { 70 | margin-left: 20% 71 | } 72 | 73 | .row .column.column-offset-25 { 74 | margin-left: 25% 75 | } 76 | 77 | .row .column.column-offset-33, .row .column.column-offset-34 { 78 | margin-left: 33.3333% 79 | } 80 | 81 | .row .column.column-offset-50 { 82 | margin-left: 50% 83 | } 84 | 85 | .row .column.column-offset-66, .row .column.column-offset-67 { 86 | margin-left: 66.6666% 87 | } 88 | 89 | .row .column.column-offset-75 { 90 | margin-left: 75% 91 | } 92 | 93 | .row .column.column-offset-80 { 94 | margin-left: 80% 95 | } 96 | 97 | .row .column.column-offset-90 { 98 | margin-left: 90% 99 | } 100 | 101 | .row .column.column-10 { 102 | flex: 0 0 10%; 103 | max-width: 10% 104 | } 105 | 106 | .row .column.column-20 { 107 | flex: 0 0 20%; 108 | max-width: 20% 109 | } 110 | 111 | .row .column.column-25 { 112 | flex: 0 0 25%; 113 | max-width: 25% 114 | } 115 | 116 | .row .column.column-33, .row .column.column-34 { 117 | flex: 0 0 33.3333%; 118 | max-width: 33.3333% 119 | } 120 | 121 | .row .column.column-40 { 122 | flex: 0 0 40%; 123 | max-width: 40% 124 | } 125 | 126 | .row .column.column-50 { 127 | flex: 0 0 50%; 128 | max-width: 50% 129 | } 130 | 131 | .row .column.column-60 { 132 | flex: 0 0 60%; 133 | max-width: 60% 134 | } 135 | 136 | .row .column.column-66, .row .column.column-67 { 137 | flex: 0 0 66.6666%; 138 | max-width: 66.6666% 139 | } 140 | 141 | .row .column.column-75 { 142 | flex: 0 0 75%; 143 | max-width: 75% 144 | } 145 | 146 | .row .column.column-80 { 147 | flex: 0 0 80%; 148 | max-width: 80% 149 | } 150 | 151 | .row .column.column-90 { 152 | flex: 0 0 90%; 153 | max-width: 90% 154 | } 155 | 156 | .row .column .column-top { 157 | align-self: flex-start 158 | } 159 | 160 | .row .column .column-bottom { 161 | align-self: flex-end 162 | } 163 | 164 | .row .column .column-center { 165 | -ms-grid-row-align: center; 166 | align-self: center 167 | } 168 | 169 | @media (min-width: 40rem) { 170 | .row { 171 | flex-direction: row; 172 | margin-left: -1.0rem; 173 | width: calc(100% + 2.0rem) 174 | } 175 | .row .column { 176 | margin-bottom: inherit; 177 | padding: 0 1.0rem 178 | } 179 | } 180 | 181 | blockquote { 182 | border-left: 0.3rem solid $markdown-green; 183 | margin-left: 0; 184 | margin-right: 0; 185 | padding: 1rem 1.5rem; 186 | font-size: 1rem; 187 | } 188 | 189 | blockquote *:last-child { 190 | margin-bottom: 0 191 | } 192 | 193 | q:before { 194 | color: $markdown-green; 195 | content: open-quote; 196 | font-size: 4em; 197 | line-height: 0.1em; 198 | margin-right: 0.25em; 199 | vertical-align: -0.4em 200 | } 201 | 202 | q:after { 203 | content: none 204 | } 205 | 206 | .button, button, input[type='button'], input[type='reset'], input[type='submit'] { 207 | background-color: $markdown-primary; 208 | border: 0.1rem solid $markdown-primary; 209 | border-radius: .4rem; 210 | color: #fff; 211 | cursor: pointer; 212 | display: inline-block; 213 | font-size: 1.1rem; 214 | font-weight: 700; 215 | height: 3.8rem; 216 | letter-spacing: .1rem; 217 | line-height: 3.8rem; 218 | padding: 0 3.0rem; 219 | text-align: center; 220 | text-decoration: none; 221 | text-transform: uppercase; 222 | white-space: nowrap 223 | } 224 | 225 | .button:focus, .button:hover, button:focus, button:hover, input[type='button']:focus, input[type='button']:hover, input[type='reset']:focus, input[type='reset']:hover, input[type='submit']:focus, input[type='submit']:hover { 226 | background-color: $markdown-green; 227 | border-color: $markdown-green; 228 | color: #fff; 229 | outline: 0 230 | } 231 | 232 | .button[disabled], button[disabled], input[type='button'][disabled], input[type='reset'][disabled], input[type='submit'][disabled] { 233 | cursor: default; 234 | opacity: .5 235 | } 236 | 237 | .button[disabled]:focus, .button[disabled]:hover, button[disabled]:focus, button[disabled]:hover, input[type='button'][disabled]:focus, input[type='button'][disabled]:hover, input[type='reset'][disabled]:focus, input[type='reset'][disabled]:hover, input[type='submit'][disabled]:focus, input[type='submit'][disabled]:hover { 238 | background-color: $markdown-primary; 239 | border-color: $markdown-primary 240 | } 241 | 242 | .button.button-outline, button.button-outline, input[type='button'].button-outline, input[type='reset'].button-outline, input[type='submit'].button-outline { 243 | background-color: transparent; 244 | color: $markdown-primary 245 | } 246 | 247 | .button.button-outline:focus, .button.button-outline:hover, button.button-outline:focus, button.button-outline:hover, input[type='button'].button-outline:focus, input[type='button'].button-outline:hover, input[type='reset'].button-outline:focus, input[type='reset'].button-outline:hover, input[type='submit'].button-outline:focus, input[type='submit'].button-outline:hover { 248 | background-color: transparent; 249 | border-color: $markdown-green; 250 | color: $markdown-green 251 | } 252 | 253 | .button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover, button.button-outline[disabled]:focus, button.button-outline[disabled]:hover, input[type='button'].button-outline[disabled]:focus, input[type='button'].button-outline[disabled]:hover, input[type='reset'].button-outline[disabled]:focus, input[type='reset'].button-outline[disabled]:hover, input[type='submit'].button-outline[disabled]:focus, input[type='submit'].button-outline[disabled]:hover { 254 | border-color: inherit; 255 | color: $markdown-primary 256 | } 257 | 258 | .button.button-clear, button.button-clear, input[type='button'].button-clear, input[type='reset'].button-clear, input[type='submit'].button-clear { 259 | background-color: transparent; 260 | border-color: transparent; 261 | color: $markdown-primary 262 | } 263 | 264 | .button.button-clear:focus, .button.button-clear:hover, button.button-clear:focus, button.button-clear:hover, input[type='button'].button-clear:focus, input[type='button'].button-clear:hover, input[type='reset'].button-clear:focus, input[type='reset'].button-clear:hover, input[type='submit'].button-clear:focus, input[type='submit'].button-clear:hover { 265 | background-color: transparent; 266 | border-color: transparent; 267 | color: $markdown-green 268 | } 269 | 270 | .button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover, button.button-clear[disabled]:focus, button.button-clear[disabled]:hover, input[type='button'].button-clear[disabled]:focus, input[type='button'].button-clear[disabled]:hover, input[type='reset'].button-clear[disabled]:focus, input[type='reset'].button-clear[disabled]:hover, input[type='submit'].button-clear[disabled]:focus, input[type='submit'].button-clear[disabled]:hover { 271 | color: $markdown-primary 272 | } 273 | 274 | code { 275 | background: $markdown-code-grey; 276 | border-radius: .4rem; 277 | font-size: 86%; 278 | margin: 0 .2rem; 279 | padding: .2rem .5rem; 280 | white-space: nowrap 281 | } 282 | 283 | pre { 284 | background: $markdown-code-grey; 285 | border-left: 0.3rem solid $markdown-green; 286 | overflow-y: hidden 287 | } 288 | 289 | pre > code { 290 | border-radius: 0; 291 | display: block; 292 | padding: 1rem 1.5rem; 293 | white-space: pre 294 | } 295 | 296 | hr { 297 | border: 0; 298 | border-top: 0.1rem solid $markdown-primary; 299 | margin: 3.0rem 0 300 | } 301 | 302 | input[type='email'], input[type='number'], input[type='password'], input[type='search'], input[type='tel'], input[type='text'], input[type='url'], input:not([type]), textarea, select { 303 | -webkit-appearance: none; 304 | -moz-appearance: none; 305 | appearance: none; 306 | background-color: transparent; 307 | border: 0.1rem solid $markdown-grey; 308 | border-radius: .4rem; 309 | box-shadow: none; 310 | box-sizing: inherit; 311 | height: 3.8rem; 312 | padding: .6rem 1.0rem; 313 | width: 100% 314 | } 315 | 316 | input[type='email']:focus, input[type='number']:focus, input[type='password']:focus, input[type='search']:focus, input[type='tel']:focus, input[type='text']:focus, input[type='url']:focus, input:not([type]):focus, textarea:focus, select:focus { 317 | border-color: $markdown-green; 318 | outline: 0 319 | } 320 | 321 | select { 322 | background: url('data:image/svg+xml;utf8,') center right no-repeat; 323 | padding-right: 3.0rem 324 | } 325 | 326 | select:focus { 327 | background-image: url('data:image/svg+xml;utf8,') 328 | } 329 | 330 | textarea { 331 | min-height: 6.5rem 332 | } 333 | 334 | label, legend { 335 | display: block; 336 | font-size: 1.6rem; 337 | font-weight: 700; 338 | margin-bottom: .5rem 339 | } 340 | 341 | fieldset { 342 | border-width: 0; 343 | padding: 0 344 | } 345 | 346 | input[type='checkbox'], input[type='radio'] { 347 | display: inline 348 | } 349 | 350 | .label-inline { 351 | display: inline-block; 352 | font-weight: normal; 353 | margin-left: .5rem 354 | } 355 | 356 | img { 357 | max-width: 100%; 358 | display: block; 359 | margin: 1em auto; 360 | } 361 | 362 | figcaption { 363 | text-align: center; 364 | padding: 8px; 365 | color: $markdown-mid-grey; 366 | } 367 | 368 | a { 369 | color: $markdown-green; 370 | text-decoration: none 371 | } 372 | 373 | a:focus, a:hover { 374 | color: $markdown-blue 375 | } 376 | 377 | dl, ol, ul { 378 | list-style: none; 379 | margin-top: 0; 380 | padding-left: 0 381 | } 382 | 383 | dl dl, dl ol, dl ul, ol dl, ol ol, ol ul, ul dl, ul ol, ul ul { 384 | font-size: 90%; 385 | margin: 1.0rem 0 1.5rem 1.0rem 386 | } 387 | 388 | ol { 389 | list-style: decimal inside 390 | } 391 | 392 | ul { 393 | list-style: disc 394 | } 395 | 396 | li { 397 | font-size: 1rem; 398 | color: $markdown-primary 399 | } 400 | 401 | .button, button, dd, dt, li { 402 | margin-bottom: 1.0rem 403 | } 404 | 405 | fieldset, input, select, textarea { 406 | margin-bottom: 1.5rem 407 | } 408 | 409 | blockquote, dl, figure, form, ol, p, pre, table, ul { 410 | margin-bottom: 1.5rem 411 | } 412 | 413 | table { 414 | border-spacing: 0; 415 | width: 100% 416 | } 417 | 418 | table thead th, thead th { 419 | border-top: 0.1rem solid $markdown-grey; 420 | background: $markdown-light-grey 421 | } 422 | 423 | table td { 424 | min-width: 7rem; 425 | } 426 | 427 | tbody tr:nth-child(even) { 428 | background: $markdown-light-grey 429 | } 430 | 431 | tbody tr:nth-child(odd) { 432 | background: $markdown-code-grey 433 | } 434 | 435 | td, th { 436 | border-bottom: 0.1rem solid $markdown-grey; 437 | padding: 1.2rem 1.5rem; 438 | text-align: left; 439 | } 440 | 441 | u { 442 | text-decoration: none; 443 | border-bottom: 1px dashed 444 | } 445 | 446 | a { 447 | text-decoration: none 448 | } 449 | 450 | u { 451 | background: $markdown-color-yellow 452 | } 453 | 454 | b, strong { 455 | font-weight: bold 456 | } 457 | 458 | p { 459 | font-weight: 200; 460 | padding: 0.5em; 461 | } 462 | 463 | h1, h2, h3, h4, h5, h6 { 464 | font-weight: 300; 465 | margin-top: 2.0rem; 466 | margin-bottom: 1rem; 467 | line-height: 1.5; 468 | } 469 | 470 | h1 { 471 | font-size: 2.4rem; 472 | font-weight: 700; 473 | margin-top: 4rem; 474 | margin-bottom: 2rem; 475 | } 476 | 477 | h2 { 478 | font-size: 1.8rem; 479 | font-weight: 500; 480 | margin-top: 3rem; 481 | border-bottom: 1px double rgba(0,0,0,.1); 482 | } 483 | 484 | h3 { 485 | font-size: 1.5rem; 486 | font-weight: 400; 487 | } 488 | 489 | h4 { 490 | font-size: 1.3rem; 491 | } 492 | 493 | h5 { 494 | font-size: 1.2rem; 495 | } 496 | 497 | h6 { 498 | font-size: 1.1rem; 499 | letter-spacing: 0; 500 | } 501 | 502 | p { 503 | font-size: 1.0rem; 504 | letter-spacing: 0.05em; 505 | } 506 | 507 | pre[class*="language-"] { 508 | margin: 1em 0; 509 | } 510 | 511 | p { 512 | line-height: $line-height; 513 | margin-bottom: 0; 514 | } 515 | 516 | ul, ol { 517 | margin-top: 0.8em; 518 | font-weight: 300; 519 | 520 | li { 521 | color: $markdown-list-color; 522 | line-height: $line-height; 523 | } 524 | } 525 | 526 | table { 527 | margin-top: 1em; 528 | } 529 | 530 | td { 531 | padding: 1rem 1rem; 532 | } 533 | 534 | pre { 535 | margin: 1em 0 !important; 536 | padding: 0 4px !important; 537 | 538 | code { 539 | padding: 16px 0; 540 | font-size: 0.9em; 541 | line-height: 1.5; 542 | } 543 | } 544 | } 545 | 546 | 547 | 548 | -------------------------------------------------------------------------------- /src/assets/images/esp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | esp 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Maturity(成熟度) 142 | 143 | 144 | Insights(洞见) 145 | 146 | 147 | Measure(度量) 148 | 149 | 150 | Capable(能力) 151 | 152 | 153 | Patterns(原则与模式) 154 | 155 | 156 | Starterkit(入门包) 157 | 158 | 159 | 160 | 161 | --------------------------------------------------------------------------------