ines-num lines-num-new"> 39
+  ],
40
+  exports: [RouterModule]
41
+})
42
+export class AppRoutingModule { }

+ 25 - 0
src/app/app.component.html

@@ -0,0 +1,25 @@
1
+<ion-app>
2
+  <ion-menu side="start" contentId="content">
3
+    <ion-header>
4
+      <ion-toolbar>
5
+        <ion-title class="menu-title">Menu</ion-title>
6
+      </ion-toolbar>
7
+    </ion-header>
8
+    <ion-content>
9
+      <ion-list>
10
+        <ion-menu-toggle auto-hide="true">
11
+          <ion-item routerLink="/tabs/tab1" routerDirection="forward">
12
+            <ion-label>Tab 1</ion-label>
13
+          </ion-item>
14
+          <ion-item routerLink="/tabs/tab2" routerDirection="forward">
15
+            <ion-label>Tab 2</ion-label>
16
+          </ion-item>
17
+          <ion-item routerLink="/tabs/tab3" routerDirection="forward">
18
+            <ion-label>Tab 3</ion-label>
19
+          </ion-item>
20
+        </ion-menu-toggle>
21
+      </ion-list>
22
+    </ion-content>
23
+  </ion-menu>
24
+  <ion-router-outlet id="content"></ion-router-outlet>
25
+</ion-app>  

+ 0 - 0
src/app/app.component.scss


+ 23 - 0
src/app/app.component.spec.ts

@@ -0,0 +1,23 @@
1
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
+import { TestBed, waitForAsync } from '@angular/core/testing';
3
+
4
+import { AppComponent } from './app.component';
5
+
6
+describe('AppComponent', () => {
7
+
8
+  beforeEach(waitForAsync(() => {
9
+
10
+    TestBed.configureTestingModule({
11
+      declarations: [AppComponent],
12
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
13
+    }).compileComponents();
14
+  }));
15
+
16
+  it('should create the app', () => {
17
+    const fixture = TestBed.createComponent(AppComponent);
18
+    const app = fixture.debugElement.componentInstance;
19
+    expect(app).toBeTruthy();
20
+  });
21
+  // TODO: add more tests!
22
+
23
+});

+ 10 - 0
src/app/app.component.ts

@@ -0,0 +1,10 @@
1
+import { Component } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-root',
5
+  templateUrl: 'app.component.html',
6
+  styleUrls: ['app.component.scss'],
7
+})
8
+export class AppComponent {
9
+  constructor() {}
10
+}

+ 26 - 0
src/app/app.module.ts

@@ -0,0 +1,26 @@
1
+import { NgModule } from '@angular/core';
2
+import { BrowserModule } from '@angular/platform-browser';
3
+import { RouteReuseStrategy } from '@angular/router';
4
+
5
+import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
6
+
7
+import { AppComponent } from './app.component';
8
+import { AppRoutingModule } from './app-routing.module';
9
+import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
10
+import { fas } from '@fortawesome/free-solid-svg-icons'
11
+import { far } from '@fortawesome/free-regular-svg-icons'
12
+import { fab } from '@fortawesome/free-brands-svg-icons'
13
+
14
+@NgModule({
15
+  declarations: [AppComponent],
16
+  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, FontAwesomeModule],
17
+  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
18
+  bootstrap: [AppComponent],
19
+})
20
+export class AppModule {
21
+
22
+  constructor(library: FaIconLibrary) { 
23
+		library.addIconPacks(fas, far, fab);
24
+	}
25
+  
26
+}

+ 17 - 0
src/app/article/article-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { ArticlePage } from './article.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: ArticlePage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class ArticlePageRoutingModule {}

+ 20 - 0
src/app/article/article.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { ArticlePageRoutingModule } from './article-routing.module';
8
+
9
+import { ArticlePage } from './article.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    ArticlePageRoutingModule
17
+  ],
18
+  declarations: [ArticlePage]
19
+})
20
+export class ArticlePageModule {}

+ 9 - 0
src/app/article/article.page.html

@@ -0,0 +1,9 @@
1
+<ion-header>
2
+  <ion-toolbar>
3
+    <ion-title>article</ion-title>
4
+  </ion-toolbar>
5
+</ion-header>
6
+
7
+<ion-content>
8
+
9
+</ion-content>

+ 0 - 0
src/app/article/article.page.scss


+ 24 - 0
src/app/article/article.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { ArticlePage } from './article.page';
5
+
6
+describe('ArticlePage', () => {
7
+  let component: ArticlePage;
8
+  let fixture: ComponentFixture<ArticlePage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ ArticlePage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(ArticlePage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 15 - 0
src/app/article/article.page.ts

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-article',
5
+  templateUrl: './article.page.html',
6
+  styleUrls: ['./article.page.scss'],
7
+})
8
+export class ArticlePage implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 16 - 0
src/app/home/home-routing.module.ts

@@ -0,0 +1,16 @@
1
+import { NgModule } from '@angular/core';
2
+import { RouterModule, Routes } from '@angular/router';
3
+import { HomePage } from './home.page';
4
+
5
+const routes: Routes = [
6
+  {
7
+    path: '',
8
+    component: HomePage,
9
+  }
10
+];
11
+
12
+@NgModule({
13
+  imports: [RouterModule.forChild(routes)],
14
+  exports: [RouterModule]
15
+})
16
+export class HomePageRoutingModule {}

+ 22 - 0
src/app/home/home.module.ts

@@ -0,0 +1,22 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { IonicModule } from '@ionic/angular';
4
+import { FormsModule } from '@angular/forms';
5
+import { HomePage } from './home.page';
6
+
7
+import { HomePageRoutingModule } from './home-routing.module';
8
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
9
+import { SwiperModule } from 'swiper/angular';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    HomePageRoutingModule,
17
+    FontAwesomeModule,
18
+    SwiperModule
19
+  ],
20
+  declarations: [HomePage]
21
+})
22
+export class HomePageModule {}

+ 86 - 0
src/app/home/home.page.html

@@ -0,0 +1,86 @@
1
+<ion-header [translucent]="true">
2
+  <ion-toolbar>
3
+    <ion-buttons slot="end">
4
+      <ion-menu-button></ion-menu-button>
5
+    </ion-buttons>
6
+    <img src="../../../assets/icon/logo-fm99.png" class="head-img">
7
+  </ion-toolbar>
8
+</ion-header>
9
+<ion-content>
10
+  <swiper #swiper [config]="config">
11
+    <ng-template swiperSlide><img src="../../../assets/img-static/fb-Ad-1.jpeg" alt=""></ng-template>
12
+    <ng-template swiperSlide><img src="../../../assets/img-static/fb-Ad-2.jpeg" alt=""></ng-template>
13
+    <ng-template swiperSlide><img src="../../../assets/img-static/fb-Ad-3.jpeg" alt=""></ng-template>
14
+  </swiper>
15
+  <!-- <div id="container">
16
+    <fa-icon [icon]="['fas', 'graduation-cap']"></fa-icon>
17
+    <hr>
18
+    <strong>Ready to create an app?</strong>
19
+    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer"
20
+        href="https://ionicframework.com/docs/components">UI Components</a></p>
21
+  </div> -->
22
+  <ion-grid>
23
+    <ion-row>
24
+      <ion-col>
25
+        <h2>ข่าวกีฬา</h2>
26
+      </ion-col>
27
+    </ion-row>
28
+    <ion-row>
29
+      <ion-col size="5">
30
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
31
+      </ion-col>
32
+      <ion-col>“น้องเทนนิส” หวังคว้าแชมป์ในศึกชิงแชมป์โลก ต้นเดือนหน้า</ion-col>
33
+    </ion-row>
34
+    <ion-row>
35
+      <ion-col size="5">
36
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
37
+      </ion-col>
38
+      <ion-col>โรนัลโด กลับมาซ้อมกับทีม พร้อมปรับความเข้าใจกับ เทน ฮาก</ion-col>
39
+    </ion-row>
40
+    <ion-row>
41
+      <ion-col size="5">
42
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
43
+      </ion-col>
44
+      <ion-col>เตรียมหารือบอร์ดหาข้อสรุป รัฐ-เอกชน จับมือยิงสดฟุตบอลโลก</ion-col>
45
+    </ion-row>
46
+    <ion-row>
47
+      <ion-col size="5">
48
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
49
+      </ion-col>
50
+      <ion-col>บีจี แยกทางกับ “มาโกโตะ เทกุระโมริ” หลังผลงานไม่เป็นตามเป้า</ion-col>
51
+    </ion-row>
52
+    <ion-row>
53
+      <ion-col>
54
+        <img src="../../../assets/img-static/banner-320x50.jpeg" alt="">
55
+      </ion-col>
56
+    </ion-row>
57
+    <ion-row>
58
+      <ion-col>
59
+        <h2>รายการย้อนหลัง</h2>
60
+      </ion-col>
61
+    </ion-row>
62
+    <ion-row>
63
+      <ion-col size="5">
64
+        <img src="../../assets/img-static/mqdefault-1.jpg">
65
+      </ion-col>
66
+      <ion-col>Morning Talk [26-10-2022 l 07:30 - 09:00 น. ]</ion-col>
67
+    </ion-row>
68
+    <ion-row>
69
+      <ion-col size="5">
70
+        <img src="../../assets/img-static/mqdefault-2.jpg">
71
+      </ion-col>
72
+      <ion-col>Golf Trick [26-10-2022 l 09:00 - 10:00 น.]</ion-col>
73
+    </ion-row>
74
+    <ion-row>
75
+      <ion-col size="5">
76
+        <img src="../../assets/img-static/mqdefault-3.jpg">
77
+      </ion-col>
78
+      <ion-col>เจาะสนามบอลไทย [ 20-10-2022 l 20:00 - 21:30 น. ]</ion-col>
79
+    </ion-row>
80
+    <ion-row>
81
+      <ion-col>
82
+        <img src="../../../assets/img-static/banner-320x100.jpeg" alt="">
83
+      </ion-col>
84
+    </ion-row>
85
+  </ion-grid>
86
+</ion-content>

