반응형

자바스크립트 filter 메소드에 대해 알아보겠습니다. Filter 메소드는 배열 내부를 순환하면서 특정 조건에 부합하는 요소들을 새로운 배열에 담아 반환합니다.

 

1. 기본사용법

result = my_array.filter (a => {
    return [조건];
});

 

const my_array = [1,2,3,4,5,6,7,8,9,10]

const result = my_array.filter(a => {
  return a % 2 == 0; // a중 2로 나눈 나머지가 0(즉, 짝수)인 요소
});

console.log(result);

// [ 2, 4, 6, 8, 10 ]

 

 

2. 객체 다루기

const my_array = [
    {name:'홍길동', age:28},
    {name:'이순신', age:37},
    {name:'유관순', age:18}
];

const result = my_array.filter(a => {
  return a.age > 20; // 20세 이상인 요소
});

console.log(result);

// [ { name: '홍길동', age: 28 }, { name: '이순신', age: 37 } ]

 

3. Map, Reduce로 filter 구현하기

 

map, reduce함수로도 filter를 구현할 수 있습니다.

[map 예시]

const my_array = [
    {name:'홍길동', age:28},
    {name:'이순신', age:37},
    {name:'유관순', age:18}
];

const result = my_array.map(a=>{
  if(a.age>20){
    return a;
  }
})
console.log(result);

// [ { name: '홍길동', age: 28 }, { name: '이순신', age: 37 }, undefined ]

그런데 이 경우 마지막에 undefined가 들어갑니다. map과 filter의 특성이 다르니 상황에 따라 적절히 사용하면 좋을 것 같습니다.

 

[reduce 예시]

const my_array = [
    {name:'홍길동', age:28},
    {name:'이순신', age:37},
    {name:'유관순', age:18}
];

result = my_array.reduce((acc, cur) => {
  if (cur.age > 20) acc.push(cur);
  return acc;
}, []);

console.log(result);

// [ { name: '홍길동', age: 28 }, { name: '이순신', age: 37 } ]

 

오늘은 이름과 같이 직관적으로 사용할 수 있어서 자주사용하게되는 filter메소드에 대해 간단한 사용법 몇가지, 그리고 참고로 map과 reduce를 사용하여 filter 구현하는 법을 알아보았습니다.

반응형
반응형

reduce 함수는 자바스크립트에서 배열 또는 리스트 안의 요소들을 정렬하거나 특정 조건에 부합하면 합계나 평균 등을 계산하는데 사용되는 함수입니다.  개인적으로 자주 쓰지는 않지만 잘 알고있으면 많이 유용하게 쓸 수 있을것 같아서 정리해놓겠습니다.

 

1. 기본사용법

result = my_array.reduce((acc, cur, i) => {
    return (acc~연산~cur);
}, acc초기값);

- acc: accumulate(누적값)

- cur: current(현재값)

- acc초기값: 누적하기 전에 시작값을 지정

const my_array = [1,2,3,4,5]
const result = my_array.reduce((acc, cur, i) => {
  console.log(acc, i);
  return acc + cur;
}, 0);

console.log(result);

reduce함수는 주로 누적합 또는 누적곱을 구할때 많이 씁니다. 위 예제도 가장 기본적인 누적합을 나타내고 있습니다. acc초기값에 0을 주면 누구나 예상하는 배열의 누적값을 구하게 되고, 거기에 다른 값을 주게 되면 0이아닌 다른 시작값을 갖고 더해나간다고 보시면 되겠습니다.

 

2. 홀짝 구분하기

const my_array2=[1,2,3,4,5,6,7]

const result2 = my_array2.reduce((acc, cur, i) => {
  acc.push(cur % 2 ?'홀수' : '짝수');
  return acc;
},[])

console.log(result2);

// ['홀수', '짝수', '홀수', '짝수', '홀수', '짝수', '홀수']

짝수만 Filter 

const my_array3=[1,2,3,4,5,6,7]

const result3 = my_array3.reduce((acc, cur, i) => { 
     if(i%2){
       acc.push(cur);
     }
     return acc; 
 },[]);
console.log(result3);

// [ 2, 4, 6 ]

 

3. 객체 다루기

- 나이에 3을 곱한 새로운 배열을 생성하는 예

