cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              $wiki-grey: #eaecf0;
$border: 1px solid darken($wiki-grey, 15%);
$wiki-logo: url("https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/300px-Wikipedia-logo-v2.svg.png");

div,
section {
  box-sizing: border-box;
}

#App {
  font-family: 'Helvetica Neue','Helvetica','Nimbus Sans L','Arial','Liberation Sans',sans-serif;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.SearchPanel {
  padding: .6rem;
  z-index: 1;
  box-shadow: 0 0 4px 2px rgba(0,0,0,.3);
  font-size: 1.2rem;
  display: flex;
  input {
    flex: 1;
  }
  select,
  button {
    margin-left: .5em;
    &:enabled {
      cursor: pointer;
      &:hover {
        background-color: #eee;
      }
    }
  }
  input,
  select,
  button {
    border: $border;
    padding: .2rem .5rem;
    &:focus {
      border-color: #555;
      outline: none;
    }
  }
}

#mainPanel {
  background-repeat: no-repeat;
  background-position: center;
  background-color: $wiki-grey;
  background-image: $wiki-logo;
  display: flex;
  flex: 1;
  flex-direction: row;
  .ResultList {
    background: $wiki-grey;
    flex: 1;
    width: 100%;
    height: 100%;
    overflow-y: scroll;
    
    .Result {
      padding: 0.5rem;
      border-bottom: $border;  
      cursor: pointer;
      &:hover,
      &:focus {
        background: lighten($wiki-grey, 5%);
        outline: none;
      }
      .title {
        font-weight: bold;
        font-size: .9em;
      }
      .text {
        font-size: .8em;
      }
    }
  }
  .Preview {
    border: 0;
    border-left: $border;
    flex: 2;
    width: 100%;
    background: $wiki-grey;
  }
}
            
          
!
            
              const { connect, Provider } = ReactRedux
const { debounce } = _ // Lodash
const DEBOUNCE_TIMEOUT = 250 // wait n ms before calling api

// Actions

const SEARCH_CHANGED = "SEARCH_CHANGED"
const searchChanged = text => ({ type: SEARCH_CHANGED, payload: text })

const LANGUAGE_CHANGED = "LANGUAGE_CHANGED"
const languageChanged = language => ({ type: LANGUAGE_CHANGED, payload: language })

const RESULTS_FETCHED = "RESULTS_FETCHED"
const resultsFetched = data => ({ type: RESULTS_FETCHED, payload: data })

const SELECT_ARTICLE = "SELECT_ARTICLE"
const selectArticle = url => ({ type: SELECT_ARTICLE, payload: url })

// Thunk Actions

const searchAction = text => (dispatch, getState) => {
  // update search in store and queue api call to wikipedia
  dispatch(searchChanged(text))
  const language = getState().search.language
  // debounced api call
  debouncedWikiSearch(text, language, data => dispatch(resultsFetched(data)))
}

// Utility functions for wikipedia api

const performWikiSearch = (text, language, callback) => {
  // perform http call to wikipedia opensearch api
  if (!text) return // abort if search is empty
  const url = buildUrl(text, language, "json")
  console.log(url)
  fetch(url, { mode: "cors" }) // use cors mode for cross origin sharing
    .then(response => response.json())
    .then(normalizeJsonData)
    .then(callback)
    .catch(console.error)
}
const debouncedWikiSearch = debounce(
  performWikiSearch, DEBOUNCE_TIMEOUT, {maxWait: DEBOUNCE_TIMEOUT * 2}
)
// https://lodash.com/docs/4.17.4#debounce

const normalizeJsonData = ([q, titles, texts, urls]) =>
  // convert results from wikipedia opensearch api into array of objects
  titles.map((title, index) => 
    ({ index, title, text: texts[index], url: urls[index] }))

const escapeQuery = query => encodeURIComponent(query).replace(/(%20)+/g, "+")

const buildUrl = (search, language = "en", format = "json", limit = 20) => {
  // builds wikipedia api url to fetch search results
  const attrs = {
    search: escapeQuery(search), // search query
    limit, // max number of results
    format, // json or xml
    action: "opensearch", // api action
    origin: "*" // needed for Cross-Origin resource sharing
  }
  const queryString = Object.entries(attrs)
    .map(([key, value]) => `${key}=${value}`)
    .join("&")
  return `https://${language}.wikipedia.org/w/api.php?${queryString}`
}