+ 39 - 0
src/app/home/home.page.scss

@@ -0,0 +1,39 @@
1
+
2
+// ion-title { text-align: center !important; }
3
+
4
+#container strong {
5
+  font-size: 20px;
6
+  line-height: 26px;
7
+}
8
+
9
+#container p {
10
+  font-size: 16px;
11
+  line-height: 22px;
12
+
13
+  color: #8c8c8c;
14
+
15
+  margin: 0;
16
+}
17
+
18
+#container a {
19
+  text-decoration: none;
20
+}
21
+
22
+// .swiper .swiper-slide {
23
+//   height: 100%;
24
+//   width: 100%;
25
+// }
26
+
27
+.swiper-slide {
28
+  // display: flex;
29
+  // flex-direction: column;
30
+  // align-items: center;
31
+  // justify-content: center;
32
+  background-color: blueviolet;
33
+}
34
+
35
+ion-content{ 
36
+  font-family:IBM Plex Sans Thai SemiBold !important;
37
+  font-size:1em;
38
+  line-height: 1.4em;
39
+}

+ 24 - 0
src/app/home/home.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { HomePage } from './home.page';
5
+
6
+describe('HomePage', () => {
7
+  let component: HomePage;
8
+  let fixture: ComponentFixture<HomePage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ HomePage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(HomePage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 36 - 0
src/app/home/home.page.ts

@@ -0,0 +1,36 @@
1
+import { AfterContentChecked, Component, ViewChild, ViewEncapsulation } from '@angular/core';
2
+import {SwiperComponent} from 'swiper/angular';
3
+import { SwiperOptions } from 'swiper';
4
+import SwiperCore, { Pagination,Autoplay,Navigation,Keyboard } from 'swiper';
5
+
6
+SwiperCore.use([Pagination, Autoplay, Navigation, Keyboard]);
7
+@Component({
8
+  selector: 'app-home',
9
+  templateUrl: 'home.page.html',
10
+  styleUrls: ['home.page.scss'],
11
+  encapsulation: ViewEncapsulation.None
12
+})
13
+export class HomePage implements AfterContentChecked {
14
+  
15
+  @ViewChild('swiper') swiper: SwiperComponent;
16
+
17
+  config: SwiperOptions = {
18
+    slidesPerView: 1,  
19
+    pagination: { clickable: true },
20
+    autoplay: {
21
+      delay: 200,
22
+      disableOnInteraction: false,
23
+    },
24
+    loop: true,
25
+    navigation: true,
26
+  }
27
+
28
+  constructor() {}
29
+
30
+  ngAfterContentChecked() {
31
+    if (this.swiper) {
32
+      this.swiper.updateSwiper({});
33
+    }    
34
+  }
35
+
36
+}

+ 45 - 0
src/app/navtabs/navtabs-routing.module.ts

@@ -0,0 +1,45 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { NavtabsPage } from './navtabs.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: 'navtabsTab',
9
+    component: NavtabsPage,
10
+    children: [
11
+      {
12
+        path: 'home',
13
+        loadChildren: () => import('../home/home.module').then( m => m.HomePageModule)
14
+      },   
15
+      {
16
+        path: 'news',
17
+        loadChildren: () => import('../news/news.module').then( m => m.NewsPageModule)
18
+      },   
19
+      {
20
+        path: 'article',
21
+        loadChildren: () => import('../article/article.module').then( m => m.ArticlePageModule)
22
+      },   
23
+      {
24
+        path: 'youtube',
25
+        loadChildren: () => import('../youtube/youtube.module').then( m => m.YoutubePageModule)
26
+      }, 
27
+      {
28
+        path: '',
29
+        redirectTo: '/navtabs/navtabsTab/home',
30
+        pathMatch: 'full'
31
+      }
32
+    ]
33
+  },
34
+  {
35
+    path: '',
36
+    redirectTo: '/navtabs/navtabsTab/home',
37
+    pathMatch: 'full'    
38
+  }
39
+];
40
+
41
+@NgModule({
42
+  imports: [RouterModule.forChild(routes)],
43
+  exports: [RouterModule],
44
+})
45
+export class NavtabsPageRoutingModule {}

+ 20 - 0
src/app/navtabs/navtabs.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { NavtabsPageRoutingModule } from './navtabs-routing.module';
8
+
9
+import { NavtabsPage } from './navtabs.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    NavtabsPageRoutingModule
17
+  ],
18
+  declarations: [NavtabsPage]
19
+})
20
+export class NavtabsPageModule {}

+ 25 - 0
src/app/navtabs/navtabs.page.html

@@ -0,0 +1,25 @@
1
+
2
+<ion-tabs>
3
+  <ion-tab-bar slot="bottom">
4
+    <ion-tab-button tab="home">
5
+      <ion-icon name="globe"></ion-icon>
6
+      <!-- <ion-label>Home</ion-label> -->      
7
+    </ion-tab-button>
8
+    <ion-tab-button tab="news">
9
+      <ion-icon name="football"></ion-icon>
10
+      <!-- <ion-label>News</ion-label> -->
11
+    </ion-tab-button>
12
+    <ion-tab-button tab="#">
13
+      <ion-icon name="play-circle"></ion-icon>
14
+      <!-- <ion-label>Audio</ion-label> -->
15
+    </ion-tab-button>  
16
+    <ion-tab-button tab="youtube">
17
+      <ion-icon name="logo-youtube"></ion-icon>
18
+      <!-- <ion-label>Youtube</ion-label> -->
19
+    </ion-tab-button>  
20
+    <ion-tab-button tab="#">
21
+      <ion-icon name="videocam"></ion-icon>
22
+      <!-- <ion-label>Video</ion-label> -->
23
+    </ion-tab-button>
24
+  </ion-tab-bar>
25
+</ion-tabs>

+ 98 - 0
src/app/navtabs/navtabs.page.scss

