2020-06-03

Quasar + SSR + Quill WYSIWYG Editor

透過 npm 安裝 quill editor
npm install vue-quill-editor

在 Quasar SSR 模式之下需要另外設定 boot file,首先在 boot 資料夾內新增 quill.js 檔案 :
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'

import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme

Vue.use(VueQuillEditor, /* { default global options } */)

export default ({ app, router, store, Vue }) => {
  //init axios setup

  //interceptors setup
  //...
  
}

設定 quasar.conf.js 新增 quill 設定 :
...

// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/cli-documentation/boot-files
boot: [

  'i18n',
  'axios',
  { path:'quill', server:false },
],

...

在 component 內使用 quill editor
<template>
  <q-page>
    <quill-editor v-model="html_content"></quill-editor>
  </q-page>
</template>

2020-05-08

NodeJS 後端取得 Google Play Games 玩家資訊


玩家在 Android 裝置登入 Google Play Games 之後可以取得 Server Auth Code (參照 這裡),後端可利用 Server Auth Code 取得玩家的 Access Token,再由 Access Token 取得玩家資訊 (取得玩家資訊的 API 參照 這裡)。

底下是 NodeJS 利用 Server Auth Code 取得 Access Token 的實作 :
var querystring =require('querystring')
var https =require('https')

var post_data =querystring.stringify({
  grant_type:'authorization_code',
  code:/*CLIENT 給的 SERVER AUTH CODE*/,
  client_id:/*Google API Console 取得的 client id*/,
  client_secret:/*Google API Console 取得的 client secret*/,
  redirect_uri:'',
})

var post_options ={
  host:'oauth2.googleapis.com',
  port:443,
  path:'/token',
  method:'POST',
  headers:{
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(post_data)
  }
}

var post_req =https.request(post_options, (res)=>{
  res.setEncoding('utf8')
  var body =''
  res.on('data', (chunk)=>{
    body +=chunk
  })
  res.on('end', ()=>{
    console.log(body)
  })
})
post_req.write(post_data)
post_req.end()

執行之後應該會顯示類似內容 :
{
  "access_token": "ya29.a0Ae4lvC3sSLZwqGO8iSHsnzErS_GbBc021o9Q3FhDV_bb0mu3ukKF-_D5bf1xtvIWTFEAZLRODhlfdxetMrUEfQtt5jqhsH8oUrBlGll575VnLND_WGovv98y_tzVhceh6Ti42qrpdO9A6vKtWUGaHeOtxSb71KgP0U8",
  "expires_in": 3599,
  "refresh_token": "1//0es-vhoIE4URRCgYIARAAGA4SNwF-L9IrM22Sj9e2zM-SJpjgocfM4wcYhF2i8rRossKhSxCGj1nDEVy7BmSeWGmhl4",
  "scope": "https://www.googleapis.com/auth/games_lite",
  "token_type": "Bearer"
}

有了 access_token 就可以用來取得玩家資訊 :
var player_id ='玩家的 ID'
var api_key ='Google API Console 取得的 API Key'
var get_options ={
  host:'www.googleapis.com',
  port:443,
  path:'/games/v1/players/'+player_id+'?key='+api_key,
  method:'GET',
  headers:{
    Authorization: 'Bearer '+access_token,
    Accept: 'application/json'
  }
}
var get_req =https.request(get_options, (res)=>{
  res.setEncoding('utf8')
  var body =''
  res.on('data', (chunk)=>{
    body+=chunk
  })
  res.on('end', ()=>{
    console.log(body)
  })
})
get_req.end()

範例程式裡提到的 Client ID, Client Secret 和 API Key 可以由 Google API Console 取得,參考如下圖 :


其中 A. 是 API Key,C. 下載的 JSON 檔案裡包含 Client ID/ Client Secret,這裡要注意的是 B. OAuth 用戶端 ID 的類型是網路應用程式。

2020-01-08

Bootstrap 4 Navbar Style 調整

此文記錄在 Vue 架構下調整 Bootstrap 4 Navbar style,讓它與 Bootstrap 3 Navbar 相同。調整項目如下 :

1. 點選 Dropdown Menu 時不出現藍色外框 ( 似乎是 Safari 上才會出現 )。
2. 被滑鼠指標 hover 的 Dropdown Menu 項目顯示藍底白字。
3. Active Navbar 項目顯示深底色白字。
4. Dropdown Menu 增加陰影。

下圖是 Bootstrap 4 Navbar 在 Safari 上點選 Dropdown Menu 時的擷圖 :
下圖是調整後的 Navbar 擷圖 :

1. 安裝 sass-loader 與 node-sass
npm install sass-loader
npm install node-sass
2. 在 main.js 檔案所在資料夾新增檔案 bootstrap.scss,檔案內容如下 :
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";

$dropdown-link-hover-color: $light;
$dropdown-link-hover-bg:    $blue;
.dropdown-item{
  padding: .2rem 1.5rem !important;
}

.nav-link.active {
  background:$gray-900;
}

@media (min-width: 576px) {
  .navbar {
    padding-top: 0px !important;
    padding-bottom: 0px !important;
  }
}
.navbar-nav .nav-item .nav-link {
  padding-top: 1rem !important;
  padding-bottom: 1rem !important;

  padding-left: 1rem !important;
  padding-right: 1rem !important;
}