const obj_array=[
  {
    name: "one",
    age: 1,
  },
  {
    name: "two",
    age: 2,
  },
  {
    name: "three",
    age: 3,
  }
]

const result = obj_array.reduce((acc, cur,i)=>{
  acc.push(cur.age*3)
  return acc;
},[])
console.log(result);

//[ 3, 6, 9 ]

위의 예제는 map함수와 비슷하게 사용이 가능합니다. 이처럼 reduce를 사용하면 map이나 filter, sort, find 등 다양한 함수들을 reduce로 직접 구현해서 사용할 수 있습니다.

 

Reduce함수에 대해 간단한 사용법 몇가지를 알아보았습니다.

 

반응형
반응형

Javascript에서 객체배열(Object Array) 내부의 요소를 변경해야할 경우가 자주 발생합니다. 예를 들면 아래와 같은 객체 배열이 주어질 경우 특정 요소 또는 전체 요소의 age값을 변경할 필요가 있습니다.

const obj_array=[
  {
    name: "one",
    age: 1,
  },
  {
    name: "two",
    age: 2,
  },
  {
    name: "three",
    age: 3,
  }
]

 

이때 유용하게 사용할 수 있는 함수로 Map 메소드가 있습니다. 

Map은 내부에서 callback 함수를 실행한 결과를 가지고 새로운 배열을 만들 때 사용합니다.

 

1. 모든 요소의 값 변경하기

// age를 모두 4로
const result = obj_array.map(obj => {
  return {...obj, age: 4}
});
console.log(result); 
//[{ name: 'one', age: 4 },{ name: 'two', age: 4 },{ name: 'three', age: 4 }]

// age에 4배하기
const result = obj_array.map(obj => {
  return {...obj, age:obj.age*4}
});
console.log(result); 
//[{ name: 'one', age: 4 },{ name: 'two', age: 8 },{ name: 'three', age: 12 }]

 

2. 특정 요소들만 추출하기

const result = obj_array.map(obj => obj.name);
console.log(result); 
//[ 'one', 'two', 'three' ]

 

3. 특정 요소의 값 변경하기

age가 3인경우 값을 4로 바꾸기

const result = obj_array.map(obj => 
  {
    return obj.age===3 ? {...obj, age: 4} : obj
  });
console.log(result);
//[{ name: 'one', age: 1 },{ name: 'two', age: 2 },{ name: 'three', age: 4 }]

 

 

반응형
반응형

프론트엔드 프레임워크 하면 Angular / React / Vue 그리고 최근 부각되고 있는 Svelte가 있는데요. 전문 개발자는 아니지만 조금은 손대본 경험을 토대로(Angular제외..) 특성을 알아보도록 하겠습니다.

 

Stackoverflow Ranking에서 여러 프로그래밍 언어 및 프레임워크에 대한 점유율, 선호도 등 자세한 정보는 확인하실 수 있습니다. 해당 정보에 따르면 웹 프레임워크의 점유율은 아래 그래프와 같습니다. 이 중에 프론트엔드 프레임워크만 보면 React가 제일 상단에 있고, jQuery, Angular, Vue 그리고 한참 아래쪽에 Svelte가 있네요

 

< 점유율 >

 

역시 Stackoverflow Ranking에 따르면 이번엔 Svelte가 제일 위에 있고, 그다음 React, Vue이고 Angular가 하위에 있는 모습입니다. Svelte가 가장 신생 프레임워크인데, 선호도 측면에서는 많은 개발자들이 만족하고 있다는 의미네요.

 

< 가장 사랑받는 / 피하고싶은>

 

그럼 각 프레임워크의 특징을 한번 살펴보겠습니다.

1. Angular

Angular는 구글에서 만든 Javascript 프레임워크로 초기 1.0버전에서는 AngularJS, 2.0버전부터는 그냥 Angular라고 칭했습니다. 특이한 것은 1.0은 Javascript, 2.0은 Typescript를 기본 언어로 채택하였습니다. Angular는 필요한 요소들을 모두 포함하고 있는 것이 특징입니다. 그만큼 다른 라이브러리들을 추가로 설치할 필요가 없으며, 대신 학습해야할 양이 많은 것으로 알려져 있습니다. 