@@ -0,0 +1,98 @@
1
+// ::ng-deep ion-icon.icon-button {
2
+//     background: #3c457e !important;
3
+//     color: white !important;
4
+//     border-radius: 50%!important;
5
+// }
6
+
7
+// :host ::ng-deep .tabs-inner {
8
+//     position: unset!important;
9
+//     contain: size style!important;
10
+//   }
11
+  
12
+//   .bottom-tab-bar {
13
+//     --background: transparent;
14
+//     --border: 0;
15
+//   }
16
+  
17
+//   ion-tab-button {
18
+//     --background: #134d80;
19
+//     --color:white;
20
+//     --color-selected: red;
21
+//     --ripple-color: white;
22
+//   }
23
+  
24
+
25
+// .button-center {
26
+//     --background: transparent!important;
27
+//     ion-icon {
28
+//       height: 40px;
29
+//       width: 40px;
30
+//       font-size: 60px;
31
+//     }
32
+// }
33
+// ion-tab-bar {
34
+//     &:before {
35
+//       box-shadow: 0 387px 0 300px #3c457e;
36
+//       position: absolute;
37
+//       margin-top: -48px;
38
+//       padding: 56px;
39
+//       border-radius: 65%;
40
+//       content: '';
41
+//     }
42
+//   }
43
+  
44
+// .inner-left-btn {
45
+//     border-radius: 0 50% 0 0; 
46
+//     //create the curved style on the left side of the center
47
+// }
48
+
49
+
50
+// .inner-right-btn {
51
+//     border-radius: 50% 0 0 0; 
52
+//     // create the curved style on the right side of the center
53
+// }
54
+  
55
+// .inner-center-btn {
56
+//     position: absolute;
57
+//     left: calc(50% - 35px); // position your button in the center
58
+//     bottom: 20px; // position button slightly above the half
59
+//     font-size: 70px;
60
+//     --background: transparent;
61
+// }
62
+
63
+// ion-tab-bar {
64
+//     overflow-x: scroll;
65
+//     display: flex;    
66
+//     overflow-x: auto;
67
+//     ion-tab-button {
68
+//         display: inline-block;
69
+//         min-width: 100px;
70
+//         width: auto;
71
+//     }
72
+// }
73
+
74
+// ion-tab-bar {
75
+//   bottom: 20px;
76
+//   position: relative;
77
+//   border-radius: 16px;
78
+//   width: 92%;
79
+//   margin: 0 auto;
80
+// }
81
+
82
+// ion-tab-button {
83
+//   --color: var(--ion-color-medium);
84
+//   --color-selected: var(--ion-color-primary);
85
+
86
+//   &::before {
87
+//     background-color: transparent;
88
+//     display: block;
89
+//     content: "";
90
+//     margin: 0 auto;
91
+//     width: 20px;
92
+//     height: 2px;
93
+//   }
94
+
95
+//   &.tab-selected::before {
96
+//     background-color: var(--ion-color-primary);
97
+//   }
98
+// }

+ 24 - 0
src/app/navtabs/navtabs.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { NavtabsPage } from './navtabs.page';
5
+
6
+describe('NavtabsPage', () => {
7
+  let component: NavtabsPage;
8
+  let fixture: ComponentFixture<NavtabsPage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ NavtabsPage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(NavtabsPage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 15 - 0
src/app/navtabs/navtabs.page.ts

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-navtabs',
5
+  templateUrl: './navtabs.page.html',
6
+  styleUrls: ['./navtabs.page.scss'],
7
+})
8
+export class NavtabsPage implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 17 - 0
src/app/news/news-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { NewsPage } from './news.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: NewsPage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class NewsPageRoutingModule {}

+ 22 - 0
src/app/news/news.module.ts

@@ -0,0 +1,22 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { NewsPageRoutingModule } from './news-routing.module';
8
+
9
+import { NewsPage } from './news.page';
10
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
11
+
12
+@NgModule({
13
+  imports: [
14
+    CommonModule,
15
+    FormsModule,
16
+    IonicModule,
17
+    NewsPageRoutingModule,
18
+    FontAwesomeModule,
19
+  ],
20
+  declarations: [NewsPage]
21
+})
22
+export class NewsPageModule {}

+ 72 - 0
src/app/news/news.page.html

@@ -0,0 +1,72 @@
1
+<ion-header [translucent]="true">
2
+  <ion-toolbar>
3
+    <ion-buttons slot="end">
4
+      <ion-menu-button></ion-menu-button>
5
+    </ion-buttons>
6
+    <img src="../../../assets/icon/logo-fm99.png" class="head-img">
7
+  </ion-toolbar>
8
+</ion-header>
9
+
10
+<ion-content>
11
+  <img src="../../../assets/img-static/banner-320x50.jpeg" alt="">
12
+  <ion-grid>
13
+    <ion-row>
14
+      <ion-col>
15
+        <h2>ข่าวกีฬา</h2>
16
+      </ion-col>
17
+    </ion-row>
18
+    <ion-row>
19
+      <ion-col size="5">
20
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
21
+      </ion-col>
22
+      <ion-col>“น้องเทนนิส” หวังคว้าแชมป์ในศึกชิงแชมป์โลก ต้นเดือนหน้า</ion-col>
23
+    </ion-row>
24
+    <ion-row>
25
+      <ion-col size="5">
26
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
27
+      </ion-col>
28
+      <ion-col>โรนัลโด กลับมาซ้อมกับทีม พร้อมปรับความเข้าใจกับ เทน ฮาก</ion-col>
29
+    </ion-row>
30
+    <ion-row>
31
+      <ion-col size="5">
32
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
33
+      </ion-col>
34
+      <ion-col>เตรียมหารือบอร์ดหาข้อสรุป รัฐ-เอกชน จับมือยิงสดฟุตบอลโลก</ion-col>
35
+    </ion-row>
36
+    <ion-row>
37
+      <ion-col size="5">
38
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
39
+      </ion-col>
40
+      <ion-col>บีจี แยกทางกับ “มาโกโตะ เทกุระโมริ” หลังผลงานไม่เป็นตามเป้า</ion-col>
41
+    </ion-row>
42
+    <ion-row>
43
+      <ion-col size="5">
44
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
45
+      </ion-col>
46
+      <ion-col>“น้องเทนนิส” หวังคว้าแชมป์ในศึกชิงแชมป์โลก ต้นเดือนหน้า</ion-col>
47
+    </ion-row>
48
+    <ion-row>
49
+      <ion-col size="5">
50
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
51
+      </ion-col>
52
+      <ion-col>โรนัลโด กลับมาซ้อมกับทีม พร้อมปรับความเข้าใจกับ เทน ฮาก</ion-col>
53
+    </ion-row>
54
+    <ion-row>
55
+      <ion-col size="5">
56
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
57
+      </ion-col>
58
+      <ion-col>เตรียมหารือบอร์ดหาข้อสรุป รัฐ-เอกชน จับมือยิงสดฟุตบอลโลก</ion-col>
59
+    </ion-row>
60
+    <ion-row>
61
+      <ion-col size="5">
62
+        <img src="../../assets/img-static/SEI129887404-c4a9.jpg">
63
+      </ion-col>
64
+      <ion-col>บีจี แยกทางกับ “มาโกโตะ เทกุระโมริ” หลังผลงานไม่เป็นตามเป้า</ion-col>
65
+    </ion-row> 
66
+    <ion-row>
67
+      <ion-col>
68
+        <img src="../../../assets/img-static/banner-320x100.jpeg" alt="">
69
+      </ion-col>
70
+    </ion-row>
71
+  </ion-grid>
72
+</ion-content>

+ 0 - 0
src/app/news/news.page.scss


+ 24 - 0
src/app/news/news.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { NewsPage } from './news.page';
5
+
6
+describe('NewsPage', () => {
7
+  let component: NewsPage;
8
+  let fixture: ComponentFixture<NewsPage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ NewsPage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(NewsPage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 15 - 0
src/app/news/news.page.ts

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-news',
5
+  templateUrl: './news.page.html',
6
+  styleUrls: ['./news.page.scss'],
7
+})
8
+export class NewsPage implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 17 - 0
src/app/youtube/youtube-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { YoutubePage } from './youtube.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: YoutubePage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class YoutubePageRoutingModule {}

+ 20 - 0
src/app/youtube/youtube.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { YoutubePageRoutingModule } from './youtube-routing.module';
8
+
9
+import { YoutubePage } from './youtube.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    YoutubePageRoutingModule
17
+  ],
18
+  declarations: [YoutubePage]
19
+})
20
+export class YoutubePageModule {}