.nav-item a.active.focus,
.nav-item a.active:focus,
.nav-item a.focus,
.nav-item a:active.focus,
.nav-item a:active:focus,
.nav-item a:focus {
    outline: 0;
    outline-color: transparent;
    outline-width: 0;
    outline-style: none;
    box-shadow: 0 0 0 0 rgba(0,123,255,0);
}

@import '~bootstrap/scss/bootstrap';
3. main.js 檔案調整如下 :
import Vue from 'vue'
import App from './App.vue'

import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import './bootstrap.scss'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

Vue 2.6 + Bootstrap 4

1. 首先安裝 vue-cli ( 安裝的版本是 4.1.2 )
npm install -g @vue/cli

2. 利用 vue-cli 建立 project
vue create my-vue-project
出現 present 選項時直接選擇預設選項 default (babel, eslint),完成之後會建立一個 my-vue-project 資料夾,在資料夾內執行 npm run serve 之後用瀏覽器連線至 http://localhost:8080/,成功的話可以看到 welcome to vue 的預設畫面。

3. 安裝 bootstrap,在 my-vue-project 資料夾執行 : ( 安裝的版本是 4.4.1 )
npm install bootstrap

4. 安裝 jquery 及 popper : (jquery 版本是 3.4.1, popper.js 版本是 1.16.0
npm install jquery
npm install popper.js

5. 在 vue 上測試 bootstrap :
a. 首先在 main.js import bootstrap,main.js 檔案修改如下 :
import Vue from 'vue'
import App from './App.vue'

//新增 bootstrap import
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
b. 在 components 資料夾內新增加一個 vue component,檔名為 BootstrapTest.vue,檔案內容如下 :
<template>
  <div class="container-fluid">
    <!-- Modal -->
    <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
          <div class="modal-body">
            ...
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
            <button type="button" class="btn btn-primary">Save changes</button>
          </div>
        </div>
      </div>
    </div>

    <!-- Button Test -->
    <div class="card">
      <div class="card-body">
        <ul class="list-group list-group-flush">
          <li class="list-group-item">      
            <h5 class="card-title">Button Test</h5>
            <button type="button" class="btn btn-primary">Primary</button>
            <button type="button" class="btn btn-secondary">Secondary</button>
            <button type="button" class="btn btn-success">Success</button>
            <button type="button" class="btn btn-danger">Danger</button>
            <button type="button" class="btn btn-warning">Warning</button>
            <button type="button" class="btn btn-info">Info</button>
            <button type="button" class="btn btn-light">Light</button>
            <button type="button" class="btn btn-dark">Dark</button>
            <button type="button" class="btn btn-link">Link</button>
          </li>
          <li class="list-group-item">
            <h5 class="card-title">Dropdowns / Input</h5>
            <div class="btn-group">
              
                <div class="row">
                  <div class="col-sm-6">
                    <button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                      Action
                    </button>
                    <div class="dropdown-menu">
                      <a class="dropdown-item" href="#">Action</a>
                      <a class="dropdown-item" href="#">Another action</a>
                      <a class="dropdown-item" href="#">Something else here</a>
                      <div class="dropdown-divider"></div>
                      <a class="dropdown-item" href="#">Separated link</a>
                    </div>

                  </div>
                  <div class="col-sm-6">
                    <div class="input-group">
                      <div class="input-group-prepend">
                        <span class="input-group-text" id="basic-addon1">@</span>
                      </div>
                      <input type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="basic-addon1">
                    </div>

                  </div>
                </div>
              

            </div>
          </li>
          <li class="list-group-item">
            <h5 class="card-title">Modal</h5>
            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
              Launch demo modal
            </button>
          </li>
          <li class="list-group-item">
            <h5 class="card-title">Toast</h5>
            <button type="button" class="btn btn-primary" v-on:click="showToastr">
              Show Toast
            </button>
          </li>
        </ul>
      </div>
    </div>

    <!-- Toast -->
    <div id="toast" class="toast" style="position: absolute; top: 10px; right: 10px; display:none;">
      <div class="toast-header">
        <strong class="mr-auto">Bootstrap</strong>
        <small class="text-muted">11 mins ago</small>
        <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="toast-body">
        Hello, world! This is a toast message.
      </div>
    </div>
  </div>
</template>

<script>
import $ from 'jquery'
export default {
  methods: {
    showToastr: function () {
      $('#toast').toast('show')
      $('#toast').show()
    }
  },
  mounted(){
    $('.toast').toast({
      autohide:false
    })
  }
}
</script>

c. 把預設的 HelloWorld.vue 替換成 BootstrapTest.vue,App.vue 檔案修改如下 :
<template>
  <div id="app">
    <!-- <img alt="Vue logo" src="./assets/logo.png"> -->
    <!-- <HelloWorld msg="Welcome to Your Vue.js App"/> -->
    <BootstrapTest/>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
import BootstrapTest from './components/BootstrapTest.vue'

export default {
  name: 'app',
  components: {
    BootstrapTest
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
此時瀏覽器應該能自動 reload 並顯示畫面 :
此時可以測試看看各元件顯示,Modal/Toast 運作是否正常,都能運作的話那 Bootstrap 應該是成功 import 了。