양방향 데이터바인딩을 지원하며, 태그이름, 템플릿 파일, CSS파일 정보를 decorator문법을 이용하여 전달하는 선언적 코딩 스타일을 사용한다는 특징을 갖고 있습니다. 

 

 

2. React

React는 페이스북에서 만든 라이브러리입니다. 라이브러리냐 프레임워크냐로 갑론을박이 많이 있었지만, 최근에는 라이브러리다라는 의견이 대체적으로 받아들여지고 있는 모습입니다. React는 Virtual-DOM이라는 개념을 적용하였습니다. DOM은 HTML문서를 제어할 수 있는 API 트리 자료구조인데, 실제 DOM을 사용하여 변경시마다 모든 요소를 Update하기에는 무거우며 속도가 느려지는 한계가 있어 가상 DOM이라는 개념을 적용하고 있습니다. 이는 Vue도 마찬가지입니다. 

또다른 특징으로는 Component에 의한 재사용 가능한 UI 생성을 들 수 있으며, 이를 위해 내부적으로 JSX라는 특별한 문법을 사용합니다. 이는 자바스크립트에 HTML을 내포한듯한 모습을 하고 있습니다. 

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Hello React
        </a>
      </header>
    </div>
  );
}

Component로 각 요소를 구분해가며 전체 페이지를 구축해가는 방식은 개발자들이 쉽게 접근할 수 있도록 도왔던 것으로 보입니다. Props, State 등의 개념을 익히다가 나중에 상태관리자로 Redux라는 개념을 사용할 때즈음 되면 슬슬 멘붕이 오기 시작합니다. 요즘은 Redux toolkit를 써서 쉽게 접근할 수 있도록 했다고는 하지만, 개념이 바뀌는 것은 아니므로  학습곡선이 완전히 낮아졌다고 하기는 힘들어보입니다.

 

3. Vue

Evan You에 의해 시작된 Frontend Framework로 React와 마찬가지로 Virtual-DOM의 개념을 사용합니다. Evan You는 Angular를 이용한 개발을 하던 중 본인이 필요한 기능들만 추려서 만들었다고 하고, 시기적으로도 React보다 1년 뒤에 나온만큼 Angular, React 등으로부터 영향을 받았을 것으로 보입니다. 단방향 데이터 바인딩만 지원하는 React와는 달리 Vue는 양방향 데이터바인딩을 지원합니다. React를 공부하다보면 Redux 상태관리자를 사용하면서 학습난이도가 올라가는데, Vue에서의 상태관리자인 Vuex는 이보다 훨씬 이해하기 쉬운 장점이 있습니다. 아래는 컴포넌트를 정의하는 샘플 Vue 코드인데, 보시면 template, script, style의 세 부분으로 나뉘어져 있습니다. 이런 구분에 대해서는 개발자마다 호불호가 있지만, 처음 접근하는 입장에서 가독성이 좋은 것은 큰 장점 같습니다.

<template>
    <div class="btn">
        <slot></slot> <!--버튼처럼 텍스트를 표시하기위한 속성-->
    </div>
    <h1 @dblclick="$emit('abc')">
		ABC
    </h1>
</template>
    
<script>
export default{
	emits: [
		'abc'
	]    
}
</script>

<style scoped>
.btn{
    display: inline-block;
    margin: 4px;
    padding: 6px 12px;
    border-radius: 4px;
    background-color: gray;
    color: white;
    cursor: pointer;
}
</style>

 

4. Svelte

기존의 Angular/React/Vue에이어 Svelte는 상당히 나중에 진출한 후발주자입니다. 그런데도 Stackoverflow Ranking에서 보면 개발자가 사랑하는 프레레임워크 2위를 차지할 정도로 관심도가 엄청나며(1위는 Phoenix), 다른 프레임워크들을 추격하고 있습니다. 

The State of JS 2021 Web Frontend Framework

svelte 는 라이브러리/프레임워크가 아닌 컴파일러라고 합니다. Svelte코드를 실행시점에서 해석하지 않고 컴파일 단계에서 Vanilla Javascript 번들로 만들기 때문에 다른 라이브러리를 함께 배포할 필요가 없어집니다. 또다른 특징으로는 React나 Vue처럼  Virtual-DOM을 사용하지 않고 실제 DOM을 직접 제어합니다. 성능적으로 작은 번들사이즈와 DOM의 직접제어 덕분에 다른 프레임워크보다 빠른 반응속도를 보여줍니다. 