+ 42 - 0
src/app/youtube/youtube.page.html

@@ -0,0 +1,42 @@
1
+<ion-header [translucent]="true">
2
+  <ion-toolbar>
3
+    <ion-buttons slot="end">
4
+      <ion-menu-button></ion-menu-button>
5
+    </ion-buttons>
6
+    <img src="../../../assets/icon/logo-fm99.png" class="head-img">
7
+  </ion-toolbar>
8
+</ion-header>
9
+
10
+<ion-content>
11
+  <img src="../../../assets/img-static/banner-320x50.jpeg" alt="">
12
+  <ion-grid>
13
+    <ion-row>
14
+      <ion-col>
15
+        <h2>รายการย้อนหลัง</h2>
16
+      </ion-col>
17
+    </ion-row>
18
+    <ion-row>
19
+      <ion-col size="5">
20
+        <img src="../../assets/img-static/mqdefault-1.jpg">
21
+      </ion-col>
22
+      <ion-col>Morning Talk [26-10-2022 l 07:30 - 09:00 น. ]</ion-col>
23
+    </ion-row>
24
+    <ion-row>
25
+      <ion-col size="5">
26
+        <img src="../../assets/img-static/mqdefault-2.jpg">
27
+      </ion-col>
28
+      <ion-col>Golf Trick [26-10-2022 l 09:00 - 10:00 น.]</ion-col>
29
+    </ion-row>
30
+    <ion-row>
31
+      <ion-col size="5">
32
+        <img src="../../assets/img-static/mqdefault-3.jpg">
33
+      </ion-col>
34
+      <ion-col>เจาะสนามบอลไทย [ 20-10-2022 l 20:00 - 21:30 น. ]</ion-col>
35
+    </ion-row>
36
+    <ion-row>
37
+      <ion-col>
38
+        <img src="../../../assets/img-static/banner-320x100.jpeg" alt="">
39
+      </ion-col>
40
+    </ion-row>
41
+  </ion-grid>
42
+</ion-content>

+ 0 - 0
src/app/youtube/youtube.page.scss


+ 24 - 0
src/app/youtube/youtube.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { YoutubePage } from './youtube.page';
5
+
6
+describe('YoutubePage', () => {
7
+  let component: YoutubePage;
8
+  let fixture: ComponentFixture<YoutubePage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ YoutubePage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(YoutubePage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 15 - 0
src/app/youtube/youtube.page.ts

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-youtube',
5
+  templateUrl: './youtube.page.html',
6
+  styleUrls: ['./youtube.page.scss'],
7
+})
8
+export class YoutubePage implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

BIN
src/assets/fonts/IBMPlexSansThai-Regular.ttf


BIN
src/assets/fonts/IBMPlexSansThai-SemiBold.ttf


BIN
src/assets/fonts/K2D-Light.ttf


BIN
src/assets/fonts/Montserrat-Medium.ttf


BIN
src/assets/fonts/Montserrat-SemiBold.ttf


BIN
src/assets/fonts/Prompt-Light.ttf


BIN
src/assets/fonts/Prompt-Medium.ttf


BIN
src/assets/icon/favicon.png


BIN
src/assets/icon/logo-fm99.png


BIN
src/assets/img-static/SEI129887404-c4a9.jpg


BIN
src/assets/img-static/banner-250x250.jpeg


BIN
src/assets/img-static/banner-320x100.jpeg


BIN
src/assets/img-static/banner-320x50.jpeg


BIN
src/assets/img-static/fb-Ad-1.jpeg


BIN
src/assets/img-static/fb-Ad-2.jpeg


BIN
src/assets/img-static/fb-Ad-3.jpeg


BIN
src/assets/img-static/mqdefault-1.jpg


BIN
src/assets/img-static/mqdefault-2.jpg


BIN
src/assets/img-static/mqdefault-3.jpg


File diff suppressed because it is too large
+ 1 - 0
src/assets/shapes.svg


+ 3 - 0
src/environments/environment.prod.ts

@@ -0,0 +1,3 @@
1
+export const environment = {
2
+  production: true
3
+};

+ 16 - 0
src/environments/environment.ts

@@ -0,0 +1,16 @@
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
+};
8
+
9
+/*
10
+ * For easier debugging in development mode, you can import the following file
11
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12
+ *
13
+ * This import should be commented out in production mode because it will have a negative impact
14
+ * on performance if an error is thrown.
15
+ */
16
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.

+ 106 - 0
src/global.scss

@@ -0,0 +1,106 @@
1
+/*
2
+ * App Global CSS
3
+ * ----------------------------------------------------------------------------
4
+ * Put style rules here that you want to apply globally. These styles are for
5
+ * the entire app and not just one component. Additionally, this file can be
6
+ * used as an entry point to import other CSS/Sass files to be included in the
7
+ * output CSS.
8
+ * For more information on global stylesheets, visit the documentation:
9
+ * https://ionicframework.com/docs/layout/global-stylesheets
10
+ */
11
+
12
+/* Core CSS required for Ionic components to work properly */
13
+@import "~@ionic/angular/css/core.css";
14
+
15
+/* Basic CSS for apps built with Ionic */
16
+@import "~@ionic/angular/css/normalize.css";
17
+@import "~@ionic/angular/css/structure.css";
18
+@import "~@ionic/angular/css/typography.css";
19
+@import '~@ionic/angular/css/display.css';
20
+
21
+/* Optional CSS utils that can be commented out */
22
+@import "~@ionic/angular/css/padding.css";
23
+@import "~@ionic/angular/css/float-elements.css";
24
+@import "~@ionic/angular/css/text-alignment.css";
25
+@import "~@ionic/angular/css/text-transformation.css";
26
+@import "~@ionic/angular/css/flex-utils.css";
27
+
28
+// @import "swiper/scss";
29
+// @import "swiper/scss/navigation.css";
30
+// @import "swiper/scss/autoplay";
31
+@import "@ionic/angular/css/ionic-swiper";
32
+@import "swiper/scss";
33
+@import "swiper/scss/navigation";
34
+@import "swiper/scss/pagination";
35
+@import "swiper/scss/autoplay";
36
+// @import "swiper/css/bundle";
37
+// @import "swiper/css/autoplay";
38
+// @import "~swiper/swiper-bundle";
39
+// @import "swiper/scss";
40
+// @import "swiper/scss/autoplay";
41
+// @import "swiper/scss/keyboard";
42
+// @import "swiper/scss/pagination";
43
+// @import "swiper/scss/scrollbar";
44
+// @import "swiper/scss/zoom";
45
+
46
+
47
+
48
+@font-face {
49
+    font-family: "IBM Plex Sans Thai";
50
+    font-style: normal;
51
+    font-weight: normal;
52
+    src: url("./assets/fonts/IBMPlexSansThai-Regular.ttf");
53
+  }
54
+
55
+  @font-face {
56
+    font-family: "IBM Plex Sans Thai SemiBold";
57
+    font-style: normal;
58
+    font-weight: normal;
59
+    src: url("./assets/fonts/IBMPlexSansThai-SemiBold.ttf");
60
+  }  
61
+  @font-face {
62
+    font-family: "Prompt";
63
+    font-style: normal;
64
+    font-weight: normal;
65
+    src: url("./assets/fonts/Prompt-Medium.ttf");
66
+  }  
67
+  @font-face {
68
+    font-family: "Montserrat";
69
+    font-style: normal;
70
+    font-weight: normal;
71
+    src: url("./assets/fonts/Montserrat-Medium.ttf");
72
+  }   
73
+
74
+  @font-face {
75
+    font-family: "K2D";
76
+    font-style: normal;
77
+    font-weight: normal;
78
+    src: url("./assets/fonts/K2D-Light.ttf");
79
+  }  
80
+
81
+
82
+ion-title {
83
+    font-family:Montserrat !important;
84
+    font-size:1.4em;    
85
+}
86
+
87
+
88
+h2{
89
+    font-family:Prompt !important;
90
+    font-size:1.3em;
91
+    margin-top: 5px;
92
+    margin-bottom: 0px;
93
+
94
+}
95
+
96
+h3{
97
+    font-family:IBM Plex Sans Thai SemiBold !important;
98
+    font-size:1.3em;    
99
+}
100
+
101
+.head-img {
102
+  width: 15%;
103
+  display: flex;
104
+  margin-left:10px;
105
+  margin-top:5px;
106
+}