// Components

let SearchPanel = ({ text, language, searchChanged, languageChanged }) => {
  // search input, language select and clear button
  const clearButtonOnClick = e => {
    searchChanged("")
    searchInput.focus()
  }
  const searchInputOnChange = e => {
    searchChanged(e.target.value)
  }
  const languageSelectOnChange = e => {
    languageChanged(e.target.value)
    searchChanged(text) // force new fetch from api
  }
  const languages = [
    ["en", "English"],
    ["no", "Norwegian"],
    ["es", "Spanish"],
    ["de", "German"],
    ["fr", "French"]
  ]
  let searchInput = null // ref placeholder
  return (
    <section className="SearchPanel">
      <input
        value={text}
        onChange={searchInputOnChange}
        ref={input => (searchInput = input)}
        type="search"
        placeholder="Search Wikipedia"
        spellCheck={false}
        tabIndex={1}
      />
      <select onChange={languageSelectOnChange}>
        {languages.map(([val, text]) => (
          <option value={val} selected={val === language}>{text}</option>
        ))}
      </select>
      <button disabled={!text} onClick={clearButtonOnClick}>
        Clear
      </button>
    </section>
  )
}
SearchPanel = connect(
  store => store.search,
  dispatch => ({
    searchChanged: text => dispatch(searchAction(text)),
    languageChanged: value => dispatch(languageChanged(value))
  })
)(SearchPanel)

let ResultList = ({ results }) =>
  (results.length == 0
    ? null
    : // List of search results
      <div
        className="ResultList"
        ref={div => div && (div.scrollTop = 0)} // scroll to top on render
      >
        {results.map(props => <Result {...props} />)}
      </div>)
ResultList = connect(({ results }) => ({ results }))(ResultList)

let Result = ({
  index,
  title,
  text,
  onClick,
  onKeyDown
}) => // Single search result
(
  <div
    className="Result"
    tabIndex={index + 2}
    onKeyDown={onKeyDown}
    onClick={onClick}
  >
    <div className="title">{title}</div>
    <div className="text">{text}</div>
  </div>
)
Result = connect(null, (dispatch, { url }) => ({
  onKeyDown: e => e.keyCode == 13 && dispatch(selectArticle(url)),
  onClick: e => dispatch(selectArticle(url))
}))(Result)

let Preview = ({ url }) => {
  // Iframe containing wiki article served from wikipedia
  if (!url) return null
  // use mobile url if window is narrow
  const src = window.innerWidth > 1200
    ? url
    : url.replace(/wikipedia/, "m.wikipedia")
  return <iframe className="Preview" src={src} />
}
Preview = connect(({ url }) => ({ url }))(Preview)

const App = () => // Main container
(
  <section id="App">
    <SearchPanel />
    <section id="mainPanel">
      <ResultList />
      <Preview />
    </section>
  </section>
)

// Reducers

const searchReducer = (state = { text: "", language: "en" }, action) => {
  switch (action.type) {
    case SEARCH_CHANGED:
      return { ...state, text: action.payload }
    case LANGUAGE_CHANGED:
      return { ...state, language: action.payload }
    default:
      return state
  }
}
const resultsReducer = (state = [], action) => {
  switch (action.type) {
    case RESULTS_FETCHED:
      return action.payload
    case SEARCH_CHANGED:
      // clear results if search is empty
      return action.payload ? state : []
    default:
      return state
  }
}
const urlReducer = (state = "", action) => {
  switch (action.type) {
    case SELECT_ARTICLE:
      return action.payload
    case SEARCH_CHANGED:
      // clear iframe if search is empty
      return action.payload ? state : ""
    default:
      return state
  }
}
const rootReducer = Redux.combineReducers({
  search: searchReducer,
  results: resultsReducer,
  url: urlReducer
})

// use redux-thunk middleware for async api calls
let middleware = Redux.applyMiddleware(ReduxThunk.default)
// use redux devtools if available
middleware = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || Redux.compose)(
  middleware
)
// create Redux root store
const rootStore = Redux.createStore(rootReducer, middleware)

// render app
ReactDOM.render(
  <Provider store={rootStore}><App /></Provider>,
  document.getElementById("Root")
)
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console