문법은 개인적인 느낌이 Vue랑 유사하게 생겼습니다. 가시성도 좋고 쉽다고 생각이 듭니다. 화면 Update할 시점을 인식시켜주기 위한 특이한 용법들도 있습니다만 다른 라이브러리들의 학습곡선에 비할 바가 아니라고 생각됩니다. 상태관리에서도 redux, vuex와 같이 외부 라이브러리를 설치할 필요가 없이 내부에 store를 기본으로 내장하고 있습니다. 

<script>
let name = "teo.yu"
</script>

<div>Hello world! {name}</div>

<style>
div { color: red }
</style>

<번들사이즈 비교>

파일사이즈가 작으면 다운로드속도가 올라가서 체감속도가 빨라집니다. 아래 결과를 보면 Svelte가 가장 작은 사이즈를 보이고 있습니다.

 

이런 성능과 러닝커브 등의 잇점으로 최근 svelte의 인기가 올라가고 있는 것으로 보입니다. 하지만 아직까지 시장의 메인은 React, 그리고 최근에는 Vue도 많이 도입하고 있는 것 같습니다. 저도 Vue를 주로 사용하여 개인 프로젝트들을 만들고 있는데 React에서 어려운 개념들을 익히고 나서 접근해서인지, Vue 너무 좋습니다. 성능도 준수하게 뽑아주는 것 같고, Reference도 잘 되어있어서 당분간은 Vue를 활용할 것 같습니다. Svelte는 조금 더 저변이 넓어지면 그때 적용해볼까 합니다. 

반응형
반응형

 

1. 조건이 필요할 때 => 3항 연산자

score > 5 ? 'Good': 'Bad';
// 조건이 true이면 'Good', false이면 'Bad'

2. 조건이 필요할 때 => nullish coalescing

const message = text ?? 'Nothing to display';
//text가 있으면 text, 없으면(null, undefined) 'Nothing to display'

//함수 default parameter : null 인 경우에는 null을 출력
function printMessage(text = 'Nothing to display'){
	console.log(text);
}

// Logical Or ||
leftExpr ?? right Expr
// falsy(null, undefined, false, 0, -0, NaN, "", '', ``)에 right Expr 실행

/* 함수도 할당 가능 */

 

3. Object destructing

const { name, age } = person;
console.log(name, age)

 

4. Spread syntax :

// 기존 object로부터 새로운 object를 작성하기

const item = {type: 'Y', size: 'M'};
const detail = {price: 20, made: 'Korea', gender: 'M'};

// Good
const shirt0 = Object.assign(item, detail);

// Better (spread syntax: 배열도 적용 가능)
const shirt = {...item, ...detail, price: 40};	// 신규 값 할당 가능

 

5. Optional Chaning

// Bad
if(person.job && person.job.title){
	console.log(person.job.title);
}

// Good (Optional Chinaing)
if(person.job?.title){
	console.log(person.job.title);
}

// Better (Optional Chinaing + Nullish coalescing)
const title = person.job?.title ?? 'No job yet';
console.log(title);

 