+ 26 - 0
src/index.html

@@ -0,0 +1,26 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+
4
+<head>
5
+  <meta charset="utf-8" />
6
+  <title>Ionic App</title>
7
+
8
+  <base href="/" />
9
+
10
+  <meta name="color-scheme" content="light dark" />
11
+  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
12
+  <meta name="format-detection" content="telephone=no" />
13
+  <meta name="msapplication-tap-highlight" content="no" />
14
+
15
+  <link rel="icon" type="image/png" href="assets/icon/favicon.png" />
16
+
17
+  <!-- add to homescreen for ios -->
18
+  <meta name="apple-mobile-web-app-capable" content="yes" />
19
+  <meta name="apple-mobile-web-app-status-bar-style" content="black" />
20
+</head>
21
+
22
+<body>
23
+  <app-root></app-root>
24
+</body>
25
+
26
+</html>

+ 12 - 0
src/main.ts

@@ -0,0 +1,12 @@
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().bootstrapModule(AppModule)
12
+  .catch(err => console.log(err));

+ 65 - 0
src/polyfills.ts

@@ -0,0 +1,65 @@
1
+/**
2
+ * This file includes polyfills needed by Angular and is loaded before the app.
3
+ * You can add your own extra polyfills to this file.
4
+ *
5
+ * This file is divided into 2 sections:
6
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
8
+ *      file.
9
+ *
10
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13
+ *
14
+ * Learn more in https://angular.io/guide/browser-support
15
+ */
16
+
17
+/***************************************************************************************************
18
+ * BROWSER POLYFILLS
19
+ */
20
+
21
+/** IE11 requires the following for NgClass support on SVG elements */
22
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
23
+
24
+/**
25
+ * Web Animations `@angular/platform-browser/animations`
26
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28
+ */
29
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
30
+
31
+/**
32
+ * By default, zone.js will patch all possible macroTask and DomEvents
33
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
34
+ * because those flags need to be set before `zone.js` being loaded, and webpack
35
+ * will put import in the top of bundle, so user need to create a separate file
36
+ * in this directory (for example: zone-flags.ts), and put the following flags
37
+ * into that file, and then add the following code before importing zone.js.
38
+ * import './zone-flags';
39
+ *
40
+ * The flags allowed in zone-flags.ts are listed here.
41
+ *
42
+ * The following flags will work for all browsers.
43
+ *
44
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47
+ *
48
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
50
+ *
51
+ *  (window as any).__Zone_enable_cross_context_check = true;
52
+ *
53
+ */
54
+
55
+import './zone-flags';
56
+
57
+/***************************************************************************************************
58
+ * Zone JS is required by default for Angular itself.
59
+ */
60
+import 'zone.js/dist/zone';  // Included with Angular CLI.
61
+
62
+
63
+/***************************************************************************************************
64
+ * APPLICATION IMPORTS
65
+ */

+ 25 - 0
src/test.ts

@@ -0,0 +1,25 @@
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(path: string, deep?: boolean, filter?: RegExp): {
12
+    <T>(id: string): T;
13
+    keys(): string[];
14
+  };
15
+};
16
+
17
+// First, initialize the Angular testing environment.
18
+getTestBed().initTestEnvironment(
19
+  BrowserDynamicTestingModule,
20
+  platformBrowserDynamicTesting()
21
+);
22
+// Then we find all the tests.
23
+const context = require.context('./', true, /\.spec\.ts$/);
24
+// And load the modules.
25
+context.keys().map(context);

+ 236 - 0
src/theme/variables.scss

@@ -0,0 +1,236 @@
1
+// Ionic Variables and Theming. For more info, please see:
2
+// http://ionicframework.com/docs/theming/
3
+
4
+/** Ionic CSS Variables **/
5
+:root {
6
+  /** primary **/
7
+  --ion-color-primary: #3880ff;
8
+  --ion-color-primary-rgb: 56, 128, 255;
9
+  --ion-color-primary-contrast: #ffffff;
10
+  --ion-color-primary-contrast-rgb: 255, 255, 255;
11
+  --ion-color-primary-shade: #3171e0;
12
+  --ion-color-primary-tint: #4c8dff;
13
+
14
+  /** secondary **/
15
+  --ion-color-secondary: #3dc2ff;
16
+  --ion-color-secondary-rgb: 61, 194, 255;
17
+  --ion-color-secondary-contrast: #ffffff;
18
+  --ion-color-secondary-contrast-rgb: 255, 255, 255;
19
+  --ion-color-secondary-shade: #36abe0;
20
+  --ion-color-secondary-tint: #50c8ff;
21
+
22
+  /** tertiary **/
23
+  --ion-color-tertiary: #5260ff;
24
+  --ion-color-tertiary-rgb: 82, 96, 255;
25
+  --ion-color-tertiary-contrast: #ffffff;
26
+  --ion-color-tertiary-contrast-rgb: 255, 255, 255;
27
+  --ion-color-tertiary-shade: #4854e0;
28
+  --ion-color-tertiary-tint: #6370ff;
29
+
30
+  /** success **/
31
+  --ion-color-success: #2dd36f;
32
+  --ion-color-success-rgb: 45, 211, 111;
33
+  --ion-color-success-contrast: #ffffff;
34
+  --ion-color-success-contrast-rgb: 255, 255, 255;
35
+  --ion-color-success-shade: #28ba62;
36
+  --ion-color-success-tint: #42d77d;
37
+
38
+  /** warning **/
39
+  --ion-color-warning: #ffc409;
40
+  --ion-color-warning-rgb: 255, 196, 9;
41
+  --ion-color-warning-contrast: #000000;
42
+  --ion-color-warning-contrast-rgb: 0, 0, 0;
43
+  --ion-color-warning-shade: #e0ac08;
44
+  --ion-color-warning-tint: #ffca22;
45
+
46
+  /** danger **/
47
+  --ion-color-danger: #eb445a;
48
+  --ion-color-danger-rgb: 235, 68, 90;
49
+  --ion-color-danger-contrast: #ffffff;
50
+  --ion-color-danger-contrast-rgb: 255, 255, 255;
51
+  --ion-color-danger-shade: #cf3c4f;
52
+  --ion-color-danger-tint: #ed576b;
53
+
54
+  /** dark **/
55
+  --ion-color-dark: #222428;
56
+  --ion-color-dark-rgb: 34, 36, 40;
57
+  --ion-color-dark-contrast: #ffffff;
58
+  --ion-color-dark-contrast-rgb: 255, 255, 255;
59
+  --ion-color-dark-shade: #1e2023;
60
+  --ion-color-dark-tint: #383a3e;
61
+
62
+  /** medium **/
63
+  --ion-color-medium: #92949c;
64
+  --ion-color-medium-rgb: 146, 148, 156;
65
+  --ion-color-medium-contrast: #ffffff;
66
+  --ion-color-medium-contrast-rgb: 255, 255, 255;
67
+  --ion-color-medium-shade: #808289;
68
+  --ion-color-medium-tint: #9d9fa6;
69
+
70
+  /** light **/
71
+  --ion-color-light: #f4f5f8;
72
+  --ion-color-light-rgb: 244, 245, 248;
73
+  --ion-color-light-contrast: #000000;
74
+  --ion-color-light-contrast-rgb: 0, 0, 0;
75
+  --ion-color-light-shade: #d7d8da;
76
+  --ion-color-light-tint: #f5f6f9;
77
+}
78
+
79
+@media (prefers-color-scheme: dark) {
80
+  /*
81
+   * Dark Colors
82
+   * -------------------------------------------
83
+   */
84
+
85
+  body {
86
+    --ion-color-primary: #428cff;
87
+    --ion-color-primary-rgb: 66,140,255;
88
+    --ion-color-primary-contrast: #ffffff;
89
+    --ion-color-primary-contrast-rgb: 255,255,255;
90
+    --ion-color-primary-shade: #3a7be0;
91
+    --ion-color-primary-tint: #5598ff;
92
+
93
+    --ion-color-secondary: #50c8ff;
94
+    --ion-color-secondary-rgb: 80,200,255;
95
+    --ion-color-secondary-contrast: #ffffff;
96
+    --ion-color-secondary-contrast-rgb: 255,255,255;
97
+    --ion-color-secondary-shade: #46b0e0;
98
+    --ion-color-secondary-tint: #62ceff;
99
+
100
+    --ion-color-tertiary: #6a64ff;
101
+    --ion-color-tertiary-rgb: 106,100,255;
102
+    --ion-color-tertiary-contrast: #ffffff;
103
+    --ion-color-tertiary-contrast-rgb: 255,255,255;
104
+    --ion-color-tertiary-shade: #5d58e0;
105
+    --ion-color-tertiary-tint: #7974ff;
106
+
107
+    --ion-color-success: #2fdf75;
108
+    --ion-color-success-rgb: 47,223,117;
109
+    --ion-color-success-contrast: #000000;
110
+    --ion-color-success-contrast-rgb: 0,0,0;
111
+    --ion-color-success-shade: #29c467;
112
+    --ion-color-success-tint: #44e283;
113
+
114
+    --ion-color-warning: #ffd534;
115
+    --ion-color-warning-rgb: 255,213,52;
116
+    --ion-color-warning-contrast: #000000;
117
+    --ion-color-warning-contrast-rgb: 0,0,0;
118
+    --ion-color-warning-shade: #e0bb2e;
119
+    --ion-color-warning-tint: #ffd948;
120
+
121
+    --ion-color-danger: #ff4961;
122
+    --ion-color-danger-rgb: 255,73,97;
123
+    --ion-color-danger-contrast: #ffffff;
124
+    --ion-color-danger-contrast-rgb: 255,255,255;
125
+    --ion-color-danger-shade: #e04055;
126
+    --ion-color-danger-tint: #ff5b71;
127
+
128
+    --ion-color-dark: #f4f5f8;
129
+    --ion-color-dark-rgb: 244,245,248;
130
+    --ion-color-dark-contrast: #000000;
131
+    --ion-color-dark-contrast-rgb: 0,0,0;
132
+    --ion-color-dark-shade: #d7d8da;
133
+    --ion-color-dark-tint: #f5f6f9;
134
+
135
+    --ion-color-medium: #989aa2;
136
+    --ion-color-medium-rgb: 152,154,162;
137
+    --ion-color-medium-contrast: #000000;
138
+    --ion-color-medium-contrast-rgb: 0,0,0;
139
+    --ion-color-medium-shade: #86888f;
140
+    --ion-color-medium-tint: #a2a4ab;
141
+
142
+    --ion-color-light: #222428;
143
+    --ion-color-light-rgb: 34,36,40;
144
+    --ion-color-light-contrast: #ffffff;
145
+    --ion-color-light-contrast-rgb: 255,255,255;
146
+    --ion-color-light-shade: #1e2023;
147
+    --ion-color-light-tint: #383a3e;
148
+  }
149
+
150
+  /*
151
+   * iOS Dark Theme
152
+   * -------------------------------------------
153
+   */
154
+
155
+  .ios body {
156
+    --ion-background-color: #000000;
157
+    --ion-background-color-rgb: 0,0,0;
158
+
159
+    --ion-text-color: #ffffff;
160
+    --ion-text-color-rgb: 255,255,255;
161
+
162
+    --ion-color-step-50: #0d0d0d;
163
+    --ion-color-step-100: #1a1a1a;
164
+    --ion-color-step-150: #262626;
165
+    --ion-color-step-200: #333333;
166
+    --ion-color-step-250: #404040;
167
+    --ion-color-step-300: #4d4d4d;
168
+    --ion-color-step-350: #595959;
169
+    --ion-color-step-400: #666666;
170
+    --ion-color-step-450: #737373;
171
+    --ion-color-step-500: #808080;
172
+    --ion-color-step-550: #8c8c8c;
173
+    --ion-color-step-600: #999999;
174
+    --ion-color-step-650: #a6a6a6;
175
+    --ion-color-step-700: #b3b3b3;
176
+    --ion-color-step-750: #bfbfbf;
177
+    --ion-color-step-800: #cccccc;
178
+    --ion-color-step-850: #d9d9d9;
179
+    --ion-color-step-900: #e6e6e6;
180
+    --ion-color-step-950: #f2f2f2;
181
+
182
+    --ion-item-background: #000000;
183
+
184
+    --ion-card-background: #1c1c1d;
185
+  }
186
+
187
+  .ios ion-modal {
188
+    --ion-background-color: var(--ion-color-step-100);
189
+    --ion-toolbar-background: var(--ion-color-step-150);
190
+    --ion-toolbar-border-color: var(--ion-color-step-250);
191
+  }
192
+
193
+
194
+  /*
195
+   * Material Design Dark Theme
196
+   * -------------------------------------------
197
+   */
198
+
199
+  .md body {
200
+    --ion-background-color: #121212;
201
+    --ion-background-color-rgb: 18,18,18;
202
+
203
+    --ion-text-color: #ffffff;
204
+    --ion-text-color-rgb: 255,255,255;
205
+
206
+    --ion-border-color: #222222;
207
+
208
+    --ion-color-step-50: #1e1e1e;
209
+    --ion-color-step-100: #2a2a2a;
210
+    --ion-color-step-150: #363636;
211
+    --ion-color-step-200: #414141;
212
+    --ion-color-step-250: #4d4d4d;
213
+    --ion-color-step-300: #595959;
214
+    --ion-color-step-350: #656565;
215
+    --ion-color-step-400: #717171;
216
+    --ion-color-step-450: #7d7d7d;
217
+    --ion-color-step-500: #898989;
218
+    --ion-color-step-550: #949494;
219
+    --ion-color-step-600: #a0a0a0;
220
+    --ion-color-step-650: #acacac;
221
+    --ion-color-step-700: #b8b8b8;
222
+    --ion-color-step-750: #c4c4c4;
223
+    --ion-color-step-800: #d0d0d0;
224
+    --ion-color-step-850: #dbdbdb;
225
+    --ion-color-step-900: #e7e7e7;
226
+    --ion-color-step-950: #f3f3f3;
227
+
228
+    --ion-item-background: #1e1e1e;
229
+
230
+    --ion-toolbar-background: #1f1f1f;
231
+
232
+    --ion-tab-bar-background: #1f1f1f;
233
+
234
+    --ion-card-background: #1e1e1e;
235
+  }
236
+}

+ 6 - 0
src/zone-flags.ts

@@ -0,0 +1,6 @@
1
+/**
2
+ * Prevents Angular change detection from
3
+ * running with certain Web Component callbacks
4
+ */
5
+// eslint-disable-next-line no-underscore-dangle
6
+(window as any).__Zone_disable_customElements = true;

+ 15 - 0
tsconfig.app.json

@@ -0,0 +1,15 @@
1
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+{
3
+  "extends": "./tsconfig.json",
4
+  "compilerOptions": {
5
+    "outDir": "./out-tsc/app",
6
+    "types": []
7
+  },
8
+  "files": [
9
+    "src/main.ts",
10
+    "src/polyfills.ts"
11
+  ],
12
+  "include": [
13
+    "src/**/*.d.ts"
14
+  ]
15
+}

+ 23 - 0
tsconfig.json

@@ -0,0 +1,23 @@
1
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+{
3
+  "compileOnSave": false,
4
+  "compilerOptions": {
5
+    "baseUrl": "./",
6
+    "outDir": "./dist/out-tsc",
7
+    "sourceMap": true,
8
+    "declaration": false,
9
+    "downlevelIteration": true,
10
+    "experimentalDecorators": true,
11
+    "moduleResolution": "node",
12
+    "importHelpers": true,
13
+    "target": "es2015",
14
+    "module": "es2020",
15
+    "lib": ["es2018", "dom"]
16
+  },
17
+  "angularCompilerOptions": {
18
+    "enableI18nLegacyMessageIdFormat": false,
19
+    "strictInjectionParameters": true,
20
+    "strictInputAccessModifiers": true,
21
+    "strictTemplates": true
22
+  }
23
+}