6. Template Literals ( ` : 백틱 )

console.log(`Hello ${name}, Your current score is : ${score}`);

 

7. Loop가 필요할 때 (For 대신)

// 짝수만 골라, 4를 곱해, 총 합 구하기
const items = [1, 2, 3, 4, 5, 6];
const result = items
	.filter((num) => num %2 ===0)
	.map((num) => num *4)
	.reduce((a, b) => a + b, 0);
    
console.log(result);

 

8. 비동기 처리 (promise => async/await)

// 기존
function displayUser(){
	fetchUser()
    	.then((user) => {
         fetchProfile(user)
         	.then((profile) => {
            	updateUI(user, profile);
         });
});

// 변경
async function displayUser(){
  const user = await fetchUser();
  const profile = await fetchProfile(user);
  updateUI(user, profile);
}

9. 중복 제거

const array = ['dog','cat','doggy','dog','lamb','cat'];
console.log([...new Set(array)]);
반응형
반응형

palette.js : gray, red, pink, grape, violet, indigo, blue, cyan, teal, green, lime, yellow, ornage

palette.js
0.00MB

 

일부는 적어놓고 붙여써야겠습니다.

const palette = {
  gray: [
    '#f8f9fa',
    '#f1f3f5',
    '#e9ecef',
    '#dee2e6',
    '#ced4da',
    '#adb5bd',
    '#868e96',
    '#495057',
    '#343a40',
    '#212529',
  ],
  cyan: [
    '#e3fafc',
    '#c5f6fa',
    '#99e9f2',
    '#66d9e8',
    '#3bc9db',
    '#22b8cf',
    '#15aabf',
    '#1098ad',
    '#0c8599',
    '#0b7285',
  ],
};

export default palette;

 

반응형
반응형

보안을 위해 API Key가 노출되지 않도록 API 요청 부분을 서버리스 함수로 작성하도록 하겠습니다.

추가로 API Key는 환경변수로 등록하여 Github 등으로 올릴 때 노출되는 것을 막기위해 추가적으로 보안설정을 하겠습니다.

 

1.netlify-cli 설치

npm i -D netlify-cli

2.netlify.toml

# Netlify Dev
# https://cli.netlify.com/netlify-dev/#netlifytoml-dev-block

# 제품 모드
[build]
  command = "npm run build"
  functions = "functions" # Netlify 서버리스 함수가 작성된 디렉토리를 지정합니다.
  publish = "build" # 프로젝트 빌드 결과의 디렉토리를 지정합니다.

# 개발 모드
[dev]
  framework = "#custom" # 감지할 프로젝트 유형을 지정합니다. 앱 서버 및 `targetPort` 옵션을 실행하는 명령 옵션은 `#custom`입니다.
  command = "npm run dev" # 연결할 프로젝트의 개발 서버를 실행하는 명령(스크립트)을 지정합니다.
  targetPort = 8080 # 연결할 프로젝트 개발 서버의 포트를 지정합니다.
  port = 8888 # 출력할 Netlify 서버의 포트를 지정합니다.
  publish = "public" # 프로젝트의 정적 콘텐츠 디렉토리를 지정합니다.
  jwtRolePath = "app_metadata.authorization.roles" # JWT 기반 리디렉션에 대한 역할 값을 찾아야하는 객체 경로를 지정합니다.
  autoLaunch = true # Netlify 서버가 준비되면 자동으로 브라우저를 오픈할 것인지 지정합니다.


3. package.json

 "scripts": {
    "dev": "snowpack dev",
    "dev:netlify": "netlify dev",
    "build": "snowpack build"
  },

4. /functions/movie.js

Netlify에서 작동할 서버리스 함수입니다. Netlify에서는 /functions/movie.js에 함수를 작성하게 되면, 'http://localhost:8079/.netlify/functions/movie' 와 같이 요청할 수 있습니다. node.js 환경에서 작동하므로 require로 모듈을 가져와야합니다.

const axios = require('axios')
const { OMDB_API_KEY } = process.env

exports.handler = async function(event, context) {
  const params = JSON.parse(event.body)
  const { title, type, year, page, id } = params
  //const OMDB_API_KEY='abd6b67a'

  const url = id 
    ? `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${id}&plot=full` 
    : `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`

  try{
    const res = await axios.get(url)
    console.log(res.data)
    if (res.data.Error) { //OMDB의 특수한 경우(정상반환되었으나 내용에 에러가 포함됨) 처리
      //reject(res.data.Error)
      return {
        statusCode: 400,
        body: res.data.Error
      }
    }
    //resolve(res)
    return {
      statusCode: 200,
      body: JSON.stringify(res.data)
    }
  } catch(error) {
    console.log(error.response.status)
    //reject(error.message)
    return {
      statusCode: error.response.status,
      body: error.message
    }
  }
}

 

5. 테스트용 app.svelte 소스

<script>
  import { fade } from 'svelte/transition'
  import Router,{ location } from 'svelte-spa-router'  
  import routes from '~/routes/index.js'
  import Header from '~/components/Header.svelte'  
  import Footer from '~/components/Footer.svelte'
  
  import axios from 'axios'

  async function test() {
    const res = await axios.get('/.netlify/functions/test')
    console.log(res)
  }
  test()
</script>

<Header />
{#key $location}
  <div in:fade>
    <Router 
      routes={routes} 
      restoreScrollState={true}/>
  </div>
{/key}
<Footer />

6. Netlify 환경변수에  OMDB_API_KEY 설정

경로: Netlify -> Site settings -> Build & deploy -> Environment -> Environment variables -> Edit Variables

 

7. 로컬서버에서도 테스트 가능하도록 환경변수 설정( .env 파일 생성)

OMDB_API_KEY=7035c60c

(주의) .gitignore에 추가하여 깃헙에 업로드되지 않도록 한다.

 

8. 추후 유지보수를 위한 설정 변경

1) package.json 시작 스크립트 변경

"scripts": {
    "dev:snowpack": "snowpack dev",
    "dev": "netlify dev",
    "build": "snowpack build"
  },

2) netlify.toml파일 변경