+ 18 - 0
tsconfig.spec.json

@@ -0,0 +1,18 @@
1
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
2
+{
3
+  "extends": "./tsconfig.json",
4
+  "compilerOptions": {
5
+    "outDir": "./out-tsc/spec",
6
+    "types": [
7
+      "jasmine"
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
+}

tum/whitesports - Gogs: Simplico Git Service

Keine Beschreibung

class-wp-theme-json-resolver.php 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <?php
  2. /**
  3. * WP_Theme_JSON_Resolver class
  4. *
  5. * @package WordPress
  6. * @subpackage Theme
  7. * @since 5.8.0
  8. */
  9. /**
  10. * Class that abstracts the processing of the different data sources
  11. * for site-level config and offers an API to work with them.
  12. *
  13. * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes).
  14. * This is a low-level API that may need to do breaking changes. Please,
  15. * use get_global_settings, get_global_styles, and get_global_stylesheet instead.
  16. *
  17. * @access private
  18. */
  19. class WP_Theme_JSON_Resolver {
  20. /**
  21. * Container for data coming from core.
  22. *
  23. * @since 5.8.0
  24. * @var WP_Theme_JSON
  25. */
  26. protected static $core = null;
  27. /**
  28. * Container for data coming from the theme.
  29. *
  30. * @since 5.8.0
  31. * @var WP_Theme_JSON
  32. */
  33. protected static $theme = null;
  34. /**
  35. * Whether or not the theme supports theme.json.
  36. *
  37. * @since 5.8.0
  38. * @var bool
  39. */
  40. protected static $theme_has_support = null;
  41. /**
  42. * Container for data coming from the user.
  43. *
  44. * @since 5.9.0
  45. * @var WP_Theme_JSON
  46. */
  47. protected static $user = null;
  48. /**
  49. * Stores the ID of the custom post type
  50. * that holds the user data.
  51. *
  52. * @since 5.9.0
  53. * @var int
  54. */
  55. protected static $user_custom_post_type_id = null;
  56. /**
  57. * Container to keep loaded i18n schema for `theme.json`.
  58. *
  59. * @since 5.8.0 As `$theme_json_i18n`.
  60. * @since 5.9.0 Renamed from `$theme_json_i18n` to `$i18n_schema`.
  61. * @var array
  62. */
  63. protected static $i18n_schema = null;
  64. /**
  65. * Processes a file that adheres to the theme.json schema
  66. * and returns an array with its contents, or a void array if none found.
  67. *
  68. * @since 5.8.0
  69. *
  70. * @param string $file_path Path to file. Empty if no file.
  71. * @return array Contents that adhere to the theme.json schema.
  72. */
  73. protected static function read_json_file( $file_path ) {
  74. $config = array();
  75. if ( $file_path ) {
  76. $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) );
  77. if ( is_array( $decoded_file ) ) {
  78. $config = $decoded_file;
  79. }
  80. }
  81. return $config;
  82. }
  83. /**
  84. * Returns a data structure used in theme.json translation.
  85. *
  86. * @since 5.8.0
  87. * @deprecated 5.9.0
  88. *
  89. * @return array An array of theme.json fields that are translatable and the keys that are translatable.
  90. */
  91. public static function get_fields_to_translate() {
  92. _deprecated_function( __METHOD__, '5.9.0' );
  93. return array();
  94. }
  95. /**
  96. * Given a theme.json structure modifies it in place to update certain values
  97. * by its translated strings according to the language set by the user.
  98. *
  99. * @since 5.8.0
  100. *
  101. * @param array $theme_json The theme.json to translate.
  102. * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
  103. * Default 'default'.
  104. * @return array Returns the modified $theme_json_structure.
  105. */
  106. protected static function translate( $theme_json, $domain = 'default' ) {
  107. if ( null === static::$i18n_schema ) {
  108. $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' );
  109. static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema;
  110. }
  111. return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain );
  112. }
  113. /**
  114. * Returns core's origin config.
  115. *
  116. * @since 5.8.0
  117. *
  118. * @return WP_Theme_JSON Entity that holds core data.
  119. */
  120. public static function get_core_data() {
  121. if ( null !== static::$core ) {
  122. return static::$core;
  123. }
  124. $config = static::read_json_file( __DIR__ . '/theme.json' );
  125. $config = static::translate( $config );
  126. static::$core = new WP_Theme_JSON( $config, 'default' );
  127. return static::$core;
  128. }
  129. /**
  130. * Returns the theme's data.
  131. *
  132. * Data from theme.json will be backfilled from existing
  133. * theme supports, if any. Note that if the same data
  134. * is present in theme.json and in theme supports,
  135. * the theme.json takes precedence.
  136. *
  137. * @since 5.8.0
  138. * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed.
  139. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports.
  140. *
  141. * @param array $deprecated Deprecated. Not used.
  142. * @param array $options {
  143. * Options arguments.
  144. *
  145. * @type bool $with_supports Whether to include theme supports in the data. Default true.
  146. * }
  147. * @return WP_Theme_JSON Entity that holds theme data.
  148. */
  149. public static function get_theme_data( $deprecated = array(), $options = array() ) {
  150. if ( ! empty( $deprecated ) ) {
  151. _deprecated_argument( __METHOD__, '5.9.0' );
  152. }
  153. $options = wp_parse_args( $options, array( 'with_supports' => true ) );
  154. if ( null === static::$theme ) {
  155. $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
  156. $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
  157. static::$theme = new WP_Theme_JSON( $theme_json_data );
  158. if ( wp_get_theme()->parent() ) {
  159. // Get parent theme.json.
  160. $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
  161. $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
  162. $parent_theme = new WP_Theme_JSON( $parent_theme_json_data );
  163. // Merge the child theme.json into the parent theme.json.
  164. // The child theme takes precedence over the parent.
  165. $parent_theme->merge( static::$theme );
  166. static::$theme = $parent_theme;
  167. }
  168. }
  169. if ( ! $options['with_supports'] ) {
  170. return static::$theme;
  171. }
  172. /*
  173. * We want the presets and settings declared in theme.json
  174. * to override the ones declared via theme supports.
  175. * So we take theme supports, transform it to theme.json shape
  176. * and merge the static::$theme upon that.
  177. */
  178. $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() );
  179. if ( ! static::theme_has_support() ) {
  180. if ( ! isset( $theme_support_data['settings']['color'] ) ) {
  181. $theme_support_data['settings']['color'] = array();
  182. }
  183. $default_palette = false;
  184. if ( current_theme_supports( 'default-color-palette' ) ) {
  185. $default_palette = true;
  186. }
  187. if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) {
  188. // If the theme does not have any palette, we still want to show the core one.
  189. $default_palette = true;
  190. }
  191. $theme_support_data['settings']['color']['defaultPalette'] = $default_palette;
  192. $default_gradients = false;
  193. if ( current_theme_supports( 'default-gradient-presets' ) ) {
  194. $default_gradients = true;
  195. }
  196. if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) {
  197. // If the theme does not have any gradients, we still want to show the core ones.
  198. $default_gradients = true;
  199. }
  200. $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients;
  201. // Classic themes without a theme.json don't support global duotone.
  202. $theme_support_data['settings']['color']['defaultDuotone'] = false;
  203. }
  204. $with_theme_supports = new WP_Theme_JSON( $theme_support_data );
  205. $with_theme_supports->merge( static::$theme );
  206. return $with_theme_supports;
  207. }
  208. /**
  209. * Returns the custom post type that contains the user's origin config
  210. * for the active theme or a void array if none are found.
  211. *
  212. * This can also create and return a new draft custom post type.
  213. *
  214. * @since 5.9.0
  215. *
  216. * @param WP_Theme $theme The theme object. If empty, it
  217. * defaults to the active theme.
  218. * @param bool $create_post Optional. Whether a new custom post
  219. * type should be created if none are
  220. * found. Default false.
  221. * @param array $post_status_filter Optional. Filter custom post type by
  222. * post status. Default `array( 'publish' )`,
  223. * so it only fetches published posts.
  224. * @return array Custom Post Type for the user's origin config.
  225. */
  226. public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) {
  227. if ( ! $theme instanceof WP_Theme ) {
  228. $theme = wp_get_theme();
  229. }
  230. $user_cpt = array();
  231. $post_type_filter = 'wp_global_styles';
  232. $args = array(
  233. 'numberposts' => 1,
  234. 'orderby' => 'date',
  235. 'order' => 'desc',
  236. 'post_type' => $post_type_filter,
  237. 'post_status' => $post_status_filter,
  238. 'tax_query' => array(
  239. array(
  240. 'taxonomy' => 'wp_theme',
  241. 'field' => 'name',
  242. 'terms' => $theme->get_stylesheet(),
  243. ),
  244. ),
  245. );
  246. $cache_key = sprintf( 'wp_global_styles_%s', md5( serialize( $args ) ) );
  247. $post_id = wp_cache_get( $cache_key );
  248. if ( (int) $post_id > 0 ) {
  249. return get_post( $post_id, ARRAY_A );
  250. }
  251. // Special case: '-1' is a results not found.
  252. if ( -1 === $post_id && ! $create_post ) {
  253. return $user_cpt;
  254. }
  255. $recent_posts = wp_get_recent_posts( $args );
  256. if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) {
  257. $user_cpt = $recent_posts[0];
  258. } elseif ( $create_post ) {
  259. $cpt_post_id = wp_insert_post(
  260. array(
  261. 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
  262. 'post_status' => 'publish',
  263. 'post_title' => 'Custom Styles',
  264. 'post_type' => $post_type_filter,
  265. 'post_name' => 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ),
  266. 'tax_input' => array(
  267. 'wp_theme' => array( wp_get_theme()->get_stylesheet() ),
  268. ),
  269. ),
  270. true
  271. );
  272. $user_cpt = get_post( $cpt_post_id, ARRAY_A );
  273. }
  274. $cache_expiration = $user_cpt ? DAY_IN_SECONDS : HOUR_IN_SECONDS;
  275. wp_cache_set( $cache_key, $user_cpt ? $user_cpt['ID'] : -1, '', $cache_expiration );
  276. return $user_cpt;
  277. }
  278. /**
  279. * Returns the user's origin config.
  280. *
  281. * @since 5.9.0
  282. *
  283. * @return WP_Theme_JSON Entity that holds styles for user data.
  284. */
  285. public static function get_user_data() {
  286. if ( null !== static::$user ) {
  287. return static::$user;
  288. }
  289. $config = array();
  290. $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() );
  291. if ( array_key_exists( 'post_content', $user_cpt ) ) {
  292. $decoded_data = json_decode( $user_cpt['post_content'], true );
  293. $json_decoding_error = json_last_error();
  294. if ( JSON_ERROR_NONE !== $json_decoding_error ) {
  295. trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() );
  296. return new WP_Theme_JSON( $config, 'custom' );
  297. }
  298. // Very important to verify that the flag isGlobalStylesUserThemeJSON is true.
  299. // If it's not true then the content was not escaped and is not safe.
  300. if (
  301. is_array( $decoded_data ) &&
  302. isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
  303. $decoded_data['isGlobalStylesUserThemeJSON']
  304. ) {
  305. unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
  306. $config = $decoded_data;
  307. }
  308. }
  309. static::$user = new WP_Theme_JSON( $config, 'custom' );
  310. return static::$user;
  311. }
  312. /**
  313. * Returns the data merged from multiple origins.
  314. *
  315. * There are three sources of data (origins) for a site:
  316. * default, theme, and custom. The custom's has higher priority
  317. * than the theme's, and the theme's higher than default's.
  318. *
  319. * Unlike the getters
  320. * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data},
  321. * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data},
  322. * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data},
  323. * this method returns data after it has been merged with the previous origins.
  324. * This means that if the same piece of data is declared in different origins
  325. * (user, theme, and core), the last origin overrides the previous.
  326. *
  327. * For example, if the user has set a background color
  328. * for the paragraph block, and the theme has done it as well,
  329. * the user preference wins.
  330. *
  331. * @since 5.8.0
  332. * @since 5.9.0 Added user data, removed the `$settings` parameter,
  333. * added the `$origin` parameter.
  334. *
  335. * @param string $origin Optional. To what level should we merge data.
  336. * Valid values are 'theme' or 'custom'. Default 'custom'.
  337. * @return WP_Theme_JSON
  338. */
  339. public static function get_merged_data( $origin = 'custom' ) {
  340. if ( is_array( $origin ) ) {
  341. _deprecated_argument( __FUNCTION__, '5.9.0' );
  342. }
  343. $result = new WP_Theme_JSON();
  344. $result->merge( static::get_core_data() );
  345. $result->merge( static::get_theme_data() );
  346. if ( 'custom' === $origin ) {
  347. $result->merge( static::get_user_data() );
  348. }
  349. return $result;
  350. }
  351. /**
  352. * Returns the ID of the custom post type
  353. * that stores user data.
  354. *
  355. * @since 5.9.0
  356. *
  357. * @return integer|null
  358. */
  359. public static function get_user_global_styles_post_id() {
  360. if ( null !== static::$user_custom_post_type_id ) {
  361. return static::$user_custom_post_type_id;
  362. }
  363. $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme(), true );
  364. if ( array_key_exists( 'ID', $user_cpt ) ) {
  365. static::$user_custom_post_type_id = $user_cpt['ID'];
  366. }
  367. return static::$user_custom_post_type_id;
  368. }
  369. /**
  370. * Determines whether the active theme has a theme.json file.
  371. *
  372. * @since 5.8.0
  373. * @since 5.9.0 Added a check in the parent theme.
  374. *
  375. * @return bool
  376. */
  377. public static function theme_has_support() {
  378. if ( ! isset( static::$theme_has_support ) ) {
  379. static::$theme_has_support = (
  380. is_readable( static::get_file_path_from_theme( 'theme.json' ) ) ||
  381. is_readable( static::get_file_path_from_theme( 'theme.json', true ) )
  382. );
  383. }
  384. return static::$theme_has_support;
  385. }
  386. /**
  387. * Builds the path to the given file and checks that it is readable.
  388. *
  389. * If it isn't, returns an empty string, otherwise returns the whole file path.
  390. *
  391. * @since 5.8.0
  392. * @since 5.9.0 Adapted to work with child themes, added the `$template` argument.
  393. *
  394. * @param string $file_name Name of the file.
  395. * @param bool $template Optional. Use template theme directory. Default false.
  396. * @return string The whole file path or empty if the file doesn't exist.
  397. */
  398. protected static function get_file_path_from_theme( $file_name, $template = false ) {
  399. $path = $template ? get_template_directory() : get_stylesheet_directory();
  400. $candidate = $path . '/' . $file_name;
  401. return is_readable( $candidate ) ? $candidate : '';
  402. }
  403. /**
  404. * Cleans the cached data so it can be recalculated.
  405. *
  406. * @since 5.8.0
  407. * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`,
  408. * and `$i18n_schema` variables to reset.
  409. */
  410. public static function clean_cached_data() {
  411. static::$core = null;
  412. static::$theme = null;
  413. static::$user = null;
  414. static::$user_custom_post_type_id = null;
  415. static::$theme_has_support = null;
  416. static::$i18n_schema = null;
  417. }
  418. /**
  419. * Returns the style variations defined by the theme.
  420. *
  421. * @since 6.0.0
  422. *
  423. * @return array
  424. */
  425. public static function get_style_variations() {
  426. $variations = array();
  427. $base_directory = get_stylesheet_directory() . '/styles';
  428. if ( is_dir( $base_directory ) ) {
  429. $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
  430. $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) );
  431. ksort( $nested_html_files );
  432. foreach ( $nested_html_files as $path => $file ) {
  433. $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) );
  434. if ( is_array( $decoded_file ) ) {
  435. $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) );
  436. $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data();
  437. if ( empty( $variation['title'] ) ) {
  438. $variation['title'] = basename( $path, '.json' );
  439. }
  440. $variations[] = $variation;
  441. }
  442. }
  443. }
  444. return $variations;
  445. }
  446. }