command를 바꿔주고, 시작 포트를 8079로 변경한다.

# 개발 모드
[dev]
  framework = "#custom" # 감지할 프로젝트 유형을 지정합니다. 앱 서버 및 `targetPort` 옵션을 실행하는 명령 옵션은 `#custom`입니다.
  command = "npm run dev:snowpack" # 연결할 프로젝트의 개발 서버를 실행하는 명령(스크립트)을 지정합니다.
  targetPort = 8079 # 연결할 프로젝트 개발 서버의 포트를 지정합니다.
  port = 8080 # 출력할 Netlify 서버의 포트를 지정합니다.
  publish = "public" # 프로젝트의 정적 콘텐츠 디렉토리를 지정합니다.
  jwtRolePath = "app_metadata.authorization.roles" # JWT 기반 리디렉션에 대한 역할 값을 찾아야하는 객체 경로를 지정합니다.
  autoLaunch = true # Netlify 서버가 준비되면 자동으로 브라우저를 오픈할 것인지 지정합니다.

3) snowpack.config.js 파일의 시작 포트 변경

module.exports = {
  mount: {
    public: '/',
    src: '/_dist_'
  },
  plugins: [
    '@snowpack/plugin-svelte',
    ['@snowpack/plugin-babel', {
      transformOptions: babelOptions()
    }],
    '@snowpack/plugin-dotenv',
    '@snowpack/plugin-optimize'
  ],
  alias: {
    '~': './src'
  },
  devOptions: {
    port: 8079
  }
}
반응형
반응형

1. 백엔드

헤로쿠에 백엔드를 배포할 경우 CORS(Cross Origin Resource Sharing) 문제를 해결하기 위해 라이브러리를 설치해야 합니다. Koa로 작업할 경우 'koa2-cors' 를 설치해주면 됩니다.

그리고 라우팅 설정 부분에서 아래의 구문을 추가해줍니다.

import cors from 'koa2-cors';

app.use(cors());

 

2. 프론트엔드

Github의 소스를 자동으로 배포해주는 Netlify의 경우에도 일부 수정할 부분이 필요합니다. Local에서 작업할 경우에는 같은 localhost에서 데이터를 주고 받아서 몰랐었는데, 이제 헤로쿠의 백엔드 데이터를 받아와야 하기 때문에 조금 달라져야 합니다. 그래서 필요한 것이 _redirects파일 입니다. /public 폴더에 _redirects라는 이름의 파일을 생성한 후 아래와 같이 작성해줍니다. 

/*  /index.html 200
/api/* https://헤로쿠앱이름.herokuapp.com/api/:splat  200

프론트엔드 화면에서 버튼이나 페이지 이동에 의해 서버 요청이 발생할 경우, url에 해당하는 경로를 적고, 그에 대응하는 실제 주소를 적어주면 됩니다. 뒤의 :splat은 *과 동일한 의미라고 합니다. 그리고 뒤의 숫자 200은 아직 잘 모르겠습니다. 정상 접속을 의미하는 200이라면 굳이 붙여야 하는지도 잘 모르겠고... 다른 숫자를 붙일 수도 있는데, 301은 url을 영구적으로 옮겼을 때 사용하고, 검색엔진 최적화에 좋다고 합니다. 302 일시적으로 옮겼을 때 사용한다고 합니다.

 

~끝~

반응형

+ Recent posts